def refresh_profiles(self, list_widget: QListWidget, new_values: typing.List[str]): index = self.get_index(list_widget.currentItem(), new_values) list_widget.clear() list_widget.addItems(new_values) if index != -1: list_widget.setCurrentRow(index)
class CaseList(QWidget): def __init__(self, facade: LibresFacade, notifier: ErtNotifier): self.facade = facade self.notifier = notifier QWidget.__init__(self) addHelpToWidget(self, "init/case_list") layout = QVBoxLayout() self._list = QListWidget(self) self._list.setMinimumHeight(100) self._list.setMaximumHeight(250) self._default_selection_mode = self._list.selectionMode() self.setSelectable(False) layout.addWidget(QLabel("Available cases:")) layout.addWidget(self._list) self._addRemoveWidget = AddRemoveWidget(self.addItem, self.removeItem, horizontal=True) self._addRemoveWidget.enableRemoveButton(False) layout.addWidget(self._addRemoveWidget) self._title = "New keyword" self._description = "Enter name of keyword:" self.setLayout(layout) notifier.ertChanged.connect(self.updateList) self.updateList() def setSelectable(self, selectable): if selectable: self._list.setSelectionMode(self._default_selection_mode) else: self._list.setSelectionMode(QAbstractItemView.NoSelection) def addItem(self): dialog = ValidatedDialog("New case", "Enter name of new case:", getAllCases(self.facade)) new_case_name = dialog.showAndTell() if not new_case_name == "": self.facade.select_or_create_new_case(new_case_name) self.notifier.ertChanged.emit() def removeItem(self): message = "Support for removal of items has not been implemented!" QMessageBox.information(self, "Not implemented!", message) def updateList(self): """Retrieves data from the model and inserts it into the list""" case_list = getAllCases(self.facade) self._list.clear() for case in case_list: self._list.addItem(case)
class HeaderEditDialog(QDialog): # name of the current preset; whether to set this preset as default; list of Columns header_changed = Signal(str, bool, list) def __init__(self, parent, table_header): super().__init__(parent) self.table_header = table_header self.default_preset_name = None self.preset_name = table_header.preset_name self.columns = deepcopy(table_header.columns) self.setupUi() self.update_output() def setupUi(self): self.resize(240, 400) self.vbox = QVBoxLayout(self) self.presetLabel = QLabel(self) self.columnList = QListWidget(self) self.setAsDefaultCheckbox = QCheckBox("Set as default preset", self) self.vbox.addWidget(self.presetLabel) self.vbox.addWidget(self.columnList) self.vbox.addWidget(self.setAsDefaultCheckbox) self.columnList.setDragDropMode(QListWidget.InternalMove) self.columnList.setDefaultDropAction(Qt.MoveAction) self.columnList.setSelectionMode(QListWidget.ExtendedSelection) self.columnList.setAlternatingRowColors(True) self.columnList.installEventFilter(self) self.columnList.setContextMenuPolicy(Qt.CustomContextMenu) self.columnList.customContextMenuRequested.connect(self.open_menu) self.columnList.model().rowsMoved.connect(self.read_columns_from_list) # for a dumb qss hack to make selected checkboxes not white on a light theme self.columnList.setObjectName("ColumnList") buttons = QDialogButtonBox.Reset | QDialogButtonBox.Save | QDialogButtonBox.Cancel self.buttonBox = QDialogButtonBox(buttons, self) self.vbox.addWidget(self.buttonBox) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.resetButton = self.buttonBox.button(QDialogButtonBox.Reset) self.resetButton.clicked.connect(self.reset_to_stock) def eventFilter(self, object, event): if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return: self.toggle_selected_columns() return True if event.key() == Qt.Key_Delete: self.delete_selected() return True return False def update_output(self): self.presetLabel.setText("Preset: {}".format(self.preset_name)) self.setAsDefaultCheckbox.setChecked( CONFIG['default_header_preset'] == self.preset_name) self.columnList.clear() for column in self.columns: ColumnListItem(self.columnList, column) def accept(self): self.read_columns_from_list() self.header_changed.emit(self.preset_name, self.setAsDefaultCheckbox.isChecked(), self.columns) self.done(0) def reject(self): self.done(0) def reset_to_stock(self): self.columns = deepcopy(DEFAULT_COLUMNS) self.update_output() def read_columns_from_list(self): new_columns = [] for i in range(self.columnList.count()): item = self.columnList.item(i) new_columns.append(item.column) self.columns = new_columns def toggle_selected_columns(self): selected = self.columnList.selectedItems() for item in selected: value_now = item.data(Qt.CheckStateRole) item.setData(Qt.CheckStateRole, not value_now) self.columnList.reset( ) # @Improvement: is there a better way to update QListWidget? 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 load_preset(self, name): new_columns = CONFIG.load_header_preset(name) if not new_columns: return self.columns = new_columns self.preset_name = name self.update_output() def new_preset_dialog(self): d = QInputDialog(self) d.setLabelText('Enter the new name for the new preset:') d.setWindowTitle('Create new preset') d.textValueSelected.connect(self.create_new_preset) d.open() def create_new_preset(self, name): if name in CONFIG.get_header_presets(): show_warning_dialog( self, "Preset creation error", 'Preset named "{}" already exists.'.format(name)) return if len(name.strip()) == 0: show_warning_dialog( self, "Preset creation error", 'This preset name is not allowed.'.format(name)) return self.preset_name = name self.update_output() CONFIG.save_header_preset(name, self.columns) def delete_preset(self, name): CONFIG.delete_header_preset(name) if name == self.preset_name: self.columns = deepcopy(DEFAULT_COLUMNS) self.update_output() def create_new_column_dialog(self): d = CreateNewColumnDialog(self) d.add_new_column.connect(self.add_new_column) d.setWindowTitle('Create new column') d.open() def add_new_column(self, name, title): new_column = Column(name, title) # if the last column is message, insert this column before it (I think that makes sense?) if len(self.columns) == 0: self.columns.append(new_column) elif self.columns[-1].name in ('message', 'msg'): self.columns.insert(-1, new_column) else: self.columns.append(new_column) self.update_output() def delete_selected(self): selected = self.columnList.selectedItems() for item in selected: self.columnList.takeItem(self.columnList.row(item)) self.read_columns_from_list() self.update_output()
class PathManager(QDialog): redirect_stdio = Signal(bool) def __init__(self, parent=None, pathlist=None, ro_pathlist=None, not_active_pathlist=None, sync=True): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) assert isinstance(pathlist, list) self.pathlist = pathlist if not_active_pathlist is None: not_active_pathlist = [] self.not_active_pathlist = not_active_pathlist if ro_pathlist is None: ro_pathlist = [] self.ro_pathlist = ro_pathlist self.last_path = getcwd_or_home() self.setWindowTitle(_("PYTHONPATH manager")) self.setWindowIcon(ima.icon('pythonpath')) self.resize(500, 300) self.selection_widgets = [] layout = QVBoxLayout() self.setLayout(layout) top_layout = QHBoxLayout() layout.addLayout(top_layout) self.toolbar_widgets1 = self.setup_top_toolbar(top_layout) self.listwidget = QListWidget(self) self.listwidget.currentRowChanged.connect(self.refresh) self.listwidget.itemChanged.connect(self.update_not_active_pathlist) layout.addWidget(self.listwidget) bottom_layout = QHBoxLayout() layout.addLayout(bottom_layout) self.sync_button = None self.toolbar_widgets2 = self.setup_bottom_toolbar(bottom_layout, sync) # Buttons configuration bbox = QDialogButtonBox(QDialogButtonBox.Close) bbox.rejected.connect(self.reject) bottom_layout.addWidget(bbox) self.update_list() self.refresh() @property def active_pathlist(self): return [ path for path in self.pathlist if path not in self.not_active_pathlist ] def _add_widgets_to_layout(self, layout, widgets): layout.setAlignment(Qt.AlignLeft) for widget in widgets: layout.addWidget(widget) def setup_top_toolbar(self, layout): toolbar = [] movetop_button = create_toolbutton( self, text=_("Move to top"), icon=ima.icon('2uparrow'), triggered=lambda: self.move_to(absolute=0), text_beside_icon=True) toolbar.append(movetop_button) moveup_button = create_toolbutton( self, text=_("Move up"), icon=ima.icon('1uparrow'), triggered=lambda: self.move_to(relative=-1), text_beside_icon=True) toolbar.append(moveup_button) movedown_button = create_toolbutton( self, text=_("Move down"), icon=ima.icon('1downarrow'), triggered=lambda: self.move_to(relative=1), text_beside_icon=True) toolbar.append(movedown_button) movebottom_button = create_toolbutton( self, text=_("Move to bottom"), icon=ima.icon('2downarrow'), triggered=lambda: self.move_to(absolute=1), text_beside_icon=True) toolbar.append(movebottom_button) self.selection_widgets.extend(toolbar) self._add_widgets_to_layout(layout, toolbar) return toolbar def setup_bottom_toolbar(self, layout, sync=True): toolbar = [] add_button = create_toolbutton(self, text=_('Add path'), icon=ima.icon('edit_add'), triggered=self.add_path, text_beside_icon=True) toolbar.append(add_button) remove_button = create_toolbutton(self, text=_('Remove path'), icon=ima.icon('edit_remove'), triggered=self.remove_path, text_beside_icon=True) toolbar.append(remove_button) self.selection_widgets.append(remove_button) self._add_widgets_to_layout(layout, toolbar) layout.addStretch(1) if os.name == 'nt' and sync: self.sync_button = create_toolbutton( self, text=_("Synchronize..."), icon=ima.icon('fileimport'), triggered=self.synchronize, tip=_("Synchronize Spyder's path list with PYTHONPATH " "environment variable"), text_beside_icon=True) layout.addWidget(self.sync_button) return toolbar @Slot() def synchronize(self): """ Synchronize Spyder's path list with PYTHONPATH environment variable Only apply to: current user, on Windows platforms """ answer = QMessageBox.question( self, _("Synchronize"), _("This will synchronize Spyder's path list with " "<b>PYTHONPATH</b> environment variable for current user, " "allowing you to run your Python modules outside Spyder " "without having to configure sys.path. " "<br>Do you want to clear contents of PYTHONPATH before " "adding Spyder's path list?"), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if answer == QMessageBox.Cancel: return elif answer == QMessageBox.Yes: remove = True else: remove = False from spyder.utils.environ import (get_user_env, set_user_env, listdict2envdict) env = get_user_env() if remove: ppath = self.active_pathlist + self.ro_pathlist else: ppath = env.get('PYTHONPATH', []) if not isinstance(ppath, list): ppath = [ppath] ppath = [ path for path in ppath if path not in (self.active_pathlist + self.ro_pathlist) ] ppath.extend(self.active_pathlist + self.ro_pathlist) env['PYTHONPATH'] = ppath set_user_env(listdict2envdict(env), parent=self) def get_path_list(self): """Return path list (does not include the read-only path list)""" return self.pathlist def update_not_active_pathlist(self, item): path = item.text() if bool(item.checkState()) is True: self.remove_from_not_active_pathlist(path) else: self.add_to_not_active_pathlist(path) def add_to_not_active_pathlist(self, path): if path not in self.not_active_pathlist: self.not_active_pathlist.append(path) def remove_from_not_active_pathlist(self, path): if path in self.not_active_pathlist: self.not_active_pathlist.remove(path) def update_list(self): """Update path list""" self.listwidget.clear() for name in self.pathlist + self.ro_pathlist: item = QListWidgetItem(name) item.setIcon(ima.icon('DirClosedIcon')) if name in self.ro_pathlist: item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) elif name in self.not_active_pathlist: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Unchecked) else: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) self.listwidget.addItem(item) self.refresh() def refresh(self, row=None): """Refresh widget""" for widget in self.selection_widgets: widget.setEnabled(self.listwidget.currentItem() is not None) not_empty = self.listwidget.count() > 0 if self.sync_button is not None: self.sync_button.setEnabled(not_empty) def move_to(self, absolute=None, relative=None): index = self.listwidget.currentRow() if absolute is not None: if absolute: new_index = len(self.pathlist) - 1 else: new_index = 0 else: new_index = index + relative new_index = max(0, min(len(self.pathlist) - 1, new_index)) path = self.pathlist.pop(index) self.pathlist.insert(new_index, path) self.update_list() self.listwidget.setCurrentRow(new_index) @Slot() def remove_path(self): answer = QMessageBox.warning( self, _("Remove path"), _("Do you really want to remove selected path?"), QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.Yes: self.pathlist.pop(self.listwidget.currentRow()) self.remove_from_not_active_pathlist( self.listwidget.currentItem().text()) self.update_list() @Slot() def add_path(self): self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), self.last_path) self.redirect_stdio.emit(True) if directory: is_unicode = False if PY2: try: directory.decode('ascii') except UnicodeEncodeError: is_unicode = True if is_unicode: QMessageBox.warning( self, _("Add path"), _("You are using Python 2 and the path" " selected has Unicode characters. " "The new path will not be added."), QMessageBox.Ok) return directory = osp.abspath(directory) self.last_path = directory if directory in self.pathlist: item = self.listwidget.findItems(directory, Qt.MatchExactly)[0] item.setCheckState(Qt.Checked) answer = QMessageBox.question( self, _("Add path"), _("This directory is already included in Spyder " "path list.<br>Do you want to move it to the " "top of the list?"), QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.Yes: self.pathlist.remove(directory) else: return self.pathlist.insert(0, directory) self.update_list()
class MainWindow(QMainWindow): FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = 0, 1, 2 def __init__(self, label_file=None): super(MainWindow, self).__init__() self.showMaximized() self.setWindowTitle("VTCC.Labelling") self.file_dirs = [] self.file_id = -1 self.labels = [] with open(label_file, 'r') as f: lines = f.read().splitlines() for label in lines: self.labels.append(label) # RIGHT DOCK self.label_dock = QDockWidget("Label List", self) self.label_list_widget = QListWidget(self) self.load_labels(label_file) self.label_dock.setWidget(self.label_list_widget) self.object_dock = QDockWidget("Object List", self) self.object_list_widget = QListWidget(self) self.object_list_widget.currentRowChanged.connect(self.change_object) self.object_dock.setWidget(self.object_list_widget) self.file_dock = QDockWidget("File List", self) self.file_list_widget = QListWidget(self) self.file_list_widget.currentRowChanged.connect(self.change_file) self.file_dock.setWidget(self.file_list_widget) self.addDockWidget(Qt.RightDockWidgetArea, self.label_dock) self.addDockWidget(Qt.RightDockWidgetArea, self.object_dock) self.addDockWidget(Qt.RightDockWidgetArea, self.file_dock) # MAIN CANVAS self.canvas = Canvas(self) self.canvas_area = QScrollArea() self.canvas_area.setWidget(self.canvas) self.canvas_area.setWidgetResizable(True) self.scrollBars = { Qt.Vertical: self.canvas_area.verticalScrollBar(), Qt.Horizontal: self.canvas_area.horizontalScrollBar(), } self.setCentralWidget(self.canvas_area) # LEFT DOCK self.open_action = QAction(QIcon('icons/open.png'), 'Open File', self) self.open_action.triggered.connect(self.open_file) self.open_action.setShortcut(QKeySequence("Ctrl+O")) self.open_dir_action = QAction(QIcon('icons/open.png'), 'Open Dir', self) self.open_dir_action.triggered.connect(self.open_dir) self.next_img_action = QAction(QIcon('icons/next.png'), 'Next Image', self) self.next_img_action.triggered.connect(self.next_img) self.next_img_action.setShortcut(QKeySequence("Right")) self.prev_img_action = QAction(QIcon('icons/prev.png'), 'Prev Image', self) self.prev_img_action.triggered.connect(self.prev_img) self.prev_img_action.setShortcut(QKeySequence("Left")) self.zoom_in_action = QAction(QIcon('icons/zoom-in.png'), 'Zoom In', self) self.zoom_in_action.triggered.connect(self.zoom_in) self.zoom_out_action = QAction(QIcon('icons/zoom-out.png'), 'Zoom Out', self) self.zoom_out_action.triggered.connect(self.zoom_out) self.zoom_org_action = QAction(QIcon('icons/fit-window.png'), 'Fit Window', self) self.zoom_org_action.triggered.connect(self.zoom_org) self.rectangle_action = QAction(QIcon('icons/objects.png'), 'New Rectangle', self) self.rectangle_action.triggered.connect(self.new_rectangle) self.auto_polygon_action = QAction(QIcon('icons/objects.png'), 'New Auto-Polygon', self) self.auto_polygon_action.triggered.connect(self.new_auto_polygon) self.polygon_action = QAction(QIcon('icons/objects.png'), 'New Polygon', self) self.polygon_action.triggered.connect(self.new_polygon) self.next_obj_action = QAction(QIcon('icons/next.png'), 'Next Object', self) self.next_obj_action.triggered.connect(self.canvas.next_obj) self.next_obj_action.setShortcut(QKeySequence("Down")) self.prev_obj_action = QAction(QIcon('icons/prev.png'), 'Prev Object', self) self.prev_obj_action.triggered.connect(self.canvas.prev_obj) self.prev_obj_action.setShortcut(QKeySequence("Up")) self.del_obj_action = QAction(QIcon('icons/delete.png'), 'Delete Object', self) self.del_obj_action.triggered.connect(self.canvas.del_obj) self.del_obj_action.setShortcut(QKeySequence("Del")) self.toolbar = QToolBar(self) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.toolbar.addAction(self.open_action) self.toolbar.addAction(self.open_dir_action) self.toolbar.addAction(self.next_img_action) self.toolbar.addAction(self.prev_img_action) # self.toolbar.addAction(self.zoom_in_action) # self.toolbar.addAction(self.zoom_out_action) # self.toolbar.addAction(self.zoom_org_action) self.toolbar.addAction(self.rectangle_action) self.toolbar.addAction(self.auto_polygon_action) self.toolbar.addAction(self.polygon_action) self.toolbar.addAction(self.next_obj_action) self.toolbar.addAction(self.prev_obj_action) self.toolbar.addAction(self.del_obj_action) self.addToolBar(Qt.LeftToolBarArea, self.toolbar) self.scalers = { self.FIT_WINDOW: self.scaleFitWindow, self.FIT_WIDTH: self.scaleFitWidth, # Set to one to scale to 100% when loading files. self.MANUAL_ZOOM: lambda: 1, } def update_mode(self, mode_id): pass def change_object(self, row): if (row >= 0): self.canvas.cur_object = row self.canvas.repaint() def change_file(self, row): if (row >= 0): self.file_id = row self.canvas.load_file(self.file_dirs[self.file_id]) self.adjustScale(initial=True) def open_file(self): path = '.' if len(self.file_dirs) > 0: path = os.path.dirname(str(self.file_dirs[0])) formats = [ '*.{}'.format(fmt.data().decode()) for fmt in QImageReader.supportedImageFormats() ] filters = "Image files (%s)" % ' '.join(formats) file_dir = QFileDialog.getOpenFileName(self, \ "Choose Image file", path, filters)[0] self.file_dirs = [file_dir] self.import_files() def open_dir(self): targetDirPath = str( QFileDialog.getExistingDirectory( self, 'Open Directory', '.', QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)) self.file_dirs = [] for fmt in QImageReader.supportedImageFormats(): pattern = os.path.join(targetDirPath, "*." + fmt.data().decode()) self.file_dirs += glob.glob(pattern) self.import_files() def next_img(self): if (len(self.file_dirs) > 0): self.file_id = (self.file_id + 1) % len(self.file_dirs) self.canvas.load_file(self.file_dirs[self.file_id]) self.adjustScale(initial=True) self.file_list_widget.setCurrentRow(self.file_id) def prev_img(self): if (len(self.file_dirs) > 0): self.file_id = (self.file_id + len(self.file_dirs) - 1) % len( self.file_dirs) self.canvas.load_file(self.file_dirs[self.file_id]) self.adjustScale(initial=True) self.file_list_widget.setCurrentRow(self.file_id) def import_files(self): self.load_file_list() self.file_id = 0 self.canvas.load_file(self.file_dirs[0]) self.adjustScale(initial=True) self.file_list_widget.setCurrentRow(self.file_id) def load_labels(self, label_file): self.label_list_widget.clear() for label in self.labels: item = QListWidgetItem(label) self.label_list_widget.addItem(item) def load_object_list(self, objects): self.object_list_widget.clear() for obj in objects: item = QListWidgetItem(obj.label) self.object_list_widget.addItem(item) def load_file_list(self): self.file_list_widget.clear() for image_dir in self.file_dirs: if not QFile.exists(image_dir): continue item = QListWidgetItem(image_dir) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) label_dir = os.path.splitext(image_dir)[0] + '.json' if QFile.exists(label_dir): item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) self.file_list_widget.addItem(item) def scaleFitWindow(self): """Figure out the size of the pixmap to fit the main widget.""" e = 2.0 # So that no scrollbars are generated. w1 = self.centralWidget().width() - e h1 = self.centralWidget().height() - e a1 = w1 / h1 # Calculate a new scale value based on the pixmap's aspect ratio. w2 = self.canvas.pixmap.width() - 0.0 h2 = self.canvas.pixmap.height() - 0.0 a2 = w2 / h2 return w1 / w2 if a2 >= a1 else h1 / h2 def scaleFitWidth(self): # The epsilon does not seem to work too well here. w = self.centralWidget().width() - 2.0 return w / self.canvas.pixmap.width() def adjustScale(self, initial=False): value = self.scalers[self.FIT_WINDOW if initial else self.zoomMode]() self.canvas.rescale(value) def zoom_in(self): value = self.canvas.scale self.canvas.rescale(value * 1.1) def zoom_out(self): value = self.canvas.scale self.canvas.rescale(value * 0.9) def zoom_org(self): print(self.centralWidget().width(), self.centralWidget().height()) print(self.canvas.pixmap.width(), self.canvas.pixmap.height()) print(self.canvas.width(), self.canvas.height()) print(self.canvas_area.width(), self.canvas_area.height()) self.adjustScale(initial=True) def new_rectangle(self): self.canvas.points = [] self.canvas.mode = self.canvas.RECTANGLE def new_auto_polygon(self): self.canvas.points = [] self.canvas.mode = self.canvas.AUTO_POLYGON def new_polygon(self): self.canvas.points = [] self.canvas.mode = self.canvas.POLYGON
class FileAssociationsWidget(QWidget): """Widget to add applications association to file extensions.""" # This allows validating a single extension entry or a list of comma # separated values (eg `*.json` or `*.json,*.txt,MANIFEST.in`) _EXTENSIONS_LIST_REGEX = (r'(?:(?:\*{1,1}|\w+)\.\w+)' r'(?:,(?:\*{1,1}|\w+)\.\w+){0,20}') sig_data_changed = Signal(dict) def __init__(self, parent=None): """Widget to add applications association to file extensions.""" super(FileAssociationsWidget, self).__init__(parent=parent) # Variables self._data = {} self._dlg_applications = None self._dlg_input = None self._regex = re.compile(self._EXTENSIONS_LIST_REGEX) # Widgets self.label = QLabel( _('Here you can associate which external applications you want' 'to use to open specific file extensions <br> (e.g. .txt ' 'files with Notepad++ or .csv files with Excel).')) self.label_extensions = QLabel(_('File types:')) self.list_extensions = QListWidget() self.button_add = QPushButton(_('Add')) self.button_remove = QPushButton(_('Remove')) self.button_edit = QPushButton(_('Edit')) self.label_applications = QLabel(_('Associated applications:')) self.list_applications = QListWidget() self.button_add_application = QPushButton(_('Add')) self.button_remove_application = QPushButton(_('Remove')) self.button_default = QPushButton(_('Set default')) # Layout layout_extensions = QHBoxLayout() layout_extensions.addWidget(self.list_extensions, 4) layout_buttons_extensions = QVBoxLayout() layout_buttons_extensions.addWidget(self.button_add) layout_buttons_extensions.addWidget(self.button_remove) layout_buttons_extensions.addWidget(self.button_edit) layout_buttons_extensions.addStretch() layout_applications = QHBoxLayout() layout_applications.addWidget(self.list_applications, 4) layout_buttons_applications = QVBoxLayout() layout_buttons_applications.addWidget(self.button_add_application) layout_buttons_applications.addWidget(self.button_remove_application) layout_buttons_applications.addWidget(self.button_default) layout_buttons_applications.addStretch() layout_extensions.addLayout(layout_buttons_extensions, 2) layout_applications.addLayout(layout_buttons_applications, 2) layout = QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.label_extensions) layout.addLayout(layout_extensions) layout.addWidget(self.label_applications) layout.addLayout(layout_applications) self.setLayout(layout) # Signals self.button_add.clicked.connect(lambda: self.add_association()) self.button_remove.clicked.connect(self.remove_association) self.button_edit.clicked.connect(self.edit_association) self.button_add_application.clicked.connect(self.add_application) self.button_remove_application.clicked.connect(self.remove_application) self.button_default.clicked.connect(self.set_default_application) self.list_extensions.currentRowChanged.connect(self.update_extensions) self.list_extensions.itemDoubleClicked.connect(self.edit_association) self.list_applications.currentRowChanged.connect( self.update_applications) self._refresh() self._create_association_dialog() def _refresh(self): """Refresh the status of buttons on widget.""" self.setUpdatesEnabled(False) for widget in [ self.button_remove, self.button_add_application, self.button_edit, self.button_remove_application, self.button_default ]: widget.setDisabled(True) item = self.list_extensions.currentItem() if item: for widget in [ self.button_remove, self.button_add_application, self.button_remove_application, self.button_edit ]: widget.setDisabled(False) self.update_applications() self.setUpdatesEnabled(True) def _add_association(self, value): """Add association helper.""" # Check value is not pressent for row in range(self.list_extensions.count()): item = self.list_extensions.item(row) if item.text().strip() == value.strip(): break else: item = QListWidgetItem(value) self.list_extensions.addItem(item) self.list_extensions.setCurrentItem(item) self._refresh() def _add_application(self, app_name, fpath): """Add application helper.""" app_not_found_text = _(' (Application not found!)') for row in range(self.list_applications.count()): item = self.list_applications.item(row) # Ensure the actual name is checked without the `app not found` # additional text, in case app was not found item_text = item.text().replace(app_not_found_text, '').strip() if item and item_text == app_name: break else: icon = get_application_icon(fpath) if not (os.path.isfile(fpath) or os.path.isdir(fpath)): app_name += app_not_found_text item = QListWidgetItem(icon, app_name) self.list_applications.addItem(item) self.list_applications.setCurrentItem(item) if not (os.path.isfile(fpath) or os.path.isdir(fpath)): item.setToolTip(_('Application not found!')) def _update_extensions(self): """Update extensions list.""" self.list_extensions.clear() for extension, _ in sorted(self._data.items()): self._add_association(extension) # Select first item self.list_extensions.setCurrentRow(0) self.update_extensions() self.update_applications() def _create_association_dialog(self): """Create input extension dialog and save it to for reuse.""" self._dlg_input = InputTextDialog( self, title=_('File association'), label=(_('Enter new file extension. You can add several values ' 'separated by commas.<br>Examples include:') + '<ul><li><code>*.txt</code></li>' + '<li><code>*.json,*,csv</code></li>' + '<li><code>*.json,README.md</code></li></ul>'), ) self._dlg_input.set_regex_validation(self._EXTENSIONS_LIST_REGEX) def load_values(self, data=None): """ Load file associations data. Format {'*.ext': [['Application Name', '/path/to/app/executable']]} `/path/to/app/executable` is an executable app on mac and windows and a .desktop xdg file on linux. """ self._data = data self._update_extensions() def add_association(self, value=None): """Add extension file association.""" if value is None: text, ok_pressed = '', False self._dlg_input.set_text('') if self._dlg_input.exec_(): text = self._dlg_input.text() ok_pressed = True else: match = self._regex.match(value) text, ok_pressed = value, bool(match) if ok_pressed: if text not in self._data: self._data[text] = [] self._add_association(text) self.check_data_changed() def remove_association(self): """Remove extension file association.""" if self._data: if self.current_extension: self._data.pop(self.current_extension) self._update_extensions() self._refresh() self.check_data_changed() def edit_association(self): """Edit text of current selected association.""" old_text = self.current_extension self._dlg_input.set_text(old_text) if self._dlg_input.exec_(): new_text = self._dlg_input.text() if old_text != new_text: values = self._data.pop(self.current_extension) self._data[new_text] = values self._update_extensions() self._refresh() for row in range(self.list_extensions.count()): item = self.list_extensions.item(row) if item.text() == new_text: self.list_extensions.setCurrentItem(item) break self.check_data_changed() def add_application(self): """Remove application to selected extension.""" if self.current_extension: if self._dlg_applications is None: self._dlg_applications = ApplicationsDialog(self) self._dlg_applications.set_extension(self.current_extension) if self._dlg_applications.exec_(): app_name = self._dlg_applications.application_name fpath = self._dlg_applications.application_path self._data[self.current_extension].append((app_name, fpath)) self._add_application(app_name, fpath) self.check_data_changed() def remove_application(self): """Remove application from selected extension.""" current_row = self.list_applications.currentRow() values = self._data.get(self.current_extension) if values and current_row != -1: values.pop(current_row) self.update_extensions() self.update_applications() self.check_data_changed() def set_default_application(self): """ Set the selected item on the application list as default application. """ current_row = self.list_applications.currentRow() if current_row != -1: values = self._data[self.current_extension] value = values.pop(current_row) values.insert(0, value) self._data[self.current_extension] = values self.update_extensions() self.check_data_changed() def update_extensions(self, row=None): """Update extensiosn list after additions or deletions.""" self.list_applications.clear() for extension, values in self._data.items(): if extension.strip() == self.current_extension: for (app_name, fpath) in values: self._add_application(app_name, fpath) break self.list_applications.setCurrentRow(0) self._refresh() def update_applications(self, row=None): """Update application list after additions or deletions.""" current_row = self.list_applications.currentRow() self.button_default.setEnabled(current_row != 0) def check_data_changed(self): """Check if data has changed and emit signal as needed.""" self.sig_data_changed.emit(self._data) @property def current_extension(self): """Return the current selected extension text.""" item = self.list_extensions.currentItem() if item: return item.text() @property def data(self): """Return the current file associations data.""" return self._data.copy()
class PreferencesDialog(QDialog): """Preferences Dialog for Napari user settings.""" valueChanged = Signal() ui_schema = { "call_order": { "ui:widget": "plugins" }, "highlight_thickness": { "ui:widget": "highlight" }, } resized = Signal(QSize) closed = Signal() def __init__(self, parent=None): super().__init__(parent) self._list = QListWidget(self) self._stack = QStackedWidget(self) self._list.setObjectName("Preferences") # Set up buttons self._button_cancel = QPushButton(trans._("Cancel")) self._button_ok = QPushButton(trans._("OK")) self._default_restore = QPushButton(trans._("Restore defaults")) # Setup self.setWindowTitle(trans._("Preferences")) # Layout left_layout = QVBoxLayout() left_layout.addWidget(self._list) left_layout.addStretch() left_layout.addWidget(self._default_restore) left_layout.addWidget(self._button_cancel) left_layout.addWidget(self._button_ok) main_layout = QHBoxLayout() main_layout.addLayout(left_layout, 1) main_layout.addWidget(self._stack, 3) self.setLayout(main_layout) # Signals self._list.currentRowChanged.connect( lambda index: self._stack.setCurrentIndex(index)) self._button_cancel.clicked.connect(self.on_click_cancel) self._button_ok.clicked.connect(self.on_click_ok) self._default_restore.clicked.connect(self.restore_defaults) # Make widget self.make_dialog() self._list.setCurrentRow(0) def _restart_dialog(self, event=None, extra_str=""): """Displays the dialog informing user a restart is required. Paramters --------- event : Event extra_str : str Extra information to add to the message about needing a restart. """ text_str = trans._( "napari requires a restart for image rendering changes to apply.") widget = ResetNapariInfoDialog( parent=self, text=text_str, ) widget.exec_() def closeEvent(self, event): """Override to emit signal.""" self.closed.emit() super().closeEvent(event) def reject(self): """Override to handle Escape.""" super().reject() self.close() def resizeEvent(self, event): """Override to emit signal.""" self.resized.emit(event.size()) super().resizeEvent(event) def make_dialog(self): """Removes settings not to be exposed to user and creates dialog pages.""" settings = get_settings() # Because there are multiple pages, need to keep a dictionary of values dicts. # One set of keywords are for each page, then in each entry for a page, there are dicts # of setting and its value. self._values_orig_dict = {} self._values_dict = {} self._setting_changed_dict = {} for page, setting in settings.schemas().items(): schema, values, properties = self.get_page_dict(setting) self._setting_changed_dict[page] = {} self._values_orig_dict[page] = values self._values_dict[page] = values # Only add pages if there are any properties to add. if properties: self.add_page(schema, values) def get_page_dict(self, setting): """Provides the schema, set of values for each setting, and the properties for each setting. Parameters ---------- setting : dict Dictionary of settings for a page within the settings manager. Returns ------- schema : dict Json schema of the setting page. values : dict Dictionary of values currently set for each parameter in the settings. properties : dict Dictionary of properties within the json schema. """ schema = json.loads(setting['json_schema']) # Resolve allOf references definitions = schema.get("definitions", {}) if definitions: for key, data in schema["properties"].items(): if "allOf" in data: allof = data["allOf"] allof = [d["$ref"].rsplit("/")[-1] for d in allof] for definition in allof: local_def = definitions[definition] schema["properties"][key]["enum"] = local_def["enum"] schema["properties"][key]["type"] = "string" # Need to remove certain properties that will not be displayed on the GUI properties = schema.pop('properties') model = setting['model'] values = model.dict() napari_config = getattr(model, "NapariConfig", None) if napari_config is not None: for val in napari_config.preferences_exclude: properties.pop(val) values.pop(val) schema['properties'] = properties return schema, values, properties def restore_defaults(self): """Launches dialog to confirm restore settings choice.""" self._reset_dialog = ConfirmDialog( parent=self, text=trans._("Are you sure you want to restore default settings?"), ) self._reset_dialog.valueChanged.connect(self._reset_widgets) self._reset_dialog.exec_() def _reset_widgets(self): """Deletes the widgets and rebuilds with defaults.""" self.close() self.valueChanged.emit() self._list.clear() for n in range(self._stack.count()): widget = self._stack.removeWidget(self._stack.currentWidget()) del widget self.make_dialog() self._list.setCurrentRow(0) self.show() def on_click_ok(self): """Keeps the selected preferences saved to settings.""" self.close() def on_click_cancel(self): """Restores the settings in place when dialog was launched.""" # Need to check differences for each page. settings = get_settings() for n in range(self._stack.count()): # Must set the current row so that the proper list is updated # in check differences. self._list.setCurrentRow(n) page = self._list.currentItem().text().split(" ")[0].lower() # get new values for settings. If they were changed from values at beginning # of preference dialog session, change them back. # Using the settings value seems to be the best way to get the checkboxes right # on the plugin call order widget. setting = settings.schemas()[page] schema, new_values, properties = self.get_page_dict(setting) self.check_differences(self._values_orig_dict[page], new_values) self._list.setCurrentRow(0) self.close() def add_page(self, schema, values): """Creates a new page for each section in dialog. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ widget = self.build_page_dialog(schema, values) self._list.addItem(schema["title"]) self._stack.addWidget(widget) def build_page_dialog(self, schema, values): """Builds the preferences widget using the json schema builder. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ settings = get_settings() builder = WidgetBuilder() form = builder.create_form(schema, self.ui_schema) # Disable widgets that loaded settings from environment variables section = schema["section"] form_layout = form.widget.layout() for row in range(form.widget.layout().rowCount()): widget = form_layout.itemAt(row, form_layout.FieldRole).widget() name = widget._name disable = bool( settings._env_settings.get(section, {}).get(name, None)) widget.setDisabled(disable) try: widget.opacity.setOpacity(0.3 if disable else 1) except AttributeError: # some widgets may not have opacity (such as the QtPluginSorter) pass # set state values for widget form.widget.state = values if section == 'experimental': # need to disable async if octree is enabled. if values['octree'] is True: form = self._disable_async(form, values) form.widget.on_changed.connect(lambda d: self.check_differences( d, self._values_dict[schema["title"].lower()], )) return form def _disable_async(self, form, values, disable=True, state=True): """Disable async if octree is True.""" settings = get_settings() # need to make sure that if async_ is an environment setting, that we don't # enable it here. if (settings._env_settings['experimental'].get('async_', None) is not None): disable = True idx = list(values.keys()).index('async_') form_layout = form.widget.layout() widget = form_layout.itemAt(idx, form_layout.FieldRole).widget() widget.opacity.setOpacity(0.3 if disable else 1) widget.setDisabled(disable) return form def _values_changed(self, page, new_dict, old_dict): """Loops through each setting in a page to determine if it changed. Parameters ---------- new_dict : dict Dict that has the most recent changes by user. Each key is a setting value and each item is the value. old_dict : dict Dict wtih values set at the begining of preferences dialog session. """ for setting_name, value in new_dict.items(): if value != old_dict[setting_name]: self._setting_changed_dict[page][setting_name] = value elif (value == old_dict[setting_name] and setting_name in self._setting_changed_dict[page]): self._setting_changed_dict[page].pop(setting_name) def set_current_index(self, index: int): """ Set the current page on the preferences by index. Parameters ---------- index : int Index of page to set as current one. """ self._list.setCurrentRow(index) def check_differences(self, new_dict, old_dict): """Changes settings in settings manager with changes from dialog. Parameters ---------- new_dict : dict Dict that has the most recent changes by user. Each key is a setting parameter and each item is the value. old_dict : dict Dict wtih values set at the beginning of the preferences dialog session. """ settings = get_settings() page = self._list.currentItem().text().split(" ")[0].lower() self._values_changed(page, new_dict, old_dict) different_values = self._setting_changed_dict[page] if len(different_values) > 0: # change the values in settings for setting_name, value in different_values.items(): try: setattr(settings._settings[page], setting_name, value) self._values_dict[page] = new_dict if page == 'experimental': if setting_name == 'octree': # disable/enable async checkbox widget = self._stack.currentWidget() cstate = True if value is True else False self._disable_async(widget, new_dict, disable=cstate) # need to inform user that napari restart needed. self._restart_dialog() elif setting_name == 'async_': # need to inform user that napari restart needed. self._restart_dialog() except: # noqa: E722 continue
class MeasurementSettings(QWidget): """ :type settings: Settings """ def __init__(self, settings: PartSettings, parent=None): super().__init__(parent) self.chosen_element: Optional[MeasurementListWidgetItem] = None self.chosen_element_area: Optional[Tuple[AreaType, float]] = None self.settings = settings self.profile_list = QListWidget(self) self.profile_description = QTextEdit(self) self.profile_description.setReadOnly(True) self.profile_options = QListWidget() self.profile_options_chosen = QListWidget() self.measurement_area_choose = QEnumComboBox(enum_class=AreaType) self.per_component = QEnumComboBox(enum_class=PerComponent) self.power_num = QDoubleSpinBox() self.power_num.setDecimals(3) self.power_num.setRange(-100, 100) self.power_num.setValue(1) self.choose_butt = QPushButton("→", self) self.discard_butt = QPushButton("←", self) self.proportion_butt = QPushButton("Ratio", self) self.proportion_butt.setToolTip("Create proportion from two parameter") self.move_up = QPushButton("↑", self) self.move_down = QPushButton("↓", self) self.remove_button = QPushButton("Remove") self.save_butt = QPushButton("Save") self.save_butt.setToolTip( "Set name for set and choose at least one parameter") self.save_butt_with_name = QPushButton( "Save with custom parameters designation") self.save_butt_with_name.setToolTip( "Set name for set and choose at least one parameter") self.reset_butt = QPushButton("Clear") self.soft_reset_butt = QPushButton("Remove user parameters") self.profile_name = QLineEdit(self) self.delete_profile_butt = QPushButton("Delete ") self.export_profiles_butt = QPushButton("Export") self.import_profiles_butt = QPushButton("Import") self.edit_profile_butt = QPushButton("Edit") self.choose_butt.setDisabled(True) self.choose_butt.clicked.connect(self.choose_option) self.discard_butt.setDisabled(True) self.discard_butt.clicked.connect(self.discard_option) self.proportion_butt.setDisabled(True) self.proportion_butt.clicked.connect(self.proportion_action) self.save_butt.setDisabled(True) self.save_butt.clicked.connect(self.save_action) self.save_butt_with_name.setDisabled(True) self.save_butt_with_name.clicked.connect(self.named_save_action) self.profile_name.textChanged.connect(self.name_changed) self.move_down.setDisabled(True) self.move_down.clicked.connect(self.move_down_fun) self.move_up.setDisabled(True) self.move_up.clicked.connect(self.move_up_fun) self.remove_button.setDisabled(True) self.remove_button.clicked.connect(self.remove_element) self.reset_butt.clicked.connect(self.reset_action) self.soft_reset_butt.clicked.connect(self.soft_reset) self.delete_profile_butt.setDisabled(True) self.delete_profile_butt.clicked.connect(self.delete_profile) self.export_profiles_butt.clicked.connect( self.export_measurement_profiles) self.import_profiles_butt.clicked.connect( self.import_measurement_profiles) self.edit_profile_butt.clicked.connect(self.edit_profile) self.profile_list.itemSelectionChanged.connect(self.profile_chosen) self.profile_options.itemSelectionChanged.connect( self.create_selection_changed) self.profile_options_chosen.itemSelectionChanged.connect( self.create_selection_chosen_changed) self.settings.measurement_profiles_changed.connect( self._refresh_profiles) layout = QVBoxLayout() layout.addWidget(QLabel("Measurement set:")) profile_layout = QHBoxLayout() profile_layout.addWidget(self.profile_list) profile_layout.addWidget(self.profile_description) profile_buttons_layout = QHBoxLayout() profile_buttons_layout.addWidget(self.delete_profile_butt) profile_buttons_layout.addWidget(self.export_profiles_butt) profile_buttons_layout.addWidget(self.import_profiles_butt) profile_buttons_layout.addWidget(self.edit_profile_butt) profile_buttons_layout.addStretch() layout.addLayout(profile_layout) layout.addLayout(profile_buttons_layout) heading_layout = QHBoxLayout() # heading_layout.addWidget(QLabel("Create profile"), 1) heading_layout.addWidget(h_line(), 6) layout.addLayout(heading_layout) name_layout = QHBoxLayout() name_layout.addWidget(QLabel("Set name:")) name_layout.addWidget(self.profile_name) name_layout.addStretch() name_layout.addWidget(QLabel("Per component:")) name_layout.addWidget(self.per_component) name_layout.addWidget(QLabel("Area:")) name_layout.addWidget(self.measurement_area_choose) name_layout.addWidget(QLabel("to power:")) name_layout.addWidget(self.power_num) layout.addLayout(name_layout) create_layout = QHBoxLayout() create_layout.addWidget(self.profile_options) butt_op_layout = QVBoxLayout() butt_op_layout.addStretch() butt_op_layout.addWidget(self.choose_butt) butt_op_layout.addWidget(self.discard_butt) butt_op_layout.addWidget(self.proportion_butt) butt_op_layout.addWidget(self.reset_butt) butt_op_layout.addStretch() create_layout.addLayout(butt_op_layout) create_layout.addWidget(self.profile_options_chosen) butt_move_layout = QVBoxLayout() butt_move_layout.addStretch() butt_move_layout.addWidget(self.move_up) butt_move_layout.addWidget(self.move_down) butt_move_layout.addWidget(self.remove_button) butt_move_layout.addStretch() create_layout.addLayout(butt_move_layout) layout.addLayout(create_layout) save_butt_layout = QHBoxLayout() save_butt_layout.addWidget(self.soft_reset_butt) save_butt_layout.addStretch() save_butt_layout.addWidget(self.save_butt) save_butt_layout.addWidget(self.save_butt_with_name) layout.addLayout(save_butt_layout) self.setLayout(layout) for profile in MEASUREMENT_DICT.values(): help_text = profile.get_description() lw = MeasurementListWidgetItem(profile.get_starting_leaf()) lw.setToolTip(help_text) self.profile_options.addItem(lw) self._refresh_profiles() def _refresh_profiles(self): item = self.profile_list.currentItem() items = list(self.settings.measurement_profiles.keys()) try: index = items.index(item.text()) except (ValueError, AttributeError): index = -1 self.profile_list.clear() self.profile_list.addItems(items) self.profile_list.setCurrentRow(index) if self.profile_list.count() == 0: self.export_profiles_butt.setDisabled(True) else: self.export_profiles_butt.setEnabled(True) def remove_element(self): elem = self.profile_options_chosen.currentItem() if elem is None: return index = self.profile_options_chosen.currentRow() self.profile_options_chosen.takeItem(index) if self.profile_options_chosen.count() == 0: self.move_down.setDisabled(True) self.move_up.setDisabled(True) self.remove_button.setDisabled(True) self.discard_butt.setDisabled(True) self.save_butt.setDisabled(True) self.save_butt_with_name.setDisabled(True) def delete_profile(self): item = self.profile_list.currentItem() del self.settings.measurement_profiles[str(item.text())] def profile_chosen(self): self.delete_profile_butt.setEnabled(True) if self.profile_list.count() == 0: self.profile_description.setText("") return item = self.profile_list.currentItem() if item is None: self.profile_description.setText("") return profile = self.settings.measurement_profiles[item.text()] self.profile_description.setText(str(profile)) def create_selection_changed(self): self.choose_butt.setEnabled(True) self.proportion_butt.setEnabled(True) def proportion_action(self): # TODO use get_parameters if self.chosen_element is None: item = self.profile_options.currentItem() self.chosen_element_area = self.get_parameters( deepcopy(item.stat), self.measurement_area_choose.currentEnum(), self.per_component.currentEnum(), self.power_num.value(), ) if self.chosen_element_area is None: return self.chosen_element = item item.setIcon(QIcon(os.path.join(icons_dir, "task-accepted.png"))) elif (self.profile_options.currentItem() == self.chosen_element and self.measurement_area_choose.currentEnum() == self.chosen_element_area.area and self.per_component.currentEnum() == self.chosen_element_area.per_component): self.chosen_element.setIcon(QIcon()) self.chosen_element = None else: item: MeasurementListWidgetItem = self.profile_options.currentItem( ) leaf = self.get_parameters( deepcopy(item.stat), self.measurement_area_choose.currentEnum(), self.per_component.currentEnum(), self.power_num.value(), ) if leaf is None: return lw = MeasurementListWidgetItem( Node(op="/", left=self.chosen_element_area, right=leaf)) lw.setToolTip("User defined") self._add_option(lw) self.chosen_element.setIcon(QIcon()) self.chosen_element = None self.chosen_element_area = None def _add_option(self, item: MeasurementListWidgetItem): for i in range(self.profile_options_chosen.count()): if item.text() == self.profile_options_chosen.item(i).text(): return self.profile_options_chosen.addItem(item) if self.good_name(): self.save_butt.setEnabled(True) self.save_butt_with_name.setEnabled(True) if self.profile_options.count() == 0: self.choose_butt.setDisabled(True) def create_selection_chosen_changed(self): # print(self.profile_options_chosen.count()) self.remove_button.setEnabled(True) if self.profile_options_chosen.count() == 0: self.move_down.setDisabled(True) self.move_up.setDisabled(True) self.remove_button.setDisabled(True) return self.discard_butt.setEnabled(True) if self.profile_options_chosen.currentRow() != 0: self.move_up.setEnabled(True) else: self.move_up.setDisabled(True) if self.profile_options_chosen.currentRow( ) != self.profile_options_chosen.count() - 1: self.move_down.setEnabled(True) else: self.move_down.setDisabled(True) def good_name(self): return str(self.profile_name.text()).strip() != "" def move_down_fun(self): row = self.profile_options_chosen.currentRow() item = self.profile_options_chosen.takeItem(row) self.profile_options_chosen.insertItem(row + 1, item) self.profile_options_chosen.setCurrentRow(row + 1) self.create_selection_chosen_changed() def move_up_fun(self): row = self.profile_options_chosen.currentRow() item = self.profile_options_chosen.takeItem(row) self.profile_options_chosen.insertItem(row - 1, item) self.profile_options_chosen.setCurrentRow(row - 1) self.create_selection_chosen_changed() def name_changed(self): if self.good_name() and self.profile_options_chosen.count() > 0: self.save_butt.setEnabled(True) self.save_butt_with_name.setEnabled(True) else: self.save_butt.setDisabled(True) self.save_butt_with_name.setDisabled(True) def form_dialog(self, arguments): return FormDialog(arguments, settings=self.settings, parent=self) def get_parameters(self, node: Union[Node, Leaf], area: AreaType, component: PerComponent, power: float): if isinstance(node, Node): return node node = node.replace_(power=power) if node.area is None: node = node.replace_(area=area) if node.per_component is None: node = node.replace_(per_component=component) with suppress(KeyError): arguments = MEASUREMENT_DICT[str(node.name)].get_fields() if len(arguments) > 0 and len(node.dict) == 0: dial = self.form_dialog(arguments) if dial.exec_(): node = node._replace(dict=dial.get_values()) else: return return node def choose_option(self): selected_item = self.profile_options.currentItem() # selected_row = self.profile_options.currentRow() if not isinstance(selected_item, MeasurementListWidgetItem): raise ValueError( f"Current item (type: {type(selected_item)} is not instance of MeasurementListWidgetItem" ) node = deepcopy(selected_item.stat) # noinspection PyTypeChecker node = self.get_parameters(node, self.measurement_area_choose.currentEnum(), self.per_component.currentEnum(), self.power_num.value()) if node is None: return lw = MeasurementListWidgetItem(node) lw.setToolTip(selected_item.toolTip()) self._add_option(lw) def discard_option(self): selected_item: MeasurementListWidgetItem = self.profile_options_chosen.currentItem( ) # selected_row = self.profile_options_chosen.currentRow() lw = MeasurementListWidgetItem(deepcopy(selected_item.stat)) lw.setToolTip(selected_item.toolTip()) self.create_selection_chosen_changed() for i in range(self.profile_options.count()): if lw.text() == self.profile_options.item(i).text(): return self.profile_options.addItem(lw) def edit_profile(self): item = self.profile_list.currentItem() if item is None: return profile = self.settings.measurement_profiles[str(item.text())] self.profile_options_chosen.clear() self.profile_name.setText(item.text()) for ch in profile.chosen_fields: self.profile_options_chosen.addItem( MeasurementListWidgetItem(ch.calculation_tree)) # self.gauss_img.setChecked(profile.use_gauss_image) self.save_butt.setEnabled(True) self.save_butt_with_name.setEnabled(True) def save_action(self): if self.profile_name.text() in self.settings.measurement_profiles: ret = QMessageBox.warning( self, "Profile exist", "Profile exist\nWould you like to overwrite it?", QMessageBox.No | QMessageBox.Yes, ) if ret == QMessageBox.No: return selected_values = [] for i in range(self.profile_options_chosen.count()): element: MeasurementListWidgetItem = self.profile_options_chosen.item( i) selected_values.append( MeasurementEntry(element.text(), element.stat)) stat_prof = MeasurementProfile(self.profile_name.text(), selected_values) self.settings.measurement_profiles[stat_prof.name] = stat_prof self.settings.dump() self.export_profiles_butt.setEnabled(True) def named_save_action(self): if self.profile_name.text() in self.settings.measurement_profiles: ret = QMessageBox.warning( self, "Profile exist", "Profile exist\nWould you like to overwrite it?", QMessageBox.No | QMessageBox.Yes, ) if ret == QMessageBox.No: return selected_values = [] for i in range(self.profile_options_chosen.count()): txt = str(self.profile_options_chosen.item(i).text()) selected_values.append((txt, str, txt)) val_dialog = MultipleInput("Set fields name", list(selected_values), parent=self) if val_dialog.exec_(): selected_values = [] for i in range(self.profile_options_chosen.count()): element: MeasurementListWidgetItem = self.profile_options_chosen.item( i) selected_values.append( MeasurementEntry(val_dialog.result[element.text()], element.stat)) stat_prof = MeasurementProfile(self.profile_name.text(), selected_values) self.settings.measurement_profiles[stat_prof.name] = stat_prof self.export_profiles_butt.setEnabled(True) def reset_action(self): self.profile_options.clear() self.profile_options_chosen.clear() self.profile_name.setText("") self.save_butt.setDisabled(True) self.save_butt_with_name.setDisabled(True) self.move_down.setDisabled(True) self.move_up.setDisabled(True) self.proportion_butt.setDisabled(True) self.choose_butt.setDisabled(True) self.discard_butt.setDisabled(True) for profile in MEASUREMENT_DICT.values(): help_text = profile.get_description() lw = MeasurementListWidgetItem(profile.get_starting_leaf()) lw.setToolTip(help_text) self.profile_options.addItem(lw) def soft_reset(self): # TODO rim should not be removed shift = 0 for i in range(self.profile_options.count()): item = self.profile_options.item(i - shift) if str(item.text()) not in MEASUREMENT_DICT: self.profile_options.takeItem(i - shift) if item == self.chosen_element: self.chosen_element = None del item shift += 1 self.create_selection_changed() def export_measurement_profiles(self): exp = ExportDialog(self.settings.measurement_profiles, StringViewer, parent=self) if not exp.exec_(): return dial = PSaveDialog( "Measurement profile (*.json)", settings=self.settings, path="io.export_directory", caption="Export settings profiles", ) dial.selectFile("measurements_profile.json") if dial.exec_(): file_path = str(dial.selectedFiles()[0]) data = { x: self.settings.measurement_profiles[x] for x in exp.get_export_list() } with open(file_path, "w", encoding="utf-8") as ff: json.dump(data, ff, cls=self.settings.json_encoder_class, indent=2) def import_measurement_profiles(self): dial = PLoadDialog( "Measurement profile (*.json)", settings=self.settings, path="io.export_directory", caption="Import settings profiles", parent=self, ) if dial.exec_(): file_path = str(dial.selectedFiles()[0]) stat, err = self.settings.load_part(file_path) if err: QMessageBox.warning( self, "Import error", "error during importing, part of data were filtered.") measurement_dict = self.settings.measurement_profiles imp = ImportDialog(stat, measurement_dict, StringViewer) if not imp.exec_(): return for original_name, final_name in imp.get_import_list(): measurement_dict[final_name] = stat[original_name] self.settings.dump()
class GroupsModify(PyDialog): """ +--------------------------+ | Groups : Modify | +--------------------------+ | | | Name xxx Default | | Coords xxx Default | | Elements xxx Default | | Color xxx Default | | Add xxx Add | | Remove xxx Remove | | | | Set OK Cancel | +--------------------------+ """ def __init__(self, data, win_parent=None, group_active='main'): PyDialog.__init__(self, data, win_parent) self.set_font_size(data['font_size']) self._updated_groups = False #self.out_data = data #print(data) keys = [] self.keys = [ group.name for key, group in sorted(iteritems(data)) if isinstance(key, int) ] self.active_key = self.keys.index(group_active) group_obj = data[self.active_key] name = group_obj.name self.imain = 0 self.nrows = len(self.keys) self._default_name = group_obj.name self._default_elements = group_obj.element_str self.elements_pound = group_obj.elements_pound self.table = QListWidget(parent=None) self.table.clear() self.table.addItems(self.keys) self.setWindowTitle('Groups: Modify') self.create_widgets() self.create_layout() self.set_connections() self.on_set_as_main() def create_widgets(self): """creates the menu objects""" # Name self.name = QLabel("Name:") self.name_set = QPushButton("Set") self.name_edit = QLineEdit(str(self._default_name).strip()) self.name_button = QPushButton("Default") # elements self.elements = QLabel("Element IDs:") self.elements_edit = QLineEdit(str(self._default_elements).strip()) self.elements_button = QPushButton("Default") # add self.add = QLabel("Add Elements:") self.add_edit = QElementEdit(self, str('')) self.add_button = QPushButton("Add") # remove self.remove = QLabel("Remove Elements:") self.remove_edit = QElementEdit(self, str('')) self.remove_button = QPushButton("Remove") # applies a unique implicitly self.eids = parse_patran_syntax(str(self._default_elements), pound=self.elements_pound) # closing #self.apply_button = QPushButton("Apply") self.ok_button = QPushButton("Close") #self.cancel_button = QPushButton("Cancel") self.set_as_main_button = QPushButton("Set As Main") self.create_group_button = QPushButton('Create New Group') self.delete_group_button = QPushButton('Delete Group') self.name.setEnabled(False) self.name_set.setEnabled(False) self.name_edit.setEnabled(False) self.name_button.setEnabled(False) self.elements.setEnabled(False) self.elements_button.setEnabled(False) self.elements_edit.setEnabled(False) self.add.setEnabled(False) self.add_button.setEnabled(False) self.add_edit.setEnabled(False) self.remove.setEnabled(False) self.remove_button.setEnabled(False) self.remove_edit.setEnabled(False) self.delete_group_button.setEnabled(False) #self.apply_button.setEnabled(False) #self.ok_button.setEnabled(False) def create_layout(self): """displays the menu objects""" grid = QGridLayout() grid.addWidget(self.name, 0, 0) grid.addWidget(self.name_edit, 0, 1) grid.addWidget(self.name_set, 0, 2) grid.addWidget(self.name_button, 0, 3) grid.addWidget(self.elements, 2, 0) grid.addWidget(self.elements_edit, 2, 1) grid.addWidget(self.elements_button, 2, 2) grid.addWidget(self.add, 4, 0) grid.addWidget(self.add_edit, 4, 1) grid.addWidget(self.add_button, 4, 2) grid.addWidget(self.remove, 5, 0) grid.addWidget(self.remove_edit, 5, 1) grid.addWidget(self.remove_button, 5, 2) ok_cancel_box = QHBoxLayout() #ok_cancel_box.addWidget(self.apply_button) ok_cancel_box.addWidget(self.ok_button) #ok_cancel_box.addWidget(self.cancel_button) main_create_delete = QHBoxLayout() main_create_delete.addWidget(self.set_as_main_button) main_create_delete.addWidget(self.create_group_button) main_create_delete.addWidget(self.delete_group_button) vbox = QVBoxLayout() vbox.addWidget(self.table) vbox.addLayout(grid) vbox.addLayout(main_create_delete) vbox.addStretch() vbox.addLayout(ok_cancel_box) self.setLayout(vbox) def on_set_name(self): name = str(self.name_edit.text()).strip() if name not in self.keys: self.name_edit.setStyleSheet("QLineEdit{background: white;}") group = self.out_data[self.active_key] group.name = name self.keys[self.active_key] = name self.recreate_table() elif name != self.keys[self.active_key]: self.name_edit.setStyleSheet("QLineEdit{background: red;}") elif name == self.keys[self.active_key]: self.name_edit.setStyleSheet("QLineEdit{background: white;}") def set_connections(self): self.name_set.clicked.connect(self.on_set_name) self.name_button.clicked.connect(self.on_default_name) self.elements_button.clicked.connect(self.on_default_elements) self.add_button.clicked.connect(self.on_add) self.remove_button.clicked.connect(self.on_remove) self.table.itemClicked.connect(self.on_update_active_key) self.ok_button.clicked.connect(self.on_ok) self.set_as_main_button.clicked.connect(self.on_set_as_main) self.create_group_button.clicked.connect(self.on_create_group) self.delete_group_button.clicked.connect(self.on_delete_group) def on_create_group(self): irow = self.nrows new_key = 'Group %s' % irow while new_key in self.keys: irow += 1 new_key = 'Group %s' % irow irow = self.nrows self.keys.append(new_key) group = Group(new_key, element_str='', elements_pound=self.elements_pound, editable=True) self.out_data[irow] = group self.table.reset() self.table.addItems(self.keys) self.nrows += 1 #---------------------------------- # update internal parameters #self.out_data = items if self.imain > self.active_key: self.imain += 1 #make the new group the default self.active_key = self.nrows - 1 self.keys = [ group.name for key, group in sorted(iteritems(self.out_data)) if isinstance(key, int) ] self.recreate_table() def recreate_table(self): # update gui self.table.clear() self.table.addItems(self.keys) item = self.table.item(self.imain) bold = QtGui.QFont() bold.setBold(True) bold.setItalic(True) item.setFont(bold) self.table.update() # update key name = self.keys[self.active_key] self._update_active_key_by_name(name) def on_delete_group(self): if self.active_key == 0: return #self.deleted_groups.add(self.imain) items = {} j = 0 for i, key in sorted(iteritems(self.out_data)): if isinstance(i, int): continue if i != self.active_key: items[j] = key j += 1 # update internal parameters self.out_data = items if self.imain >= self.active_key: self.imain = max(0, self.imain - 1) self.active_key = max(0, self.active_key - 1) self.nrows -= 1 self.keys = [group.name for key, group in sorted(iteritems(items))] self.recreate_table() # update key name = self.keys[self.active_key] self._update_active_key_by_name(name) def on_set_as_main(self): bold = QtGui.QFont() bold.setBold(True) bold.setItalic(True) normal = QtGui.QFont() normal.setBold(False) normal.setItalic(False) obj = self.table.item(self.imain) obj.setFont(normal) self.imain = self.active_key obj = self.table.item(self.imain) obj.setFont(bold) group = self.out_data[self.imain] self._default_elements = group.element_str self._default_name = group.name self.on_update_main() def on_update_main(self): """adds/removes the elements to the main actor when add/remove is pressed""" group = self.out_data[self.imain] if self._default_name == group.name and self.win_parent is not None: # we're not testing the menu self.win_parent.post_group(group) def closeEvent(self, event): self.out_data['close'] = True event.accept() def on_add(self): eids, is_valid = self.check_patran_syntax(self.add_edit, pound=self.elements_pound) #adict, is_valid = self.check_patran_syntax_dict(self.add_edit) if not is_valid: #self.add_edit.setStyleSheet("QLineEdit{background: red;}") return self.eids = unique(hstack([self.eids, eids])) #self.eids = _add(adict, ['e', 'elem', 'element'], self.eids) #self.cids = _add(adict, ['c', 'cid', 'coord'], self.cids) self._apply_cids_eids() self.add_edit.clear() self.add_edit.setStyleSheet("QLineEdit{background: white;}") self.on_update_main() def _apply_cids_eids(self): #ctext = _get_collapsed_text(self.cids) etext = _get_collapsed_text(self.eids) #self.coords_edit.setText(str(ctext.lstrip())) self.elements_edit.setText(str(etext.lstrip())) self.out_data[self.active_key].element_ids = self.eids def on_remove(self): eids, is_valid = self.check_patran_syntax(self.remove_edit) #adict, is_valid = self.check_patran_syntax_dict(self.remove_edit) if not is_valid: #self.remove_edit.setStyleSheet("QLineEdit{background: red;}") return #self.eids = _remove(adict, ['e', 'elem', 'element'], self.eids) #self.cids = _remove(adict, ['c', 'cid', 'coord'], self.cids) self.eids = setdiff1d(self.eids, eids) self._apply_cids_eids() self.remove_edit.clear() self.remove_edit.setStyleSheet("QLineEdit{background: white;}") self.on_update_main() def on_default_name(self): name = str(self._default_name) self.name_edit.setText(name) self.name_edit.setStyleSheet("QLineEdit{background: white;}") def on_default_elements(self): element_str = str(self._default_elements) self.elements_edit.setText(element_str) self.elements_edit.setStyleSheet("QLineEdit{background: white;}") group = self.out_data[self.active_key] group.element_str = element_str def check_name(self, cell): cell_value = cell.text() try: text = str(cell_value).strip() except UnicodeEncodeError: cell.setStyleSheet("QLineEdit{background: red;}") return None, False if len(text): cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False if self._default_name != text: if self._default_name in self.out_data: cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False def on_validate(self): name, flag0 = self.check_name(self.name_edit) elements, flag1 = self.check_patran_syntax(self.elements_edit, pound=self.elements_pound) #coords_value, flag2 = self.check_patran_syntax(self.coords_edit, #pound=self.coords_pound) if all([flag0, flag1]): self._default_name = name self._default_elements = self.eids self.out_data['clicked_ok'] = True self.out_data['close'] = True return True return False def on_apply(self, force=False): passed = self.on_validate() if passed or force: self.win_parent._apply_modify_groups(self.out_data) return passed def on_ok(self): passed = self.on_apply() if passed: self.close() #self.destroy() def on_cancel(self): self.out_data['close'] = True self.close() def on_update_active_key(self, index): self.update_active_key(index) #str(index.text()) def update_active_key(self, index): #old_obj = self.out_data[self.imain] name = str(index.text()) self._update_active_key_by_name(name) def _update_active_key_by_name(self, name): if name in self.keys: self.active_key = self.keys.index(name) else: # we (hopefully) just removed a row #self.active_key = self.keys[self.active_key] pass self.name_edit.setText(name) obj = self.out_data[self.active_key] self.eids = parse_patran_syntax(obj.element_str, pound=obj.elements_pound) self._default_elements = obj.element_str self._default_name = name self._apply_cids_eids() self.set_as_main_button.setEnabled(True) if name in ['main', 'anti-main']: self.name.setEnabled(False) self.name_set.setEnabled(False) self.name_edit.setEnabled(False) self.name_button.setEnabled(False) self.elements.setEnabled(False) self.elements_button.setEnabled(False) self.elements_edit.setEnabled(False) self.add.setEnabled(False) self.add_button.setEnabled(False) self.add_edit.setEnabled(False) self.remove.setEnabled(False) self.remove_button.setEnabled(False) self.remove_edit.setEnabled(False) self.delete_group_button.setEnabled(False) if name == 'anti-main': self.set_as_main_button.setEnabled(False) #self.apply_button.setEnabled(False) #self.ok_button.setEnabled(False) else: self.name.setEnabled(True) self.name_set.setEnabled(True) self.name_edit.setEnabled(True) self.name_button.setEnabled(True) self.elements.setEnabled(True) self.elements_button.setEnabled(True) self.add.setEnabled(True) self.add_button.setEnabled(True) self.add_edit.setEnabled(True) self.remove.setEnabled(True) self.remove_button.setEnabled(True) self.remove_edit.setEnabled(True) self.delete_group_button.setEnabled(True)
class SelectPackages_Dialog(QDialog): """ Now only used to state new paths to required packages that are not valid anymore. The 'auto import' feature is therefore probably useless by now. """ def __init__(self, parent, required_packages: [str]): super(SelectPackages_Dialog, self).__init__(parent) self.file_paths = [] self.required_packages: [str] = required_packages self.packages = [] self.setLayout(QVBoxLayout()) self.layout().addWidget( QLabel( 'You need to select the locations of the following required node packages' )) # package lists required_packages_list_widget = QListWidget() for p_name in required_packages: required_packages_list_widget.addItem(QListWidgetItem(p_name)) selected_items_widget = QWidget() selected_items_widget.setLayout(QVBoxLayout()) self.selected_packages_list_widget = QListWidget() selected_items_widget.layout().addWidget( self.selected_packages_list_widget) auto_import_button = QPushButton('auto import') auto_import_button.setFocus() auto_import_button.clicked.connect(self.auto_import_button_clicked) selected_items_widget.layout().addWidget(auto_import_button) add_package_button = QPushButton('add') add_package_button.clicked.connect(self.add_package_button_clicked) selected_items_widget.layout().addWidget(add_package_button) clear_package_list_button = QPushButton('clear') clear_package_list_button.clicked.connect( self.clear_selected_packages_list) selected_items_widget.layout().addWidget(clear_package_list_button) finished_button = QPushButton('OK') finished_button.clicked.connect(self.finished_button_clicked) selected_items_widget.layout().addWidget(finished_button) packages_lists_widget = QWidget() packages_lists_widget.setLayout(QHBoxLayout()) packages_lists_widget.layout().addWidget(required_packages_list_widget) packages_lists_widget.layout().addWidget(selected_items_widget) self.layout().addWidget(packages_lists_widget) self.setWindowTitle('select required packages') def auto_import_button_clicked(self): packages_dir = '../packages' folders_list = [ basename(x[0]) for x in os.walk(packages_dir) if basename(x[0]) in self.required_packages ] required_packages = self.required_packages.copy() for pkg_name in required_packages: p_dir = join(packages_dir, pkg_name) p_file = normpath(join(p_dir, 'nodes.py')) if pkg_name in folders_list and \ 'nodes.py' in os.listdir(p_dir) and \ p_file not in self.file_paths: self.file_paths.append(p_file) self.packages.append(NodesPackage(dirname(p_file))) self.rebuild_selected_packages_list_widget() self.clean_packages_list() if self.all_required_packages_selected(): self.finished() def add_package_button_clicked(self): file_names = QFileDialog.getOpenFileNames( self, 'select package file (nodes.py)', '../packages', '(*.py)')[0] for file_name in file_names: try: # simply try to open the file to make sure it's valid f = open(file_name) f.close() self.file_paths.append(file_name) self.packages.append(NodesPackage(dirname(file_name))) except FileNotFoundError: pass self.rebuild_selected_packages_list_widget() def rebuild_selected_packages_list_widget(self): # remove all items self.selected_packages_list_widget.clear() for f in self.file_paths: file_item = QListWidgetItem() file_item.setText(f) self.selected_packages_list_widget.addItem(file_item) def clear_selected_packages_list(self): self.file_paths.clear() self.packages.clear() self.rebuild_selected_packages_list_widget() def finished_button_clicked(self): self.clean_packages_list() if self.all_required_packages_selected(): self.finished() def clean_packages_list(self): """remove duplicates from self.file_paths""" files_dict = {} for p in self.file_paths: package_name = basename(dirname(p)) files_dict[package_name] = p self.file_paths = list(files_dict.values()) self.rebuild_selected_packages_list_widget() def all_required_packages_selected(self): selected_packages = [ basename(normpath(np.directory)) for np in self.packages ] # search for missing packages for p in self.required_packages: if p not in selected_packages: return False return True def finished(self): self.accept() def closeEvent(self, arg__1) -> None: sys.exit()
class SegmentationInfoDialog(QWidget): def __init__(self, settings: StackSettings, set_parameters: Callable[[str, dict], None], additional_text=None): """ :param settings: :param set_parameters: Function which set parameters of chosen in dialog. :param additional_text: Additional text on top of Window. """ super().__init__() self.settings = settings self.parameters_dict = None self.set_parameters = set_parameters self.components = QListWidget() self.components.currentItemChanged.connect(self.change_component_info) self.description = QPlainTextEdit() self.description.setReadOnly(True) self.close_btn = QPushButton("Close") self.close_btn.clicked.connect(self.close) self.set_parameters_btn = QPushButton("Reuse parameters") self.set_parameters_btn.clicked.connect(self.set_parameter_action) self.additional_text_label = QLabel(additional_text) layout = QGridLayout() layout.addWidget(self.additional_text_label, 0, 0, 1, 2) if not additional_text: self.additional_text_label.setVisible(False) layout.addWidget(QLabel("Components:"), 1, 0) layout.addWidget(QLabel("segmentation parameters:"), 1, 1) layout.addWidget(self.components, 2, 0) layout.addWidget(self.description, 2, 1) layout.addWidget(self.close_btn, 3, 0) layout.addWidget(self.set_parameters_btn, 3, 1) self.setLayout(layout) self.setWindowTitle("Parameters preview") def set_parameters_dict(self, val: Optional[Dict[int, SegmentationProfile]]): self.parameters_dict = val def set_additional_text(self, text): self.additional_text_label.setText(text) self.additional_text_label.setVisible(bool(text)) @property def get_parameters(self): if self.parameters_dict: return self.parameters_dict return self.settings.components_parameters_dict def change_component_info(self): if self.components.currentItem() is None: return text = self.components.currentItem().text() parameters = self.get_parameters[int(text)] if parameters is None: self.description.setPlainText("None") else: self.description.setPlainText( f"Component {text}\n" + parameters.pretty_print(mask_algorithm_dict)) def set_parameter_action(self): if self.components.currentItem() is None: return text = self.components.currentItem().text() parameters = self.get_parameters[int(text)] self.set_parameters(parameters.algorithm, parameters.values) def event(self, event: QEvent): if event.type() == QEvent.WindowActivate: index = self.components.currentRow() self.components.clear() self.components.addItems(list(map(str, self.get_parameters.keys()))) self.components.setCurrentRow(index) return super().event(event)
class CalculateInfo(QWidget): """ "widget to show information about plans and allow to se plan details :type settings: Settings """ plan_to_edit_signal = Signal() def __init__(self, settings: PartSettings): super(CalculateInfo, self).__init__() self.settings = settings self.calculate_plans = QListWidget(self) self.plan_view = PlanPreview(self) self.delete_plan_btn = QPushButton("Delete") self.edit_plan_btn = QPushButton("Edit") self.export_plans_btn = QPushButton("Export") self.import_plans_btn = QPushButton("Import") info_layout = QVBoxLayout() info_butt_layout = QGridLayout() info_butt_layout.setSpacing(0) info_butt_layout.addWidget(self.delete_plan_btn, 1, 1) info_butt_layout.addWidget(self.edit_plan_btn, 0, 1) info_butt_layout.addWidget(self.export_plans_btn, 1, 0) info_butt_layout.addWidget(self.import_plans_btn, 0, 0) info_layout.addLayout(info_butt_layout) info_chose_layout = QVBoxLayout() info_chose_layout.setSpacing(2) info_chose_layout.addWidget(QLabel("List of workflows:")) info_chose_layout.addWidget(self.calculate_plans) info_chose_layout.addWidget(QLabel("Preview:")) info_chose_layout.addWidget(self.plan_view) info_layout.addLayout(info_chose_layout) self.setLayout(info_layout) self.calculate_plans.addItems( list(sorted(self.settings.batch_plans.keys()))) self.protect = False self.plan_to_edit = None self.plan_view.header().close() self.calculate_plans.currentTextChanged.connect(self.plan_preview) self.delete_plan_btn.clicked.connect(self.delete_plan) self.edit_plan_btn.clicked.connect(self.edit_plan) self.export_plans_btn.clicked.connect(self.export_plans) self.import_plans_btn.clicked.connect(self.import_plans) def update_plan_list(self): new_plan_list = list(sorted(self.settings.batch_plans.keys())) if self.calculate_plans.currentItem() is not None: text = str(self.calculate_plans.currentItem().text()) try: index = new_plan_list.index(text) except ValueError: index = -1 else: index = -1 self.protect = True self.calculate_plans.clear() self.calculate_plans.addItems(new_plan_list) if index != -1: self.calculate_plans.setCurrentRow(index) else: pass # self.plan_view.setText("") self.protect = False def export_plans(self): choose = ExportDialog(self.settings.batch_plans, PlanPreview) if not choose.exec_(): return dial = QFileDialog(self, "Export calculation plans") dial.setFileMode(QFileDialog.AnyFile) dial.setAcceptMode(QFileDialog.AcceptSave) dial.setDirectory( dial.setDirectory( self.settings.get("io.batch_plan_directory", str(Path.home())))) dial.setNameFilter("Calculation plans (*.json)") dial.setDefaultSuffix("json") dial.selectFile("calculation_plans.json") dial.setHistory(dial.history() + self.settings.get_path_history()) if dial.exec_(): file_path = str(dial.selectedFiles()[0]) self.settings.set("io.batch_plan_directory", os.path.dirname(file_path)) self.settings.add_path_history(os.path.dirname(file_path)) data = { x: self.settings.batch_plans[x] for x in choose.get_export_list() } with open(file_path, "w") as ff: json.dump(data, ff, cls=self.settings.json_encoder_class, indent=2) def import_plans(self): dial = QFileDialog(self, "Import calculation plans") dial.setFileMode(QFileDialog.ExistingFile) dial.setAcceptMode(QFileDialog.AcceptOpen) dial.setDirectory( self.settings.get("io.open_directory", str(Path.home()))) dial.setNameFilter("Calculation plans (*.json)") dial.setDefaultSuffix("json") dial.setHistory(dial.history() + self.settings.get_path_history()) if dial.exec_(): file_path = dial.selectedFiles()[0] plans, err = self.settings.load_part(file_path) self.settings.set("io.batch_plan_directory", os.path.dirname(file_path)) self.settings.add_path_history(os.path.dirname(file_path)) if err: QMessageBox.warning( self, "Import error", "error during importing, part of data were filtered.") choose = ImportDialog(plans, self.settings.batch_plans, PlanPreview) if choose.exec_(): for original_name, final_name in choose.get_import_list(): self.settings.batch_plans[final_name] = plans[ original_name] self.update_plan_list() def delete_plan(self): if self.calculate_plans.currentItem() is None: return text = str(self.calculate_plans.currentItem().text()) if text == "": return if text in self.settings.batch_plans: del self.settings.batch_plans[text] self.update_plan_list() self.plan_view.clear() def edit_plan(self): if self.calculate_plans.currentItem() is None: return text = str(self.calculate_plans.currentItem().text()) if text == "": return if text in self.settings.batch_plans: self.plan_to_edit = self.settings.batch_plans[text] self.plan_to_edit_signal.emit() def plan_preview(self, text): if self.protect: return text = str(text) if text.strip() == "": return plan = self.settings.batch_plans[str(text)] # type: CalculationPlan self.plan_view.set_plan(plan)
class AddFiles(QWidget): """Docstring for AddFiles. """ file_list_changed = Signal(set) def __init__(self, settings: BaseSettings, parent=None, btn_layout=QHBoxLayout): """TODO: to be defined1. """ QWidget.__init__(self, parent) self.settings = settings self.files_to_proceed = set() self.paths = QLineEdit(self) self.selected_files = QListWidget(self) self.selected_files.itemSelectionChanged.connect(self.file_chosen) self.found_button = QPushButton("Find all", self) self.found_button.clicked.connect(self.find_all) self.select_files_button = QPushButton("Select files") self.select_dir_button = QPushButton("Select directory") self.select_files_button.clicked.connect(self.select_files) self.select_dir_button.clicked.connect(self.select_directory) self.delete_button = QPushButton("Remove file", self) self.delete_button.setDisabled(True) self.delete_button.clicked.connect(self.delete_element) self.clean_button = QPushButton("Remove all", self) self.clean_button.clicked.connect(self.clean) layout = QVBoxLayout() layout.addWidget(self.paths) select_layout = btn_layout() select_layout.addWidget(self.select_files_button) select_layout.addWidget(self.select_dir_button) select_layout.addWidget(self.found_button) select_layout.addStretch() select_layout.addWidget(self.clean_button) select_layout.addWidget(self.delete_button) layout.addLayout(select_layout) layout.addWidget(self.selected_files) self.setLayout(layout) def find_all(self): paths = glob(str(self.paths.text())) paths = sorted([ x for x in (set(paths) - self.files_to_proceed) if not os.path.isdir(x) ]) if len(paths) > 0: dialog = AcceptFiles(paths) if dialog.exec_(): new_paths = dialog.get_files() for path in new_paths: size = os.stat(path).st_size size = float(size) / (1024**2) lwi = QListWidgetItem("{:s} ({:.2f} MB)".format( path, size)) lwi.setTextAlignment(Qt.AlignRight) self.selected_files.addItem(lwi) self.files_to_proceed.update(new_paths) self.file_list_changed.emit(self.files_to_proceed) else: QMessageBox.warning(self, "No new files", "No new files found", QMessageBox.Ok) def select_files(self): dial = QFileDialog(self, "Select files") dial.setDirectory( self.settings.get( "io.batch_directory", self.settings.get("io.load_image_directory", str(Path.home())))) dial.setFileMode(QFileDialog.ExistingFiles) if dial.exec_(): self.settings.set("io.batch_directory", os.path.dirname(str(dial.selectedFiles()[0]))) new_paths = sorted( set(map(str, dial.selectedFiles())) - self.files_to_proceed) for path in new_paths: size = os.stat(path).st_size size = float(size) / (1024**2) lwi = QListWidgetItem("{:s} ({:.2f} MB)".format(path, size)) lwi.setTextAlignment(Qt.AlignRight) self.selected_files.addItem(lwi) self.files_to_proceed.update(new_paths) self.file_list_changed.emit(self.files_to_proceed) def select_directory(self): dial = QFileDialog(self, "Select directory") dial.setDirectory( self.settings.get( "io.batch_directory", self.settings.get("io.load_image_directory", str(Path.home())))) dial.setFileMode(QFileDialog.Directory) if dial.exec_(): self.paths.setText(dial.selectedFiles()[0]) self.settings.set("io.batch_directory", str(dial.selectedFiles()[0])) def file_chosen(self): self.delete_button.setEnabled(True) def delete_element(self): item = self.selected_files.takeItem(self.selected_files.currentRow()) path = str(item.text()) path = path[:path.rfind("(") - 1] self.files_to_proceed.remove(path) self.file_list_changed.emit(self.files_to_proceed) if self.selected_files.count() == 0: self.delete_button.setDisabled(True) def clean(self): self.selected_files.clear() self.files_to_proceed.clear() self.file_list_changed.emit(self.files_to_proceed) def get_paths(self): return list(sorted(self.files_to_proceed))
class PayloadWindow(FramelessWindow): get_build_options = Signal() start_build = Signal(str, str, list) stop_build = Signal() def __init__(self, parent): super(PayloadWindow, self).__init__(parent) self.logger = logging.getLogger(self.__class__.__name__) self.setContentsMargins(11, 11, 11, 11) self.progress_windows = [] self.content_widget = QWidget(self) self.addContentWidget(self.content_widget) self.widget_layout = QFormLayout(self.content_widget) self.content_widget.setLayout(self.widget_layout) self.spinner = WaitingSpinner(self, modality=Qt.WindowModal, roundness=70.0, fade=70.0, radius=15.0, lines=6, line_length=25.0, line_width=4.0, speed=1.0) self.build_name_label = QLabel("Name", self.content_widget) self.build_name_edit = QLineEdit(self.content_widget) self.widget_layout.addRow(self.build_name_label, self.build_name_edit) self.icon_label = QLabel("Icon", self.content_widget) self.icon_combobox = QComboBox(self.content_widget) self.widget_layout.addRow(self.icon_label, self.icon_combobox) self.generators_label = QLabel("Generators", self.content_widget) self.generators_list = QListWidget(self.content_widget) self.widget_layout.addRow(self.generators_label, self.generators_list) self.build_button = QPushButton("Build", self.content_widget) self.build_button.clicked.connect(self.on_build_button_clicked) self.widget_layout.addWidget(self.build_button) self.menu = QMenu(self) self.menu.setTitle("Payload") self.reload_action = QAction(self.menu) self.reload_action.setText("Reload options") self.reload_action.triggered.connect(self.setupUi) self.menu.addAction(self.reload_action) self.addMenu(self.menu) @Slot() def setupUi(self): self.spinner.start() self.get_build_options.emit() @Slot(dict) def process_build_message(self, message): if self.isVisible(): event = message.get("event") if event == "options": options = message.get("options") self.set_options(options.get("generators"), options.get("icons")) elif event == "started": self.set_started(message.get("generator_name")) elif event == "stopped": self.set_stopped() elif event == "progress": self.set_progress(message.get("generator_name"), message.get("progress")) elif event == "error": self.set_error(message.get("error")) elif event == "generator_finished": self.set_generator_finished(message.get("generator_name"), message.get("exit_code")) elif event == "build_finished": self.set_build_finished() @Slot(str, str) def set_progress(self, generator_name, progress): for win in self.progress_windows: if win.generator() == generator_name: win.appendProgress(progress) @Slot(str) def set_started(self, generator_name): win = ProgressWindow(self, generator_name) win.show() self.progress_windows.append(win) self.logger.info("Generator {} started!".format(generator_name)) self.reload_action.setEnabled(False) self.spinner.start() @Slot(str, int) def set_generator_finished(self, generator_name, exit_code): self.logger.info("Generator {} finished with exit code {}.".format( generator_name, exit_code)) @Slot(list, list) def set_options(self, generators, icons): self.build_name_edit.clear() self.generators_list.clear() self.icon_combobox.clear() for generator in generators: item = QListWidgetItem(generator, self.generators_list) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) self.generators_list.addItem(item) for icon in icons: name = icon.get("name") pix = QPixmap() pix.loadFromData(base64.b64decode(icon.get("ico"))) ico = QIcon() ico.addPixmap(pix) self.icon_combobox.addItem(ico, name) self.spinner.stop() @Slot() def set_stopped(self): self.on_build_finished() self.build_button.setText("Build") self.stopped_messagebox = FramelessInformationMessageBox(self) self.stopped_messagebox.setText( "Build process has been stopped successfully.") self.stopped_messagebox.setStandardButtons(QDialogButtonBox.Ok) self.stopped_messagebox.button(QDialogButtonBox.Ok).clicked.connect( self.stopped_messagebox.close) self.stopped_messagebox.show() @Slot(str) def set_error(self, error): self.on_build_finished() self.build_button.setText("Build") self.error_messagebox = FramelessCriticalMessageBox(self) self.error_messagebox.setText(error) self.error_messagebox.setStandardButtons(QDialogButtonBox.Ok) self.error_messagebox.button(QDialogButtonBox.Ok).clicked.connect( self.error_messagebox.close) self.error_messagebox.show() @Slot() def set_build_finished(self): self.on_build_finished() self.build_button.setText("Build") self.build_finished_messagebox = FramelessInformationMessageBox(self) self.build_finished_messagebox.setText( "Build process has been finished.") self.build_finished_messagebox.setStandardButtons(QDialogButtonBox.Ok) self.build_finished_messagebox.button( QDialogButtonBox.Ok).clicked.connect( self.build_finished_messagebox.close) self.build_finished_messagebox.show() @Slot() def on_build_button_clicked(self): if self.build_button.text() == "Build": generators = [] for i in range(self.generators_list.count()): item = self.generators_list.item(i) if item.checkState() == Qt.Checked: generators.append(item.text()) self.start_build.emit(self.build_name_edit.text(), self.icon_combobox.currentText(), generators) self.build_button.setText("Stop") else: self.stop_build_messagebox = FramelessQuestionMessageBox(self) self.stop_build_messagebox.setText( "Do you want to stop build process?") self.stop_build_messagebox.setStandardButtons( QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.stop_build_messagebox.button( QDialogButtonBox.Cancel).clicked.connect( self.stop_build_messagebox.close) self.stop_build_messagebox.button( QDialogButtonBox.Ok).clicked.connect(self.stop_build.emit) self.stop_build_messagebox.button( QDialogButtonBox.Ok).clicked.connect( self.stop_build_messagebox.close) self.stop_build_messagebox.show() @Slot() def on_build_finished(self): self.reload_action.setEnabled(True) self.spinner.stop() @Slot() def close(self) -> bool: for win in self.progress_windows: win.close() return super().close()
class GroupsModify(PyDialog): """ +-------------------------------+ | Groups : Modify | +-------------------------------+ | | | Name xxx Default | | Nodes xxx Default Show | | Color xxx Default | | Add xxx Add Show | | Remove xxx Remove Show | | | | Set OK Cancel | +-------------------------------+ """ def __init__(self, data, win_parent=None, model_name=None, group_active='main'): super(GroupsModify, self).__init__(data, win_parent) self.set_font_size(data['font_size']) self._updated_groups = False self.model_name = model_name self.actors = [] #self.out_data = data #print(data) self.keys = get_groups_sorted_by_name(data) self.active_key = self.keys.index(group_active) group_obj = data[self.active_key] unused_name = group_obj.name self.imain = 0 self.nrows = len(self.keys) self._default_name = group_obj.name self._default_elements = group_obj.element_str self.elements_pound = group_obj.elements_pound self.table = QListWidget(parent=None) self.table.clear() self.table.addItems(self.keys) self.setWindowTitle('Groups: Modify') self.create_widgets() self.create_layout() self.set_connections() self.on_set_as_main() def create_widgets(self): """creates the menu objects""" #icon = QtGui.QPixmap(os.path.join(ICON_PATH, 'node.png')) #icon = QtGui.QPixmap(os.path.join(ICON_PATH, 'element.png')) # Name self.pick_style_label = QLabel('Pick Style:') #self.pick_node_button = QPushButton('Node') self.pick_element_button = QPushButton('Element') self.pick_area_button = QPushButton('Area') #self.pick_node_button.setIcon(icon) #self.pick_area_button.setIcon(icon) # Name self.name_label = QLabel('Name:') self.name_set = QPushButton('Set') self.name_edit = QLineEdit(str(self._default_name).strip()) self.name_button = QPushButton('Default') # elements self.elements_label = QLabel('Element IDs:') self.elements_edit = QLineEdit(str(self._default_elements).strip()) self.elements_button = QPushButton('Default') self.elements_highlight_button = QPushButton('Show') # add self.add_label = QLabel('Add Elements:') self.add_edit = QElementEdit(self, self.model_name, pick_style='area') self.add_button = QPushButton('Add') self.add_highlight_button = QPushButton('Show') # remove self.remove_label = QLabel('Remove Elements:') self.remove_edit = QElementEdit(self, self.model_name, pick_style='area') self.remove_button = QPushButton('Remove') self.remove_highlight_button = QPushButton('Show') # applies a unique implicitly self.eids = parse_patran_syntax(str(self._default_elements), pound=self.elements_pound) # closing #self.apply_button = QPushButton('Apply') self.ok_button = QPushButton('Close') #self.cancel_button = QPushButton('Cancel') self.set_as_main_button = QPushButton('Set As Main') self.create_group_button = QPushButton('Create New Group') self.delete_group_button = QPushButton('Delete Group') self.name_label.setEnabled(False) self.name_set.setEnabled(False) self.name_edit.setEnabled(False) self.name_button.setEnabled(False) self.elements_label.setEnabled(False) self.elements_button.setEnabled(False) self.elements_edit.setEnabled(False) self.elements_highlight_button.setEnabled(False) self.add_label.setEnabled(False) self.add_button.setEnabled(False) self.add_edit.setEnabled(False) self.add_highlight_button.setEnabled(False) self.remove_label.setEnabled(False) self.remove_button.setEnabled(False) self.remove_edit.setEnabled(False) self.remove_highlight_button.setEnabled(False) #self.apply_button.setEnabled(False) #self.ok_button.setEnabled(False) def create_layout(self): """displays the menu objects""" hbox = QHBoxLayout() hbox.addWidget(self.pick_style_label) hbox.addWidget(self.pick_element_button) hbox.addWidget(self.pick_area_button) hbox.addStretch() grid = QGridLayout() irow = 0 grid.addWidget(self.name_label, irow, 0) grid.addWidget(self.name_edit, irow, 1) grid.addWidget(self.name_set, irow, 2) grid.addWidget(self.name_button, irow, 3) irow += 1 grid.addWidget(self.elements_label, irow, 0) grid.addWidget(self.elements_edit, irow, 1) grid.addWidget(self.elements_button, irow, 2) grid.addWidget(self.elements_highlight_button, irow, 3) irow += 1 grid.addWidget(self.add_label, irow, 0) grid.addWidget(self.add_edit, irow, 1) grid.addWidget(self.add_button, irow, 2) grid.addWidget(self.add_highlight_button, irow, 3) irow += 1 grid.addWidget(self.remove_label, irow, 0) grid.addWidget(self.remove_edit, irow, 1) grid.addWidget(self.remove_button, irow, 2) grid.addWidget(self.remove_highlight_button, irow, 3) irow += 1 ok_cancel_box = QHBoxLayout() #ok_cancel_box.addWidget(self.apply_button) ok_cancel_box.addWidget(self.ok_button) #ok_cancel_box.addWidget(self.cancel_button) main_create_delete = QHBoxLayout() main_create_delete.addWidget(self.set_as_main_button) main_create_delete.addWidget(self.create_group_button) main_create_delete.addWidget(self.delete_group_button) vbox = QVBoxLayout() vbox.addWidget(self.table) vbox.addLayout(hbox) vbox.addLayout(grid) vbox.addLayout(main_create_delete) vbox.addStretch() vbox.addLayout(ok_cancel_box) self.setLayout(vbox) def on_set_name(self): self.remove_highlight_actor() name = str(self.name_edit.text()).strip() if name not in self.keys: self.name_edit.setStyleSheet("QLineEdit{background: white;}") group = self.out_data[self.active_key] group.name = name self.keys[self.active_key] = name self.recreate_table() elif name != self.keys[self.active_key]: self.name_edit.setStyleSheet('QLineEdit{background: red;}') elif name == self.keys[self.active_key]: self.name_edit.setStyleSheet('QLineEdit{background: white;}') def on_pick_element(self): self.add_edit.pick_style = 'single' self.remove_edit.pick_style = 'single' def on_pick_area(self): self.add_edit.pick_style = 'area' self.remove_edit.pick_style = 'area' def set_connections(self): """creates the actions for the menu""" self.pick_element_button.clicked.connect(self.on_pick_element) self.pick_area_button.clicked.connect(self.on_pick_area) self.name_set.clicked.connect(self.on_set_name) self.name_button.clicked.connect(self.on_default_name) self.elements_button.clicked.connect(self.on_default_elements) self.elements_highlight_button.clicked.connect( self.on_highlight_elements) self.add_button.clicked.connect(self.on_add) self.add_highlight_button.clicked.connect(self.on_highlight_add) self.remove_button.clicked.connect(self.on_remove) self.remove_highlight_button.clicked.connect(self.on_highlight_remove) self.table.itemClicked.connect(self.on_update_active_key) self.ok_button.clicked.connect(self.on_ok) self.set_as_main_button.clicked.connect(self.on_set_as_main) self.create_group_button.clicked.connect(self.on_create_group) self.delete_group_button.clicked.connect(self.on_delete_group) def on_create_group(self): self.remove_highlight_actor() irow = self.nrows new_key = f'Group {irow:d}' while new_key in self.keys: irow += 1 new_key = f'Group {irow:d}' irow = self.nrows self.keys.append(new_key) group = Group(new_key, element_str='', elements_pound=self.elements_pound, editable=True) self.out_data[irow] = group self.table.reset() self.table.addItems(self.keys) self.nrows += 1 #---------------------------------- # update internal parameters #self.out_data = items if self.imain > self.active_key: self.imain += 1 #make the new group the default self.active_key = self.nrows - 1 self.keys = get_groups_sorted_by_name(self.out_data) self.recreate_table() def recreate_table(self): # update gui self.table.clear() self.table.addItems(self.keys) item = self.table.item(self.imain) bold = QtGui.QFont() bold.setBold(True) bold.setItalic(True) item.setFont(bold) self.table.update() # update key name = self.keys[self.active_key] self._update_active_key_by_name(name) def on_delete_group(self): self.remove_highlight_actor() if self.active_key == 0: return #self.deleted_groups.add(self.imain) items = {} j = 0 for i, key in sorted(self.out_data.items()): if isinstance(i, int): continue if i != self.active_key: items[j] = key j += 1 # update internal parameters self.out_data = items if self.imain >= self.active_key: self.imain = max(0, self.imain - 1) self.active_key = max(0, self.active_key - 1) self.nrows -= 1 self.keys = [group.name for key, group in sorted(items.items())] self.recreate_table() # update key name = self.keys[self.active_key] self._update_active_key_by_name(name) def on_set_as_main(self): bold = QtGui.QFont() bold.setBold(True) bold.setItalic(True) normal = QtGui.QFont() normal.setBold(False) normal.setItalic(False) obj = self.table.item(self.imain) obj.setFont(normal) self.imain = self.active_key obj = self.table.item(self.imain) obj.setFont(bold) group = self.out_data[self.imain] self._default_elements = group.element_str self._default_name = group.name self.on_update_main() def on_update_main(self): """adds/removes the elements to the main actor when add/remove is pressed""" group = self.out_data[self.imain] if self._default_name == group.name and self.win_parent is not None: # we're not testing the menu self.win_parent.post_group(group, update_groups=True) def closeEvent(self, event): """closes the window""" self.remove_highlight_actor() self.out_data['close'] = True event.accept() def remove_highlight_actor(self): """removes the highlighted actor""" gui = self.win_parent remove_actors_from_gui(gui, self.actors, render=True) self.actors = [] def on_highlight(self, nids=None, eids=None): """highlights the nodes""" gui = self.win_parent unused_name = self.keys[self.active_key] if gui is not None: self.remove_highlight_actor() ## TODO: super strange; doesn't work... mouse_actions = gui.mouse_actions grid = mouse_actions.get_grid_selected(self.model_name) #all_nodes = mouse_actions.node_ids all_elements = mouse_actions.element_ids actors = create_highlighted_actors(gui, grid, all_nodes=None, nodes=nids, all_elements=all_elements, elements=eids, add_actors=False) if actors: add_actors_to_gui(gui, actors, render=True) self.actors = actors def on_highlight_elements(self): """highlights the active elements""" self.on_highlight(eids=self.eids) def on_highlight_add(self): """highlights the elements to add""" eids, is_valid = check_patran_syntax(self.add_edit, pound=self.elements_pound) if not is_valid: #self.add_edit.setStyleSheet("QLineEdit{background: red;}") return self.on_highlight(eids=eids) def on_highlight_remove(self): """highlights the elements to remove""" eids, is_valid = check_patran_syntax(self.remove_edit) if not is_valid: #self.remove_edit.setStyleSheet("QLineEdit{background: red;}") return self.on_highlight(eids=eids) def on_add(self): self.remove_highlight_actor() eids, is_valid = check_patran_syntax(self.add_edit, pound=self.elements_pound) #adict, is_valid = check_patran_syntax_dict(self.add_edit) if not is_valid: #self.add_edit.setStyleSheet("QLineEdit{background: red;}") return self.eids = unique(hstack([self.eids, eids])) #self.eids = _add(adict, ['e', 'elem', 'element'], self.eids) #self.cids = _add(adict, ['c', 'cid', 'coord'], self.cids) self._apply_cids_eids() self.add_edit.clear() self.add_edit.setStyleSheet("QLineEdit{background: white;}") self.on_update_main() def _apply_cids_eids(self): #ctext = _get_collapsed_text(self.cids) etext = _get_collapsed_text(self.eids) #self.coords_edit.setText(str(ctext.lstrip())) self.elements_edit.setText(str(etext.lstrip())) self.out_data[self.active_key].element_ids = self.eids def on_remove(self): self.remove_highlight_actor() eids, is_valid = check_patran_syntax(self.remove_edit) #adict, is_valid = check_patran_syntax_dict(self.remove_edit) if not is_valid: #self.remove_edit.setStyleSheet("QLineEdit{background: red;}") return #self.eids = _remove(adict, ['e', 'elem', 'element'], self.eids) #self.cids = _remove(adict, ['c', 'cid', 'coord'], self.cids) self.eids = setdiff1d(self.eids, eids) self._apply_cids_eids() self.remove_edit.clear() self.remove_edit.setStyleSheet(QLINE_EDIT_BASIC) self.on_update_main() def on_default_name(self): self.remove_highlight_actor() name = str(self._default_name) self.name_edit.setText(name) self.name_edit.setStyleSheet("QLineEdit{background: white;}") def on_default_elements(self): self.remove_highlight_actor() element_str = str(self._default_elements) self.elements_edit.setText(element_str) self.elements_edit.setStyleSheet("QLineEdit{background: white;}") group = self.out_data[self.active_key] group.element_str = element_str def check_name(self, cell): cell_value = cell.text() try: text = str(cell_value).strip() except UnicodeEncodeError: cell.setStyleSheet("QLineEdit{background: red;}") return None, False if len(text): cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False if self._default_name != text: if self._default_name in self.out_data: cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False def on_validate(self): name, flag0 = self.check_name(self.name_edit) unused_elements, flag1 = check_patran_syntax(self.elements_edit, pound=self.elements_pound) #coords_value, flag2 = check_patran_syntax( #self.coords_edit, pound=self.coords_pound) if all([flag0, flag1]): self._default_name = name self._default_elements = self.eids self.out_data['clicked_ok'] = True self.out_data['close'] = True return True return False def on_apply(self, force=False): passed = self.on_validate() if passed or force: self.win_parent._apply_modify_groups(self.out_data) return passed def on_ok(self): passed = self.on_apply() if passed: self.close() #self.destroy() def on_cancel(self): self.remove_highlight_actor() self.out_data['close'] = True self.close() def on_update_active_key(self, index): self.update_active_key(index) #str(index.text()) def update_active_key(self, index): #old_obj = self.out_data[self.imain] name = str(index.text()) self._update_active_key_by_name(name) def _update_active_key_by_name(self, name): if name in self.keys: self.active_key = self.keys.index(name) else: # we (hopefully) just removed a row #self.active_key = self.keys[self.active_key] pass self.name_edit.setText(name) obj = self.out_data[self.active_key] self.eids = parse_patran_syntax(obj.element_str, pound=obj.elements_pound) self._default_elements = obj.element_str self._default_name = name self._apply_cids_eids() self.set_as_main_button.setEnabled(True) if name in ['main', 'anti-main']: enabled = False self.elements_edit.setEnabled(False) if name == 'anti-main': self.set_as_main_button.setEnabled(False) #self.apply_button.setEnabled(False) #self.ok_button.setEnabled(False) else: enabled = True self.elements_highlight_button.setEnabled(True) self.add_label.setEnabled(True) self.add_highlight_button.setEnabled(True) self.remove_highlight_button.setEnabled(True) #self.apply_button.setEnabled(True) #self.ok_button.setEnabled(True) self.name_label.setEnabled(enabled) self.name_set.setEnabled(enabled) self.name_edit.setEnabled(enabled) self.name_button.setEnabled(enabled) self.elements_label.setEnabled(enabled) self.elements_button.setEnabled(enabled) self.add_button.setEnabled(enabled) self.add_edit.setEnabled(enabled) self.remove_label.setEnabled(enabled) self.remove_button.setEnabled(enabled) self.remove_edit.setEnabled(enabled) self.delete_group_button.setEnabled(enabled)
class RemoteKernelSetupDialog(QDialog): """Dialog to connect to existing kernels (either local or remote).""" def __init__(self, parent=None): super(RemoteKernelSetupDialog, self).__init__(parent) self.setWindowTitle(_('Setup remote kernel')) self.TEXT_FETCH_REMOTE_CONN_FILES_BTN = 'Fetch remote connection files' self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME = 'jupyter --runtime-dir' # Name of the connection cfg_name_label = QLabel(_('Configuration name:')) self.cfg_name_line_edit = QLineEdit() # SSH connection hostname_label = QLabel(_('Hostname:')) self.hostname_lineedit = QLineEdit() port_label = QLabel(_('Port:')) self.port_lineeidt = QLineEdit() self.port_lineeidt.setMaximumWidth(75) username_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:')) self.pw = QLineEdit() self.pw.setEchoMode(QLineEdit.Password) self.pw_radio.toggled.connect(self.pw.setEnabled) self.keyfile_radio.toggled.connect(self.pw.setDisabled) self.keyfile_path_lineedit = QLineEdit() keyfile_browse_btn = QPushButton(_('Browse')) keyfile_browse_btn.clicked.connect(self.select_ssh_key) keyfile_layout = QHBoxLayout() keyfile_layout.addWidget(self.keyfile_path_lineedit) keyfile_layout.addWidget(keyfile_browse_btn) passphrase_label = QLabel(_('Passphrase:')) self.passphrase_lineedit = QLineEdit() self.passphrase_lineedit.setPlaceholderText(_('Optional')) self.passphrase_lineedit.setEchoMode(QLineEdit.Password) self.keyfile_radio.toggled.connect(self.keyfile_path_lineedit.setEnabled) self.keyfile_radio.toggled.connect(self.passphrase_lineedit.setEnabled) self.keyfile_radio.toggled.connect(keyfile_browse_btn.setEnabled) self.keyfile_radio.toggled.connect(passphrase_label.setEnabled) self.pw_radio.toggled.connect(self.keyfile_path_lineedit.setDisabled) self.pw_radio.toggled.connect(self.passphrase_lineedit.setDisabled) self.pw_radio.toggled.connect(keyfile_browse_btn.setDisabled) self.pw_radio.toggled.connect(passphrase_label.setDisabled) # Button to fetch JSON files listing # self.kf_fetch_conn_files_btn = QPushButton(_(self.TEXT_FETCH_REMOTE_CONN_FILES_BTN)) # self.kf_fetch_conn_files_btn.clicked.connect(self.fill_combobox_with_fetched_remote_connection_files) # self.cb_remote_conn_files = QComboBox() # self.cb_remote_conn_files.currentIndexChanged.connect(self._take_over_selected_remote_configuration_file) # Remote kernel groupbox self.start_remote_kernel_group = QGroupBox(_("Start remote kernel")) # Advanced settings to get remote connection files jupyter_runtime_location_cmd_label = QLabel(_('Command to get Jupyter runtime:')) self.jupyter_runtime_location_cmd_lineedit = QLineEdit() self.jupyter_runtime_location_cmd_lineedit.setPlaceholderText(_(self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME)) # SSH layout ssh_layout = QGridLayout() ssh_layout.addWidget(cfg_name_label, 0, 0) ssh_layout.addWidget(self.cfg_name_line_edit, 0, 2) ssh_layout.addWidget(hostname_label, 1, 0, 1, 2) ssh_layout.addWidget(self.hostname_lineedit, 1, 2) ssh_layout.addWidget(port_label, 1, 3) ssh_layout.addWidget(self.port_lineeidt, 1, 4) ssh_layout.addWidget(username_label, 2, 0, 1, 2) ssh_layout.addWidget(self.username_lineedit, 2, 2, 1, 3) # SSH authentication layout auth_layout = QGridLayout() auth_layout.addWidget(self.pw_radio, 1, 0) auth_layout.addWidget(pw_label, 1, 1) auth_layout.addWidget(self.pw, 1, 2) auth_layout.addWidget(self.keyfile_radio, 2, 0) auth_layout.addWidget(keyfile_label, 2, 1) auth_layout.addLayout(keyfile_layout, 2, 2) auth_layout.addWidget(passphrase_label, 3, 1) auth_layout.addWidget(self.passphrase_lineedit, 3, 2) auth_layout.addWidget(jupyter_runtime_location_cmd_label, 4, 1) auth_layout.addWidget(self.jupyter_runtime_location_cmd_lineedit, 4, 2) # auth_layout.addWidget(self.kf_fetch_conn_files_btn, 5, 1) # auth_layout.addWidget(self.cb_remote_conn_files, 5, 2) auth_group.setLayout(auth_layout) # Remote kernel layout self.rm_group = QGroupBox(_("Setup up of a remote connection")) self.rm_group.setEnabled(False) rm_layout = QVBoxLayout() rm_layout.addLayout(ssh_layout) rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) rm_layout.addWidget(auth_group) self.rm_group.setLayout(rm_layout) self.rm_group.setCheckable(False) # Ok and Cancel buttons self.accept_btns = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.accept_btns.accepted.connect(self.accept) self.accept_btns.rejected.connect(self.reject) btns_layout = QHBoxLayout() btns_layout.addWidget(self.accept_btns) # Dialog layout layout = QVBoxLayout() layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) # layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12))) layout.addWidget(self.rm_group) layout.addLayout(btns_layout) # Main layout hbox_layout = QHBoxLayout(self) # Left side with the list of all remote connection configurations items_label = QLabel(text="Configured remote locations") self.items_list = QListWidget() self.items_list.clicked.connect(self._on_items_list_click) items_layout = QVBoxLayout() items_layout.addWidget(items_label) items_layout.addWidget(self.items_list) edit_delete_new_buttons_layout = QHBoxLayout() edit_btn = QPushButton(text="Edit") add_btn = QPushButton(text="Add") delete_btn = QPushButton(text="Delete") add_btn.clicked.connect(self._on_add_btn_click) edit_btn.clicked.connect(self._on_edit_btn_click) delete_btn.clicked.connect(self._on_delete_btn_click) edit_delete_new_buttons_layout.addWidget(add_btn) edit_delete_new_buttons_layout.addWidget(edit_btn) edit_delete_new_buttons_layout.addWidget(delete_btn) items_layout.addLayout(edit_delete_new_buttons_layout) hbox_layout.addSpacerItem(QSpacerItem(10, 0)) hbox_layout.addLayout(items_layout) hbox_layout.addLayout(layout) self.lst_with_connecion_configs = [] def _on_items_list_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings idx_of_config = self.items_list.selectedIndexes()[0].row() cfg = self.lst_with_connecion_configs[idx_of_config] if isinstance(cfg, RemoteConnectionSettings): self._update_remote_connection_input_fields(cfg) else: show_info_dialog("Information", "This functionality is still not available") def _clear_remote_connection_input_fields(self): self.keyfile_path_lineedit.setText("") self.passphrase_lineedit.setText("") self.hostname_lineedit.setText("") self.username_lineedit.setText("") self.port_lineeidt.setText("") self.cfg_name_line_edit.setText("") self.jupyter_runtime_location_cmd_lineedit.setText("") self.keyfile_radio.setChecked(False) self.pw_radio.setChecked(False) def _update_remote_connection_input_fields(self, remote_conn_settings): self.keyfile_path_lineedit.setText(remote_conn_settings.keyfile_path) self.passphrase_lineedit.setText(remote_conn_settings.password) self.hostname_lineedit.setText(remote_conn_settings.hostname) self.username_lineedit.setText(remote_conn_settings.username) self.port_lineeidt.setText(str(remote_conn_settings.port)) self.cfg_name_line_edit.setText(remote_conn_settings.connection_name) self.jupyter_runtime_location_cmd_lineedit.setText(remote_conn_settings.cmd_for_jupyter_runtime_location) self.keyfile_radio.setChecked(remote_conn_settings.keyfile_path is not None) self.pw_radio.setChecked(remote_conn_settings.password is not None) def _on_add_btn_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings username = self.username_lineedit.text() passphrase = self.passphrase_lineedit.text() hostname = self.hostname_lineedit.text() keyfile_path = self.keyfile_path_lineedit.text() port = int(self.port_lineeidt.text()) if self.port_lineeidt.text() != "" else 22 jup_runtime_cmd = self.jupyter_runtime_location_cmd_lineedit.text() cfg_name = self.cfg_name_line_edit.text() cfg = RemoteConnectionSettings( username=username, hostname=hostname, keyfile_path=keyfile_path, port=port, connection_name=cfg_name, cmd_for_jupyter_runtime_location=jup_runtime_cmd, password=passphrase ) self.lst_with_connecion_configs.append(cfg) self._update_list_with_configs() self.rm_group.setEnabled(False) def _on_edit_btn_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings self.rm_group.setEnabled(True) idx_of_config = self.items_list.selectedIndexes()[0].row() cfg = self.lst_with_connecion_configs[idx_of_config] if isinstance(cfg, RemoteConnectionSettings): self._update_remote_connection_input_fields(cfg) else: show_info_dialog("Information", "This functionality is still not available") def _on_delete_btn_click(self): idx_of_config = self.items_list.selectedIndexes()[0].row() self.lst_with_connecion_configs.pop(idx_of_config) self._update_list_with_configs() def select_ssh_key(self): kf = getopenfilename(self, _('Select SSH keyfile'), get_home_dir(), '*.pem;;*')[0] self.keyfile_path_lineedit.setText(kf) def _take_over_selected_remote_configuration_file(self, chosen_idx_of_combobox_with_remote_conn_files): remote_path_filename = self.remote_conn_file_paths[chosen_idx_of_combobox_with_remote_conn_files] self.cf.setText(remote_path_filename) def set_connection_configs(self, lst_with_connecion_configs): self.lst_with_connecion_configs = lst_with_connecion_configs self._update_list_with_configs() def _update_list_with_configs(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings # now, fill the list self.items_list.clear() for cfg in self.lst_with_connecion_configs: if isinstance(cfg, LocalConnectionSettings): self.items_list.addItem(f"Local: {cfg.connection_name}") elif isinstance(cfg, RemoteConnectionSettings): self.items_list.addItem(f"Remote: {cfg.connection_name}") def get_connection_settings(self): return self.lst_with_connecion_configs
class FileSwitcher(QDialog): """A Sublime-like file switcher.""" sig_goto_file = Signal(int, object) # Constants that define the mode in which the list widget is working # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols # in a given file when using the '@' symbol. FILE_MODE, SYMBOL_MODE = [1, 2] MAX_WIDTH = 600 def __init__(self, parent, plugin, tabs, data, icon): QDialog.__init__(self, parent) # Variables self.plugins_tabs = [] self.plugins_data = [] self.plugins_instances = [] self.add_plugin(plugin, tabs, data, icon) self.plugin = None # Last plugin with focus self.mode = self.FILE_MODE # By default start in this mode self.initial_cursors = None # {fullpath: QCursor} self.initial_path = None # Fullpath of initial active editor self.initial_widget = None # Initial active editor self.line_number = None # Selected line number in filer self.is_visible = False # Is the switcher visible? help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to " "cancel.<br><br>Type to filter filenames.<br><br>" "Use <b>:number</b> to go to a line, e.g. " "<b><code>main:42</code></b><br>" "Use <b>@symbol_text</b> to go to a symbol, e.g. " "<b><code>@init</code></b>" "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>") # Either allow searching for a line number or a symbol but not both regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" + "([A-Za-z0-9_]{0,100}:{0,1}[0-9]{0,100})") # Widgets self.edit = FilesFilterLine(self) self.help = HelperToolButton() self.list = QListWidget(self) self.filter = KeyPressFilter() regex_validator = QRegExpValidator(regex, self.edit) # Widgets setup self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) self.setWindowOpacity(0.95) self.edit.installEventFilter(self.filter) self.edit.setValidator(regex_validator) self.help.setToolTip(help_text) self.list.setItemDelegate(HTMLDelegate(self)) # Layout edit_layout = QHBoxLayout() edit_layout.addWidget(self.edit) edit_layout.addWidget(self.help) layout = QVBoxLayout() layout.addLayout(edit_layout) layout.addWidget(self.list) self.setLayout(layout) # Signals self.rejected.connect(self.restore_initial_state) self.filter.sig_up_key_pressed.connect(self.previous_row) self.filter.sig_down_key_pressed.connect(self.next_row) self.edit.returnPressed.connect(self.accept) self.edit.textChanged.connect(self.setup) self.list.itemSelectionChanged.connect(self.item_selection_changed) self.list.clicked.connect(self.edit.setFocus) # --- Properties @property def widgets(self): widgets = [] for tabs, plugin in self.plugins_tabs: widgets += [(tabs.widget(index), plugin) for index in range(tabs.count())] return widgets @property def line_count(self): line_count = [] for widget in self.widgets: try: current_line_count = widget[0].get_line_count() except AttributeError: current_line_count = 0 line_count.append(current_line_count) return line_count @property def save_status(self): save_status = [] for da, icon in self.plugins_data: save_status += [getattr(td, 'newly_created', False) for td in da] return save_status @property def paths(self): paths = [] for da, icon in self.plugins_data: paths += [getattr(td, 'filename', None) for td in da] return paths @property def filenames(self): filenames = [] for da, icon in self.plugins_data: filenames += [os.path.basename(getattr(td, 'filename', None)) for td in da] return filenames @property def icons(self): icons = [] for da, icon in self.plugins_data: icons += [icon for td in da] return icons @property def current_path(self): return self.paths_by_widget[self.get_widget()] @property def paths_by_widget(self): widgets = [w[0] for w in self.widgets] return dict(zip(widgets, self.paths)) @property def widgets_by_path(self): widgets = [w[0] for w in self.widgets] return dict(zip(self.paths, widgets)) @property def filter_text(self): """Get the normalized (lowecase) content of the filter text.""" return to_text_string(self.edit.text()).lower() def set_search_text(self, _str): self.edit.setText(_str) def save_initial_state(self): """Save initial cursors and initial active widget.""" paths = self.paths self.initial_widget = self.get_widget() self.initial_cursors = {} for i, editor in enumerate(self.widgets): if editor is self.initial_widget: self.initial_path = paths[i] # This try is needed to make the fileswitcher work with # plugins that does not have a textCursor. try: self.initial_cursors[paths[i]] = editor.textCursor() except AttributeError: pass def accept(self): self.is_visible = False QDialog.accept(self) self.list.clear() def restore_initial_state(self): """Restores initial cursors and initial active editor.""" self.list.clear() self.is_visible = False widgets = self.widgets_by_path if not self.edit.clicked_outside: for path in self.initial_cursors: cursor = self.initial_cursors[path] if path in widgets: self.set_editor_cursor(widgets[path], cursor) if self.initial_widget in self.paths_by_widget: index = self.paths.index(self.initial_path) self.sig_goto_file.emit(index) def set_dialog_position(self): """Positions the file switcher dialog.""" parent = self.parent() geo = parent.geometry() width = self.list.width() # This has been set in setup left = parent.geometry().width()/2 - width/2 # Note: the +1 pixel on the top makes it look better if isinstance(parent, QMainWindow): top = (parent.toolbars_menu.geometry().height() + parent.menuBar().geometry().height() + 1) else: top = self.plugins_tabs[0][0].tabBar().geometry().height() + 1 while parent: geo = parent.geometry() top += geo.top() left += geo.left() parent = parent.parent() self.move(left, top) def get_item_size(self, content): """ Get the max size (width and height) for the elements of a list of strings as a QLabel. """ strings = [] if content: for rich_text in content: label = QLabel(rich_text) label.setTextFormat(Qt.PlainText) strings.append(label.text()) fm = label.fontMetrics() return (max([fm.width(s) * 1.3 for s in strings]), fm.height()) def fix_size(self, content): """ Adjusts the width and height of the file switcher based on the relative size of the parent and content. """ # Update size of dialog based on relative size of the parent if content: width, height = self.get_item_size(content) # Width parent = self.parent() relative_width = parent.geometry().width() * 0.65 if relative_width > self.MAX_WIDTH: relative_width = self.MAX_WIDTH self.list.setMinimumWidth(relative_width) # Height if len(content) < 8: max_entries = len(content) else: max_entries = 8 max_height = height * max_entries * 2.5 self.list.setMinimumHeight(max_height) # Resize self.list.resize(relative_width, self.list.height()) # --- Helper methods: List widget def count(self): """Gets the item count in the list widget.""" return self.list.count() def current_row(self): """Returns the current selected row in the list widget.""" return self.list.currentRow() def set_current_row(self, row): """Sets the current selected row in the list widget.""" return self.list.setCurrentRow(row) def select_row(self, steps): """Select row in list widget based on a number of steps with direction. Steps can be positive (next rows) or negative (previous rows). """ row = self.current_row() + steps if 0 <= row < self.count(): self.set_current_row(row) def previous_row(self): """Select previous row in list widget.""" if self.mode == self.SYMBOL_MODE: self.select_row(-1) return prev_row = self.current_row() - 1 if prev_row >= 0: title = self.list.item(prev_row).text() else: title = '' if prev_row == 0 and '</b></big><br>' in title: self.list.scrollToTop() elif '</b></big><br>' in title: # Select the next previous row, the one following is a title self.select_row(-2) else: self.select_row(-1) def next_row(self): """Select next row in list widget.""" if self.mode == self.SYMBOL_MODE: self.select_row(+1) return next_row = self.current_row() + 1 if next_row < self.count(): if '</b></big><br>' in self.list.item(next_row).text(): # Select the next next row, the one following is a title self.select_row(+2) else: self.select_row(+1) def get_stack_index(self, stack_index, plugin_index): """Get the real index of the selected item.""" other_plugins_count = sum([other_tabs[0].count() \ for other_tabs in \ self.plugins_tabs[:plugin_index]]) real_index = stack_index - other_plugins_count return real_index # --- Helper methods: Widget def get_widget(self, index=None, path=None, tabs=None): """Get widget by index. If no tabs and index specified the current active widget is returned. """ if index and tabs: return tabs.widget(index) elif path and tabs: return tabs.widget(index) elif self.plugin: index = self.plugins_instances.index(self.plugin) return self.plugins_tabs[index][0].currentWidget() else: return self.plugins_tabs[0][0].currentWidget() def set_editor_cursor(self, editor, cursor): """Set the cursor of an editor.""" pos = cursor.position() anchor = cursor.anchor() new_cursor = QTextCursor() if pos == anchor: new_cursor.movePosition(pos) else: new_cursor.movePosition(anchor) new_cursor.movePosition(pos, QTextCursor.KeepAnchor) editor.setTextCursor(cursor) def goto_line(self, line_number): """Go to specified line number in current active editor.""" if line_number: line_number = int(line_number) editor = self.get_widget() try: editor.go_to_line(min(line_number, editor.get_line_count())) except AttributeError: pass # --- Helper methods: Outline explorer def get_symbol_list(self): """Get the list of symbols present in the file.""" try: oedata = self.get_widget().get_outlineexplorer_data() except AttributeError: oedata = {} return oedata # --- Handlers def item_selection_changed(self): """List widget item selection change handler.""" row = self.current_row() if self.count() and row >= 0: if '</b></big><br>' in self.list.currentItem().text() and row == 0: self.next_row() if self.mode == self.FILE_MODE: try: stack_index = self.paths.index(self.filtered_path[row]) self.plugin = self.widgets[stack_index][1] plugin_index = self.plugins_instances.index(self.plugin) # Count the real index in the tabWidget of the # current plugin real_index = self.get_stack_index(stack_index, plugin_index) self.sig_goto_file.emit(real_index, self.plugin.get_current_tab_manager()) self.goto_line(self.line_number) try: self.plugin.switch_to_plugin() self.raise_() except AttributeError: # The widget using the fileswitcher is not a plugin pass self.edit.setFocus() except ValueError: pass else: line_number = self.filtered_symbol_lines[row] self.goto_line(line_number) def setup_file_list(self, filter_text, current_path): """Setup list widget content for file list display.""" short_paths = shorten_paths(self.paths, self.save_status) paths = self.paths icons = self.icons results = [] trying_for_line_number = ':' in filter_text # Get optional line number if trying_for_line_number: filter_text, line_number = filter_text.split(':') # Get all the available filenames scores = get_search_scores('', self.filenames, template="<b>{0}</b>") else: line_number = None # Get all available filenames and get the scores for # "fuzzy" matching scores = get_search_scores(filter_text, self.filenames, template="<b>{0}</b>") # Get max width to determine if shortpaths should be used max_width = self.get_item_size(paths)[0] self.fix_size(paths) # Build the text that will appear on the list widget for index, score in enumerate(scores): text, rich_text, score_value = score if score_value != -1: text_item = '<big>' + rich_text.replace('&', '') + '</big>' if trying_for_line_number: text_item += " [{0:} {1:}]".format(self.line_count[index], _("lines")) if max_width > self.list.width(): text_item += u"<br><i>{0:}</i>".format(short_paths[index]) else: text_item += u"<br><i>{0:}</i>".format(paths[index]) if (trying_for_line_number and self.line_count[index] != 0 or not trying_for_line_number): results.append((score_value, index, text_item)) # Sort the obtained scores and populate the list widget self.filtered_path = [] plugin = None for result in sorted(results): index = result[1] path = paths[index] icon = icons[index] text = '' try: title = self.widgets[index][1].get_plugin_title().split(' - ') if plugin != title[0]: plugin = title[0] text += '<br><big><b>' + plugin + '</b></big><br>' item = QListWidgetItem(text) item.setToolTip(path) item.setSizeHint(QSize(0, 25)) item.setFlags(Qt.ItemIsEditable) self.list.addItem(item) self.filtered_path.append(path) except: # The widget using the fileswitcher is not a plugin pass text = '' text += result[-1] item = QListWidgetItem(icon, text) item.setToolTip(path) item.setSizeHint(QSize(0, 25)) self.list.addItem(item) self.filtered_path.append(path) # To adjust the delegate layout for KDE themes self.list.files_list = True # Move selected item in list accordingly and update list size if current_path in self.filtered_path: self.set_current_row(self.filtered_path.index(current_path)) elif self.filtered_path: self.set_current_row(0) # If a line number is searched look for it self.line_number = line_number self.goto_line(line_number) def setup_symbol_list(self, filter_text, current_path): """Setup list widget content for symbol list display.""" # Get optional symbol name filter_text, symbol_text = filter_text.split('@') # Fetch the Outline explorer data, get the icons and values oedata = self.get_symbol_list() icons = get_python_symbol_icons(oedata) # The list of paths here is needed in order to have the same # point of measurement for the list widget size as in the file list # See issue 4648 paths = self.paths # Update list size self.fix_size(paths) symbol_list = process_python_symbol_data(oedata) line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list] choices = [item[1] for item in symbol_list] scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>") # Build the text that will appear on the list widget results = [] lines = [] self.filtered_symbol_lines = [] for index, score in enumerate(scores): text, rich_text, score_value = score line, fold_level, token = line_fold_token[index] lines.append(text) if score_value != -1: results.append((score_value, line, text, rich_text, fold_level, icons[index], token)) template = '{0}{1}' for (score, line, text, rich_text, fold_level, icon, token) in sorted(results): fold_space = ' '*(fold_level) line_number = line + 1 self.filtered_symbol_lines.append(line_number) textline = template.format(fold_space, rich_text) item = QListWidgetItem(icon, textline) item.setSizeHint(QSize(0, 16)) self.list.addItem(item) # To adjust the delegate layout for KDE themes self.list.files_list = False # Move selected item in list accordingly # NOTE: Doing this is causing two problems: # 1. It makes the cursor to auto-jump to the last selected # symbol after opening or closing a different file # 2. It moves the cursor to the first symbol by default, # which is very distracting. # That's why this line is commented! # self.set_current_row(0) def setup(self): """Setup list widget content.""" if len(self.plugins_tabs) == 0: self.close() return self.list.clear() current_path = self.current_path filter_text = self.filter_text # Get optional line or symbol to define mode and method handler trying_for_symbol = ('@' in self.filter_text) if trying_for_symbol: self.mode = self.SYMBOL_MODE self.setup_symbol_list(filter_text, current_path) else: self.mode = self.FILE_MODE self.setup_file_list(filter_text, current_path) # Set position according to size self.set_dialog_position() def add_plugin(self, plugin, tabs, data, icon): """Add a plugin to display its files.""" self.plugins_tabs.append((tabs, plugin)) self.plugins_data.append((data, icon)) self.plugins_instances.append(plugin)
class PreferencesDialog(QDialog): """Preferences Dialog for Napari user settings.""" ui_schema = { "call_order": { "ui:widget": "plugins" }, } resized = Signal(QSize) closed = Signal() def __init__(self, parent=None): super().__init__(parent) self._list = QListWidget(self) self._stack = QStackedWidget(self) self._list.setObjectName("Preferences") # Set up buttons self._button_cancel = QPushButton(trans._("Cancel")) self._button_ok = QPushButton(trans._("OK")) self._default_restore = QPushButton(trans._("Restore defaults")) # Setup self.setWindowTitle(trans._("Preferences")) # Layout left_layout = QVBoxLayout() left_layout.addWidget(self._list) left_layout.addStretch() left_layout.addWidget(self._default_restore) left_layout.addWidget(self._button_cancel) left_layout.addWidget(self._button_ok) main_layout = QHBoxLayout() main_layout.addLayout(left_layout, 1) main_layout.addWidget(self._stack, 3) self.setLayout(main_layout) # Signals self._list.currentRowChanged.connect( lambda index: self._stack.setCurrentIndex(index)) self._button_cancel.clicked.connect(self.on_click_cancel) self._button_ok.clicked.connect(self.on_click_ok) self._default_restore.clicked.connect(self.restore_defaults) # Make widget self.make_dialog() self._list.setCurrentRow(0) def closeEvent(self, event): """Override to emit signal.""" self.closed.emit() super().closeEvent(event) def reject(self): """Override to handle Escape.""" super().reject() self.close() def resizeEvent(self, event): """Override to emit signal.""" self.resized.emit(event.size()) super().resizeEvent(event) def make_dialog(self): """Removes settings not to be exposed to user and creates dialog pages.""" # Because there are multiple pages, need to keep a dictionary of values dicts. # One set of keywords are for each page, then in each entry for a page, there are dicts # of setting and its value. self._values_orig_dict = {} self._values_dict = {} self._setting_changed_dict = {} for page, setting in SETTINGS.schemas().items(): schema, values, properties = self.get_page_dict(setting) self._setting_changed_dict[page] = {} self._values_orig_dict[page] = values self._values_dict[page] = values # Only add pages if there are any properties to add. if properties: self.add_page(schema, values) def get_page_dict(self, setting): """Provides the schema, set of values for each setting, and the properties for each setting. Parameters ---------- setting : dict Dictionary of settings for a page within the settings manager. Returns ------- schema : dict Json schema of the setting page. values : dict Dictionary of values currently set for each parameter in the settings. properties : dict Dictionary of properties within the json schema. """ schema = json.loads(setting['json_schema']) # Need to remove certain properties that will not be displayed on the GUI properties = schema.pop('properties') model = setting['model'] values = model.dict() napari_config = getattr(model, "NapariConfig", None) if napari_config is not None: for val in napari_config.preferences_exclude: properties.pop(val) values.pop(val) schema['properties'] = properties return schema, values, properties def restore_defaults(self): """Launches dialog to confirm restore settings choice.""" widget = ConfirmDialog( parent=self, text=trans._("Are you sure you want to restore default settings?"), ) widget.valueChanged.connect(self._reset_widgets) widget.exec_() def _reset_widgets(self): """Deletes the widgets and rebuilds with defaults.""" self.close() self._list.clear() for n in range(self._stack.count()): widget = self._stack.removeWidget(self._stack.currentWidget()) del widget self.make_dialog() self._list.setCurrentRow(0) self.show() def on_click_ok(self): """Keeps the selected preferences saved to SETTINGS.""" self.close() def on_click_cancel(self): """Restores the settings in place when dialog was launched.""" # Need to check differences for each page. for n in range(self._stack.count()): # Must set the current row so that the proper list is updated # in check differences. self._list.setCurrentRow(n) page = self._list.currentItem().text().split(" ")[0].lower() # get new values for settings. If they were changed from values at beginning # of preference dialog session, change them back. # Using the settings value seems to be the best way to get the checkboxes right # on the plugin call order widget. setting = SETTINGS.schemas()[page] schema, new_values, properties = self.get_page_dict(setting) self.check_differences(self._values_orig_dict[page], new_values) self._list.setCurrentRow(0) self.close() def add_page(self, schema, values): """Creates a new page for each section in dialog. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ widget = self.build_page_dialog(schema, values) self._list.addItem(schema["title"]) self._stack.addWidget(widget) def build_page_dialog(self, schema, values): """Builds the preferences widget using the json schema builder. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ builder = WidgetBuilder() form = builder.create_form(schema, self.ui_schema) # set state values for widget form.widget.state = values form.widget.on_changed.connect(lambda d: self.check_differences( d, self._values_dict[schema["title"].lower()], )) return form def _values_changed(self, page, new_dict, old_dict): """Loops through each setting in a page to determine if it changed. Parameters ---------- new_dict : dict Dict that has the most recent changes by user. Each key is a setting value and each item is the value. old_dict : dict Dict wtih values set at the begining of preferences dialog session. """ for setting_name, value in new_dict.items(): if value != old_dict[setting_name]: self._setting_changed_dict[page][setting_name] = value elif (value == old_dict[setting_name] and setting_name in self._setting_changed_dict[page]): self._setting_changed_dict[page].pop(setting_name) def check_differences(self, new_dict, old_dict): """Changes settings in settings manager with changes from dialog. Parameters ---------- new_dict : dict Dict that has the most recent changes by user. Each key is a setting parameter and each item is the value. old_dict : dict Dict wtih values set at the beginning of the preferences dialog session. """ page = self._list.currentItem().text().split(" ")[0].lower() self._values_changed(page, new_dict, old_dict) different_values = self._setting_changed_dict[page] if len(different_values) > 0: # change the values in SETTINGS for setting_name, value in different_values.items(): try: setattr(SETTINGS._settings[page], setting_name, value) self._values_dict[page] = new_dict except: # noqa: E722 continue
class PMGListCtrl(BaseExtendedWidget): def __init__(self, layout_dir: str, title: str, initial_value: List[List[str]], new_id_func: Callable = None): super().__init__(layout_dir) self.choices = [] self.text_list = [] lab_title = QLabel(text=title) layout = QHBoxLayout() self.central_layout.addWidget(lab_title) self.on_check_callback = None self.list_widget = QListWidget() self.list_widget.mouseDoubleClickEvent = self.on_listwidget_double_cicked self.set_value(initial_value) layout_tools = QVBoxLayout() self.button_add_item = QPushButton('+') self.button_delete_item = QPushButton('-') self.button_delete_item.clicked.connect(self.delete_row) self.button_add_item.clicked.connect(self.add_row) self.button_add_item.setMaximumWidth(20) self.button_delete_item.setMaximumWidth(20) layout_tools.addWidget(self.button_add_item) layout_tools.addWidget(self.button_delete_item) layout.addLayout(layout_tools) layout.addWidget(self.list_widget) self.central_layout.addLayout(layout) self.data = initial_value self.new_id_func = new_id_func self.text_edit = QLineEdit(parent=self.list_widget) self.text_edit.setWindowFlags(self.text_edit.windowFlags() | Qt.Dialog | Qt.FramelessWindowHint) self.text_edit.hide() self.completer = QCompleter() self.text_edit.setCompleter(self.completer) self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) def set_completions(self, completions: List[str]): """ 设置补全内容 Args: completions: Returns: """ self.completer.setModel(QStringListModel(completions)) def new_id(self): if callable(self.new_id_func): return self.new_id_func() else: return None def add_row(self): self.data = self.get_value() self.data[0].append(self.new_id()) self.data[1].append('Unnamed') self.list_widget.addItem(QListWidgetItem('Unnamed')) def delete_row(self): index = self.list_widget.currentIndex().row() self.data[0].pop(index) self.data[1].pop(index) self.list_widget.takeItem(index) def on_listwidget_double_cicked(self, evt: QMouseEvent): print('edit', evt) pos = evt.globalPos() current_item: QListWidgetItem = self.list_widget.currentItem() def set_value(): current_item.setText(self.text_edit.text()) self.text_edit.hide() self.text_edit.returnPressed.disconnect(set_value) item: QListWidgetItem = self.list_widget.currentItem() self.text_edit.setGeometry(pos.x(), pos.y(), 200, 20) self.text_edit.returnPressed.connect(set_value) self.text_edit.show() # self.list_widget.editItem(item) def get_value(self): text = [] for i in range(self.list_widget.count()): text.append(self.list_widget.item(i).text()) self.data[1] = text assert len(self.data[1]) == len(self.data[0]), repr(self.data) return self.data def set_value(self, data: List[List[str]]): self.list_widget.clear() self.list_widget.addItems(data[1]) self.data = data for index in range(self.list_widget.count()): item: QListWidgetItem = self.list_widget.item(index)
class CycleWindow(SiriusMainWindow): """Power supplies cycle window.""" def __init__(self, parent=None, checked_accs=(), adv_mode=False): """Constructor.""" super().__init__(parent) self.setObjectName('ASApp') cor = get_appropriate_color(section='AS') self.setWindowIcon(qta.icon('mdi.recycle', color=cor)) self._is_adv_mode = adv_mode # Data structs self._psnames = get_psnames(isadv=self._is_adv_mode) self._timing = Timing() self._ps2cycle = list() self._ps_ready = list() self._ps_failed = list() self._checked_accs = checked_accs # Flags self._is_preparing = '' self._prepared_init_vals = { 'timing': False, 'ps_sofbmode': False, 'ps_om_slowref': False, 'ps_current': False, 'ps_params': False, 'ps_om_cycle': False, 'trims': True } self._prepared = self._prepared_init_vals.copy() self._icon_check = qta.icon('fa5s.check') self._pixmap_check = self._icon_check.pixmap( self._icon_check.actualSize(QSize(16, 16))) self._icon_not = qta.icon('fa5s.times') self._pixmap_not = self._icon_not.pixmap( self._icon_not.actualSize(QSize(16, 16))) # Tasks self._step_2_task = { 'save_timing': SaveTiming, 'timing': PrepareTiming, 'ps_sofbmode': PreparePSSOFBMode, 'ps_om_slowref': PreparePSOpModeSlowRef, 'ps_current': PreparePSCurrentZero, 'ps_params': PreparePSParams, 'ps_om_cycle': PreparePSOpModeCycle, 'trims': CycleTrims, 'cycle': Cycle, 'restore_timing': RestoreTiming, } # Setup UI self._needs_update_setup = False self._setup_ui() self._update_setup_timer = QTimer(self) self._update_setup_timer.timeout.connect(self._update_setup) self._update_setup_timer.setInterval(250) self._update_setup_timer.start() self.setWindowTitle('PS Cycle') def _setup_ui(self): # central widget self.central_widget = QWidget() self.setCentralWidget(self.central_widget) # tree gb_tree = QGroupBox('Select power supplies:') self.pwrsupplies_tree = PVNameTree(self._psnames, ('sec', 'mag_group'), tuple(), self) self.pwrsupplies_tree.tree.setHeaderHidden(True) self.pwrsupplies_tree.tree.setColumnCount(1) glay_tree = QVBoxLayout(gb_tree) glay_tree.addWidget(self.pwrsupplies_tree) # commands lb_prep_ti = QLabel('<h4>Prepare Timing</h4>', self, alignment=Qt.AlignCenter) ti_ch = [ PVName(name).substitute(prefix=VACA_PREFIX) for name in self._timing.get_pvnames_by_psnames() ] self.ticonn_led = PyDMLedMultiConn(self, channels=ti_ch) self.save_timing_bt = QPushButton('1. Save Timing Initial State', self) self.save_timing_bt.setToolTip( 'Save timing current state as initial state.') self.save_timing_bt.clicked.connect( _part(self._run_task, 'save_timing')) self.save_timing_bt.clicked.connect(self._set_lastcomm) self.prepare_timing_bt = QPushButton('2. Prepare Timing', self) self.prepare_timing_bt.setToolTip('Prepare EVG, triggers and events') self.prepare_timing_bt.clicked.connect(_part(self._run_task, 'timing')) self.prepare_timing_bt.clicked.connect(self._set_lastcomm) self.prepare_timing_lb = QLabel(self) self.prepare_timing_lb.setPixmap(self._pixmap_not) lb_prep_ps = QLabel('<h4>Prepare PS</h4>', self, alignment=Qt.AlignCenter) self.psconn_led = PyDMLedMultiConn(self) self.set_ps_sofbmode_off_bt = QPushButton('3. Turn off PS SOFBMode', self) self.set_ps_sofbmode_off_bt.setToolTip( 'Turn off power supplies SOFBMode.') self.set_ps_sofbmode_off_bt.clicked.connect( _part(self._run_task, 'ps_sofbmode')) self.set_ps_sofbmode_off_bt.clicked.connect(self._set_lastcomm) self.set_ps_sofbmode_off_lb = QLabel(self) self.set_ps_sofbmode_off_lb.setPixmap(self._pixmap_not) self.set_ps_opmode_slowref_bt = QPushButton( '4. Set PS OpMode to SlowRef', self) self.set_ps_opmode_slowref_bt.setToolTip( 'Set power supplies OpMode to SlowRef.') self.set_ps_opmode_slowref_bt.clicked.connect( _part(self._run_task, 'ps_om_slowref')) self.set_ps_opmode_slowref_bt.clicked.connect(self._set_lastcomm) self.set_ps_opmode_slowref_lb = QLabel(self) self.set_ps_opmode_slowref_lb.setPixmap(self._pixmap_not) self.set_ps_current_zero_bt = QPushButton('5. Set PS current to zero', self) self.set_ps_current_zero_bt.setToolTip( 'Set power supplies current to zero.') self.set_ps_current_zero_bt.clicked.connect( _part(self._run_task, 'ps_current')) self.set_ps_current_zero_bt.clicked.connect(self._set_lastcomm) self.set_ps_current_zero_lb = QLabel(self) self.set_ps_current_zero_lb.setPixmap(self._pixmap_not) self.prepare_ps_params_bt = QPushButton('6. Prepare PS Parameters', self) self.prepare_ps_params_bt.setToolTip( 'Check power supplies OpMode in SlowRef, check\n' 'current is zero and configure cycle parameters.') self.prepare_ps_params_bt.clicked.connect( _part(self._run_task, 'ps_params')) self.prepare_ps_params_bt.clicked.connect(self._set_lastcomm) self.prepare_ps_params_lb = QLabel(self) self.prepare_ps_params_lb.setPixmap(self._pixmap_not) self.prepare_ps_opmode_bt = QPushButton('7. Prepare PS OpMode', self) self.prepare_ps_opmode_bt.setToolTip( 'Set power supplies OpMode to Cycle.') self.prepare_ps_opmode_bt.clicked.connect( _part(self._run_task, 'ps_om_cycle')) self.prepare_ps_opmode_bt.clicked.connect(self._set_lastcomm) self.prepare_ps_opmode_lb = QLabel(self) self.prepare_ps_opmode_lb.setPixmap(self._pixmap_not) lb_cycle = QLabel('<h4>Cycle</h4>', self, alignment=Qt.AlignCenter) self.cycle_trims_bt = QPushButton('8. Cycle Trims', self) self.cycle_trims_bt.setToolTip( 'Cycle trims:\nStep 1) CH, QS and QTrims\nStep 2) CV') self.cycle_trims_bt.clicked.connect(_part(self._run_task, 'trims')) self.cycle_trims_bt.clicked.connect(self._set_lastcomm) self.cycle_trims_bt.setVisible(False) self.cycle_trims_lb = QLabel(self) self.cycle_trims_lb.setPixmap(self._pixmap_check) self.cycle_trims_lb.setVisible(False) self.cycle_bt = QPushButton('8. Cycle', self) self.cycle_bt.setToolTip( 'Check all configurations,\nenable triggers and run cycle.') self.cycle_bt.clicked.connect(_part(self._run_task, 'cycle')) self.cycle_bt.clicked.connect(self._set_lastcomm) self.cycle_bt.setEnabled(False) lb_rest_ti = QLabel('<h4>Restore Timing</h4>', self, alignment=Qt.AlignCenter) self.restore_timing_bt = QPushButton('9. Restore Timing Initial State', self) self.restore_timing_bt.setToolTip('Restore timing initial state.') self.restore_timing_bt.clicked.connect( _part(self._run_task, 'restore_timing')) self.restore_timing_bt.clicked.connect(self._set_lastcomm) self._prepared_labels = { 'timing': self.prepare_timing_lb, 'ps_sofbmode': self.set_ps_sofbmode_off_lb, 'ps_om_slowref': self.set_ps_opmode_slowref_lb, 'ps_current': self.set_ps_current_zero_lb, 'ps_params': self.prepare_ps_params_lb, 'ps_om_cycle': self.prepare_ps_opmode_lb, 'trims': self.cycle_trims_lb } gb_commsts = QGroupBox() gb_commsts.setStyleSheet(""" QPushButton{min-height:1.5em;} QLabel{qproperty-alignment: AlignCenter;}""") lay_commsts = QGridLayout(gb_commsts) lay_commsts.addItem( QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 0, 0, 1, 2) lay_commsts.addWidget(lb_prep_ti, 1, 0) lay_commsts.addWidget(self.ticonn_led, 1, 1) lay_commsts.addWidget(self.save_timing_bt, 2, 0) lay_commsts.addWidget(self.prepare_timing_bt, 3, 0) lay_commsts.addWidget(self.prepare_timing_lb, 3, 1) lay_commsts.addItem( QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 4, 0) lay_commsts.addWidget(lb_prep_ps, 5, 0) lay_commsts.addWidget(self.psconn_led, 5, 1) lay_commsts.addWidget(self.set_ps_sofbmode_off_bt, 6, 0) lay_commsts.addWidget(self.set_ps_sofbmode_off_lb, 6, 1) lay_commsts.addWidget(self.set_ps_opmode_slowref_bt, 7, 0) lay_commsts.addWidget(self.set_ps_opmode_slowref_lb, 7, 1) lay_commsts.addWidget(self.set_ps_current_zero_bt, 8, 0) lay_commsts.addWidget(self.set_ps_current_zero_lb, 8, 1) lay_commsts.addWidget(self.prepare_ps_params_bt, 9, 0) lay_commsts.addWidget(self.prepare_ps_params_lb, 9, 1) lay_commsts.addWidget(self.prepare_ps_opmode_bt, 10, 0) lay_commsts.addWidget(self.prepare_ps_opmode_lb, 10, 1) lay_commsts.addItem( QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 11, 0) lay_commsts.addWidget(lb_cycle, 12, 0) lay_commsts.addWidget(self.cycle_trims_bt, 13, 0) lay_commsts.addWidget(self.cycle_trims_lb, 13, 1) lay_commsts.addWidget(self.cycle_bt, 14, 0) lay_commsts.addItem( QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 15, 0) lay_commsts.addWidget(lb_rest_ti, 16, 0) lay_commsts.addWidget(self.restore_timing_bt, 17, 0) lay_commsts.addItem( QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 18, 0) lay_commsts.setColumnStretch(0, 10) lay_commsts.setColumnStretch(1, 1) lay_commsts.setVerticalSpacing(12) lay_commsts.setHorizontalSpacing(6) self.label_lastcomm = QLabel('Last Command: ', self) self.clearhist_bt = QPushButton('Clear', self) self.clearhist_bt.clicked.connect(self._clear_lastcomm) lay_lc = QHBoxLayout() lay_lc.setContentsMargins(0, 0, 0, 0) lay_lc.addWidget(self.label_lastcomm, alignment=Qt.AlignLeft) lay_lc.addWidget(self.clearhist_bt, alignment=Qt.AlignRight) lay_lc.setStretch(0, 10) lay_lc.setStretch(1, 1) self.progress_list = QListWidget(self) self.progress_list.setObjectName('progresslist') self.progress_list.setStyleSheet('#progresslist{min-width:20em;}') self.progress_list.itemDoubleClicked.connect(self._open_ps_detail) self.progress_list.setSelectionMode(QAbstractItemView.MultiSelection) self.progress_list.setToolTip( 'Select rows and press Ctrl+C to copy and Esc to deselect.') self.progress_bar = MyProgressBar(self) lay_log = QVBoxLayout() lay_log.addLayout(lay_lc) lay_log.addWidget(self.progress_list) lay_log.addWidget(self.progress_bar) # connect tree signals self.pwrsupplies_tree.tree.doubleClicked.connect(self._open_ps_detail) self.pwrsupplies_tree.tree.itemChanged.connect( self._handle_checked_items_changed) self.pwrsupplies_tree.check_requested_levels(self._checked_accs) # layout layout = QGridLayout() layout.setVerticalSpacing(10) layout.setHorizontalSpacing(10) layout.addWidget( QLabel('<h3>PS Cycle</h3>', self, alignment=Qt.AlignCenter), 0, 0, 1, 3) layout.addWidget(gb_tree, 1, 0) layout.addWidget(gb_commsts, 1, 1) layout.addLayout(lay_log, 1, 2) layout.setRowStretch(0, 1) layout.setRowStretch(1, 15) layout.setColumnStretch(0, 5) layout.setColumnStretch(1, 4) layout.setColumnStretch(2, 8) self.central_widget.setLayout(layout) # --- handle tasks --- def _run_task(self, control=''): if not self._check_connected(control): return pwrsupplies = self._get_ps_list() if not pwrsupplies: return if 'ps' in control and not self._verify_ps(pwrsupplies): return if control in self._step_2_task: task_class = self._step_2_task[control] else: raise NotImplementedError( "Task not defined for control '{}'".format(control)) self._is_preparing = control self._handle_buttons_enabled(False) self.progress_list.clear() task = task_class(parent=self, psnames=pwrsupplies, timing=self._timing, isadv=self._is_adv_mode) task.updated.connect(self._update_progress) duration = task.duration() self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(duration) self.progress_bar.setValue(0) pal = self.progress_bar.palette() pal.setColor(QPalette.Highlight, self.progress_bar.default_color) self.progress_bar.setPalette(pal) self.update_bar = UpdateProgressBar(duration, self) self.update_bar.increment.connect(self.progress_bar.increment) task.start() self.update_bar.start() def _update_progress(self, text, done, warning=False, error=False): """Update automated cycle progress list and bar.""" if done: last_item = self.progress_list.item(self.progress_list.count() - 1) curr_text = last_item.text() last_item.setText(curr_text + ' done.') elif 'Remaining time' in text: last_item = self.progress_list.item(self.progress_list.count() - 1) if 'Remaining time' in last_item.text(): last_item.setText(text) else: self.progress_list.addItem(text) self.progress_list.scrollToBottom() elif 'Sent ' in text: last_item = self.progress_list.item(self.progress_list.count() - 1) if 'Sent ' in last_item.text(): last_item.setText(text) else: self.progress_list.addItem(text) self.progress_list.scrollToBottom() elif 'Successfully checked ' in text: last_item = self.progress_list.item(self.progress_list.count() - 1) if 'Successfully checked ' in last_item.text(): last_item.setText(text) else: self.progress_list.addItem(text) self.progress_list.scrollToBottom() elif 'Created connections ' in text: last_item = self.progress_list.item(self.progress_list.count() - 1) if 'Created connections ' in last_item.text(): last_item.setText(text) else: self.progress_list.addItem(text) self.progress_list.scrollToBottom() else: item = QListWidgetItem(text) if error: item.setForeground(errorcolor) self.update_bar.exit_task() pal = self.progress_bar.palette() pal.setColor(QPalette.Highlight, self.progress_bar.warning_color) self.progress_bar.setPalette(pal) if self._is_preparing in self._prepared.keys(): self._prepared[self._is_preparing] = False cycle = all(self._prepared.values()) self._handle_buttons_enabled(True, cycle=cycle) elif warning: item.setForeground(warncolor) elif 'finished' in text: self.update_bar.exit_task() self.progress_bar.setValue(self.progress_bar.maximum()) if self._is_preparing == 'cycle': self._prepared = {k: False for k in self._prepared.keys()} if not self.cycle_trims_bt.isVisible(): self._prepared['trims'] = True cycle = False else: if self._is_preparing in self._prepared.keys(): self._prepared[self._is_preparing] = True cycle = all(self._prepared.values()) self._handle_buttons_enabled(True, cycle=cycle) self._handle_stslabels_content() self.progress_list.addItem(item) self.progress_list.scrollToBottom() def _handle_buttons_enabled(self, enable, cycle=False): self.save_timing_bt.setEnabled(enable) self.prepare_timing_bt.setEnabled(enable) self.set_ps_sofbmode_off_bt.setEnabled(enable) self.set_ps_opmode_slowref_bt.setEnabled(enable) self.set_ps_current_zero_bt.setEnabled(enable) self.prepare_ps_params_bt.setEnabled(enable) self.prepare_ps_opmode_bt.setEnabled(enable) self.cycle_trims_bt.setEnabled(enable) self.cycle_bt.setEnabled(cycle) self.restore_timing_bt.setEnabled(enable) self.clearhist_bt.setEnabled(enable) self.pwrsupplies_tree.setEnabled(enable) def _handle_stslabels_content(self): for prep, value in self._prepared.items(): pixmap = self._pixmap_check if value else self._pixmap_not self._prepared_labels[prep].setPixmap(pixmap) def _set_lastcomm(self): sender_text = self.sender().text() self.label_lastcomm.setText('Last Command: ' + sender_text) def _clear_lastcomm(self): self.progress_bar.setValue(0) self.progress_list.clear() self.label_lastcomm.setText('Last Command: ') # --- handle ps selection --- def _get_ps_list(self): """Return list of power supplies to cycle.""" # Get power supplies list pwrsupplies = self.pwrsupplies_tree.checked_items() if not pwrsupplies: QMessageBox.critical(self, 'Message', 'No power supply selected!') return False sections = get_sections(pwrsupplies) if 'BO' in sections and len(sections) > 1: QMessageBox.critical(self, 'Error', 'Can not cycle Booster with other sectors!') return False create_task = CreateCyclers(parent=self, psnames=pwrsupplies) dlg = ProgressDialog('Creating cycles...', create_task, self) ret = dlg.exec_() if ret == dlg.Rejected: return False return pwrsupplies def _handle_checked_items_changed(self, item): psname = PVName(item.data(0, Qt.DisplayRole)) if not _re.match('.*-.*:.*-.*', psname): return if not self._is_adv_mode and psname.sec == 'SI' and \ not psname.dev.startswith('FC'): psname2check = Filter.process_filters(self._psnames, filters={ 'sec': 'SI', 'dev': '(?!FC)' }) psname2check.remove(psname) state2set = item.checkState(0) self.pwrsupplies_tree.tree.blockSignals(True) for psn in psname2check: item2check = self.pwrsupplies_tree._item_map[psn] if item2check.checkState(0) != state2set: item2check.setData(0, Qt.CheckStateRole, state2set) self.pwrsupplies_tree.tree.blockSignals(False) else: if (psname.sec in ['BO', 'SI'] and psname.dev in ['B', 'B1B2']): psname2check = PSSearch.get_psnames({ 'sec': psname.sec, 'dev': 'B.*' }) psname2check.remove(psname) item2check = self.pwrsupplies_tree._item_map[psname2check[0]] state2set = item.checkState(0) state2change = item2check.checkState(0) if state2change != state2set: self.pwrsupplies_tree.tree.blockSignals(True) item2check.setData(0, Qt.CheckStateRole, state2set) self.pwrsupplies_tree.tree.blockSignals(False) self._prepared.update(self._prepared_init_vals) self._needs_update_setup = True def _update_setup(self): if not self._needs_update_setup: return self._needs_update_setup = False # update leds psnames = self.pwrsupplies_tree.checked_items() ti_ch = [ PVName(name).substitute(prefix=VACA_PREFIX) for name in self._timing.get_pvnames_by_psnames(psnames) ] self.ticonn_led.set_channels(ti_ch) ps_ch = list() for name in psnames: ps_ch.append( PVName(name).substitute(prefix=VACA_PREFIX, propty='PwrState-Sts')) self.psconn_led.set_channels(ps_ch) # update buttons and self._prepared dict if not in advanced mode if not self._is_adv_mode: has_si = False for psn in PSSearch.get_psnames({'sec': 'SI', 'dis': 'PS'}): if psn not in self.pwrsupplies_tree._item_map: continue item = self.pwrsupplies_tree._item_map[psn] has_si |= item.checkState(0) != 0 if not has_si: self.cycle_bt.setText('8. Cycle') self.restore_timing_bt.setText( '9. Restore Timing Initial State') self.cycle_trims_bt.setVisible(False) self.cycle_trims_lb.setVisible(False) self._prepared['trims'] = True else: self.cycle_bt.setText('9. Cycle') self.restore_timing_bt.setText( '10. Restore Timing Initial State') self.cycle_trims_bt.setVisible(True) self.cycle_trims_lb.setVisible(True) self._prepared['trims'] = False self._handle_stslabels_content() self._handle_buttons_enabled(True) # --- auxiliary checks --- def _check_connected(self, control): if control in ['trims', 'cycle']: leds = [self.ticonn_led, self.psconn_led] elif 'timing' in control: leds = [ self.ticonn_led, ] else: leds = [ self.psconn_led, ] for led in leds: pvs_disconnected = set() for ch, v in led.channels2conn.items(): if not v: pvs_disconnected.add(ch) if pvs_disconnected: sttr = '' for item in pvs_disconnected: sttr += item + '\n' QMessageBox.information( self, 'Message', 'The following PVs are not connected:\n' + sttr) return False return True def _verify_ps(self, pwrsupplies): self._ps_failed = set() task = VerifyPS(parent=self, psnames=pwrsupplies) task.itemDone.connect(self._get_ps_not_ready_2_cycle) dlg = ProgressDialog('Verifying power supplies initial state...', task, self) ret = dlg.exec_() if ret == dlg.Rejected: self._handle_buttons_enabled(True) return False if self._ps_failed: text = 'Verify power state and interlocks' \ ' of the following power supplies' dlg = PSStatusDialog(self._ps_failed, text, self) dlg.exec_() self._handle_buttons_enabled(True) return False return True def _get_ps_not_ready_2_cycle(self, psname, status): if not status: self._ps_failed.add(psname) def _open_ps_detail(self, item): if self.sender() == self.progress_list: text_split = item.data(Qt.DisplayRole).split(' ') psname = '' for text in text_split: if _re.match('.*-.*:.*-.*', text): psname = text if not psname: return else: psname = item.data() if not _re.match('.*-.*:.*-.*', psname): if item.model().rowCount(item) == 1: psname = item.child(0, 0).data() else: return run_newprocess(['sirius-hla-as-ps-detail.py', psname]) # --- events --- def keyPressEvent(self, evt): """Implement keyPressEvent.""" if evt.matches(QKeySequence.Copy) and self.progress_list.underMouse(): items = self.progress_list.selectedItems() items = '\n'.join([i.text() for i in items]) QApplication.clipboard().setText(items) if evt.key() == Qt.Key_Escape and self.progress_list.underMouse(): items = self.progress_list.clearSelection() super().keyPressEvent(evt) def closeEvent(self, ev): self._update_setup_timer.stop() super().closeEvent(ev)
class PathManager(QDialog): redirect_stdio = Signal(bool) def __init__(self, parent=None, pathlist=None, ro_pathlist=None, not_active_pathlist=None, sync=True): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) assert isinstance(pathlist, list) self.pathlist = pathlist if not_active_pathlist is None: not_active_pathlist = [] self.not_active_pathlist = not_active_pathlist if ro_pathlist is None: ro_pathlist = [] self.ro_pathlist = ro_pathlist self.last_path = getcwd() self.setWindowTitle(_("PYTHONPATH manager")) self.setWindowIcon(ima.icon('pythonpath')) self.resize(500, 300) self.selection_widgets = [] layout = QVBoxLayout() self.setLayout(layout) top_layout = QHBoxLayout() layout.addLayout(top_layout) self.toolbar_widgets1 = self.setup_top_toolbar(top_layout) self.listwidget = QListWidget(self) self.listwidget.currentRowChanged.connect(self.refresh) self.listwidget.itemChanged.connect(self.update_not_active_pathlist) layout.addWidget(self.listwidget) bottom_layout = QHBoxLayout() layout.addLayout(bottom_layout) self.sync_button = None self.toolbar_widgets2 = self.setup_bottom_toolbar(bottom_layout, sync) # Buttons configuration bbox = QDialogButtonBox(QDialogButtonBox.Close) bbox.rejected.connect(self.reject) bottom_layout.addWidget(bbox) self.update_list() self.refresh() @property def active_pathlist(self): return [path for path in self.pathlist if path not in self.not_active_pathlist] def _add_widgets_to_layout(self, layout, widgets): layout.setAlignment(Qt.AlignLeft) for widget in widgets: layout.addWidget(widget) def setup_top_toolbar(self, layout): toolbar = [] movetop_button = create_toolbutton(self, text=_("Move to top"), icon=ima.icon('2uparrow'), triggered=lambda: self.move_to(absolute=0), text_beside_icon=True) toolbar.append(movetop_button) moveup_button = create_toolbutton(self, text=_("Move up"), icon=ima.icon('1uparrow'), triggered=lambda: self.move_to(relative=-1), text_beside_icon=True) toolbar.append(moveup_button) movedown_button = create_toolbutton(self, text=_("Move down"), icon=ima.icon('1downarrow'), triggered=lambda: self.move_to(relative=1), text_beside_icon=True) toolbar.append(movedown_button) movebottom_button = create_toolbutton(self, text=_("Move to bottom"), icon=ima.icon('2downarrow'), triggered=lambda: self.move_to(absolute=1), text_beside_icon=True) toolbar.append(movebottom_button) self.selection_widgets.extend(toolbar) self._add_widgets_to_layout(layout, toolbar) return toolbar def setup_bottom_toolbar(self, layout, sync=True): toolbar = [] add_button = create_toolbutton(self, text=_('Add path'), icon=ima.icon('edit_add'), triggered=self.add_path, text_beside_icon=True) toolbar.append(add_button) remove_button = create_toolbutton(self, text=_('Remove path'), icon=ima.icon('edit_remove'), triggered=self.remove_path, text_beside_icon=True) toolbar.append(remove_button) self.selection_widgets.append(remove_button) self._add_widgets_to_layout(layout, toolbar) layout.addStretch(1) if os.name == 'nt' and sync: self.sync_button = create_toolbutton(self, text=_("Synchronize..."), icon=ima.icon('fileimport'), triggered=self.synchronize, tip=_("Synchronize Spyder's path list with PYTHONPATH " "environment variable"), text_beside_icon=True) layout.addWidget(self.sync_button) return toolbar @Slot() def synchronize(self): """ Synchronize Spyder's path list with PYTHONPATH environment variable Only apply to: current user, on Windows platforms """ answer = QMessageBox.question(self, _("Synchronize"), _("This will synchronize Spyder's path list with " "<b>PYTHONPATH</b> environment variable for current user, " "allowing you to run your Python modules outside Spyder " "without having to configure sys.path. " "<br>Do you want to clear contents of PYTHONPATH before " "adding Spyder's path list?"), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if answer == QMessageBox.Cancel: return elif answer == QMessageBox.Yes: remove = True else: remove = False from spyder.utils.environ import (get_user_env, set_user_env, listdict2envdict) env = get_user_env() if remove: ppath = self.active_pathlist+self.ro_pathlist else: ppath = env.get('PYTHONPATH', []) if not isinstance(ppath, list): ppath = [ppath] ppath = [path for path in ppath if path not in (self.active_pathlist+self.ro_pathlist)] ppath.extend(self.active_pathlist+self.ro_pathlist) env['PYTHONPATH'] = ppath set_user_env(listdict2envdict(env), parent=self) def get_path_list(self): """Return path list (does not include the read-only path list)""" return self.pathlist def update_not_active_pathlist(self, item): path = item.text() if bool(item.checkState()) is True: self.remove_from_not_active_pathlist(path) else: self.add_to_not_active_pathlist(path) def add_to_not_active_pathlist(self, path): if path not in self.not_active_pathlist: self.not_active_pathlist.append(path) def remove_from_not_active_pathlist(self, path): if path in self.not_active_pathlist: self.not_active_pathlist.remove(path) def update_list(self): """Update path list""" self.listwidget.clear() for name in self.pathlist+self.ro_pathlist: item = QListWidgetItem(name) item.setIcon(ima.icon('DirClosedIcon')) if name in self.ro_pathlist: item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) elif name in self.not_active_pathlist: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Unchecked) else: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) self.listwidget.addItem(item) self.refresh() def refresh(self, row=None): """Refresh widget""" for widget in self.selection_widgets: widget.setEnabled(self.listwidget.currentItem() is not None) not_empty = self.listwidget.count() > 0 if self.sync_button is not None: self.sync_button.setEnabled(not_empty) def move_to(self, absolute=None, relative=None): index = self.listwidget.currentRow() if absolute is not None: if absolute: new_index = len(self.pathlist)-1 else: new_index = 0 else: new_index = index + relative new_index = max(0, min(len(self.pathlist)-1, new_index)) path = self.pathlist.pop(index) self.pathlist.insert(new_index, path) self.update_list() self.listwidget.setCurrentRow(new_index) @Slot() def remove_path(self): answer = QMessageBox.warning(self, _("Remove path"), _("Do you really want to remove selected path?"), QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.Yes: self.pathlist.pop(self.listwidget.currentRow()) self.remove_from_not_active_pathlist( self.listwidget.currentItem().text()) self.update_list() @Slot() def add_path(self): self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), self.last_path) self.redirect_stdio.emit(True) if directory: directory = osp.abspath(directory) self.last_path = directory if directory in self.pathlist: item = self.listwidget.findItems(directory, Qt.MatchExactly)[0] item.setCheckState(Qt.Checked) answer = QMessageBox.question(self, _("Add path"), _("This directory is already included in Spyder path " "list.<br>Do you want to move it to the top of " "the list?"), QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.Yes: self.pathlist.remove(directory) else: return self.pathlist.insert(0, directory) self.update_list()
class LoadDataWidget(FormBaseWidget): update_main_window_title = Signal() update_global_state = Signal() computations_complete = Signal(object) update_preview_map_range = Signal(str) signal_new_run_loaded = Signal(bool) # True/False - success/failed signal_loading_new_run = Signal() # Emitted before new run is loaded signal_data_channel_changed = Signal(bool) def __init__(self, *, gpc, gui_vars): super().__init__() # Global processing classes self.gpc = gpc # Global GUI variables (used for control of GUI state) self.gui_vars = gui_vars self.ref_main_window = self.gui_vars["ref_main_window"] self.update_global_state.connect( self.ref_main_window.update_widget_state) self.initialize() def initialize(self): v_spacing = global_gui_parameters["vertical_spacing_in_tabs"] vbox = QVBoxLayout() self._setup_wd_group() vbox.addWidget(self.group_wd) vbox.addSpacing(v_spacing) self._setup_load_group() vbox.addWidget(self.group_file) vbox.addSpacing(v_spacing) self._setup_sel_channel_group() vbox.addWidget(self.group_sel_channel) vbox.addSpacing(v_spacing) self._setup_spec_settings_group() vbox.addWidget(self.group_spec_settings) vbox.addSpacing(v_spacing) self._setup_preview_group() vbox.addWidget(self.group_preview) vbox.addStretch(1) self.setLayout(vbox) self._set_tooltips() def _setup_wd_group(self): self.group_wd = QGroupBox("Working Directory") self.pb_set_wd = PushButtonMinimumWidth("..") self.pb_set_wd.clicked.connect(self.pb_set_wd_clicked) self.le_wd = LineEditReadOnly() # Initial working directory. Set to the HOME directory for now current_dir = os.path.expanduser( self.gpc.get_current_working_directory()) self.le_wd.setText(current_dir) hbox = QHBoxLayout() hbox.addWidget(self.pb_set_wd) hbox.addWidget(self.le_wd) self.group_wd.setLayout(hbox) def _setup_load_group(self): self.group_file = QGroupBox("Load Data") self.pb_file = QPushButton("Read File ...") self.pb_file.clicked.connect(self.pb_file_clicked) self.pb_dbase = QPushButton("Load Run ...") self.pb_dbase.setEnabled( self.gui_vars["gui_state"]["databroker_available"]) self.pb_dbase.clicked.connect(self.pb_dbase_clicked) self.cb_file_all_channels = QCheckBox("All channels") self.cb_file_all_channels.setChecked(self.gpc.get_load_each_channel()) self.cb_file_all_channels.toggled.connect( self.cb_file_all_channels_toggled) self.le_file_default = "No data is loaded" self.le_file = LineEditReadOnly(self.le_file_default) self.pb_view_metadata = QPushButton("View Metadata ...") self.pb_view_metadata.setEnabled(False) self.pb_view_metadata.clicked.connect(self.pb_view_metadata_clicked) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.pb_file) hbox.addWidget(self.pb_dbase) hbox.addWidget(self.cb_file_all_channels) vbox.addLayout(hbox) vbox.addWidget(self.le_file) hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(self.pb_view_metadata) vbox.addLayout(hbox) self.group_file.setLayout(vbox) def _setup_sel_channel_group(self): self.group_sel_channel = QGroupBox("Select Channel For Processing") self.cbox_channel = QComboBox() self.cbox_channel.currentIndexChanged.connect( self.cbox_channel_index_changed) self._set_cbox_channel_items(items=[]) hbox = QHBoxLayout() hbox.addWidget(self.cbox_channel) self.group_sel_channel.setLayout(hbox) def _setup_spec_settings_group(self): self.group_spec_settings = QGroupBox("Total Spectrum Settings") self.pb_apply_mask = QPushButton("Apply Mask ...") self.pb_apply_mask.clicked.connect(self.pb_apply_mask_clicked) hbox = QHBoxLayout() hbox.addWidget(self.pb_apply_mask) hbox.addStretch(1) self.group_spec_settings.setLayout(hbox) def _setup_preview_group(self): self.group_preview = QGroupBox("Preview") self.list_preview = QListWidget() self.list_preview.itemChanged.connect(self.list_preview_item_changed) self.list_preview.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.list_preview.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._set_list_preview_items(items=[]) hbox = QHBoxLayout() hbox.addWidget(self.list_preview) self.group_preview.setLayout(hbox) def _set_tooltips(self): set_tooltip( self.pb_set_wd, "Select <b>Working Directory</b>. The Working Directory is " "used as <b>default</b> for loading and saving data and " "configuration files.", ) set_tooltip(self.le_wd, "Currently selected <b>Working Directory</b>") set_tooltip(self.pb_file, "Load data from a <b>file on disk</b>.") set_tooltip(self.pb_dbase, "Load data from a <b>database</b> (Databroker).") set_tooltip( self.cb_file_all_channels, "Load <b>all</b> available data channels (checked) or only the <b>sum</b> of the channels", ) set_tooltip( self.le_file, "The <b>name</b> of the loaded file or <b>ID</b> of the loaded run." ) set_tooltip(self.pb_view_metadata, "View scan <b>metadata</b> (if available)") set_tooltip( self.cbox_channel, "Select channel for processing. Typically the <b>sum</b> channel is used." ) set_tooltip( self.pb_apply_mask, "Load the mask from file and/or select spatial ROI. The mask and ROI " "are used in run <b>Preview</b> and fitting of the <b>total spectrum</b>.", ) set_tooltip( self.list_preview, "Data for the selected channels is displayed in <b>Preview</b> tab. " "The displayed <b>total spectrum</b> is computed for the selected " "ROI and using the loaded mask. If no mask or ROI are enabled, then " "total spectrum is computed over all pixels of the image.", ) def update_widget_state(self, condition=None): if condition == "tooltips": self._set_tooltips() state = self.gui_vars["gui_state"]["state_file_loaded"] self.group_sel_channel.setEnabled(state) self.group_spec_settings.setEnabled(state) self.group_preview.setEnabled(state) def _set_cbox_channel_items(self, *, items=None): """ Set items of the combo box for item selection. If the list of items is not specified, then it is loaded from the respective data structure. Parameters ---------- items: list(str) The list of items. The list may be cleared by calling `self._set_cbox_channel_items(items=[])` """ self.cbox_channel.currentIndexChanged.disconnect( self.cbox_channel_index_changed) self.cbox_channel.clear() if items is None: items = list(self.gpc.get_file_channel_list()) self.cbox_channel.addItems(items) self.cbox_channel.currentIndexChanged.connect( self.cbox_channel_index_changed) if len(items): # Select the first item (if there is at least one item) self.cbox_channel.setCurrentIndex(0) def _set_list_preview_items(self, *, items=None): """ Set items of the list for selecting channels in preview tab. If the list of items is not specified, then it is loaded from the respective data structure. Parameters ---------- items: list(str) The list of items. The list may be cleared by calling `self._set_cbox_channel_items(items=[])` """ self.list_preview.itemChanged.disconnect( self.list_preview_item_changed) self.list_preview.clear() if items is None: items = list(self.gpc.get_file_channel_list()) for s in items: wi = QListWidgetItem(s, self.list_preview) wi.setFlags(wi.flags() | Qt.ItemIsUserCheckable) wi.setFlags(wi.flags() & ~Qt.ItemIsSelectable) wi.setCheckState(Qt.Unchecked) # Adjust height so that it fits all the elements adjust_qlistwidget_height(self.list_preview, other_widgets=[self.group_preview, self]) # This will cause the preview data to be plotted (the plot is expected to be hidden, # since no channels were selected). Here we select the first channel in the list. for n, item in enumerate(items): state = Qt.Checked if self.gpc.is_dset_item_selected_for_preview( item) else Qt.Unchecked self.list_preview.item(n).setCheckState(state) self.list_preview.itemChanged.connect(self.list_preview_item_changed) def pb_set_wd_clicked(self): dir_current = self.le_wd.text() dir = QFileDialog.getExistingDirectory( self, "Select Working Directory", dir_current, QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks, ) if dir: self.gpc.set_current_working_directory(dir) self.le_wd.setText(dir) def pb_file_clicked(self): dir_current = self.gpc.get_current_working_directory() file_paths = QFileDialog.getOpenFileName(self, "Open Data File", dir_current, "HDF5 (*.h5);; All (*)") file_path = file_paths[0] if file_path: self.signal_loading_new_run.emit() def cb(file_path): result_dict = {} try: msg = self.gpc.open_data_file(file_path) status = True except Exception as ex: msg = str(ex) status = False result_dict.update({ "status": status, "msg": msg, "file_path": file_path }) return result_dict self._compute_in_background(cb, self.slot_file_clicked, file_path=file_path) @Slot(object) def slot_file_clicked(self, result): self._recover_after_compute(self.slot_file_clicked) status = result["status"] msg = result["msg"] # Message is empty if file loading failed file_path = result["file_path"] if status: file_text = f"'{self.gpc.get_loaded_file_name()}'" if self.gpc.is_scan_metadata_available(): file_text += f": ID#{self.gpc.get_metadata_scan_id()}" self.le_file.setText(file_text) self.gui_vars["gui_state"]["state_file_loaded"] = True # Invalidate fit. Fit must be rerun for new data. self.gui_vars["gui_state"]["state_model_fit_exists"] = False # Check if any datasets were loaded. self.gui_vars["gui_state"][ "state_xrf_map_exists"] = self.gpc.is_xrf_maps_available() # Disable the button for changing working directory. This is consistent # with the behavior of the old PyXRF, but will be changed in the future. self.pb_set_wd.setEnabled(False) # Enable/disable 'View Metadata' button self.pb_view_metadata.setEnabled( self.gpc.is_scan_metadata_available()) self.le_wd.setText(self.gpc.get_current_working_directory()) self.update_main_window_title.emit() self.update_global_state.emit() self._set_cbox_channel_items() self._set_list_preview_items() # Here we want to reset the range in the Total Count Map preview self.update_preview_map_range.emit("reset") self.signal_new_run_loaded.emit( True) # Data is loaded successfully if msg: # Display warning message if it was generated msgbox = QMessageBox(QMessageBox.Warning, "Warning", msg, QMessageBox.Ok, parent=self) msgbox.exec() else: self.le_file.setText(self.le_file_default) # Disable 'View Metadata' button self.pb_view_metadata.setEnabled(False) self.le_wd.setText(self.gpc.get_current_working_directory()) # Clear flags: the state now is "No data is loaded". clear_gui_state(self.gui_vars) self.update_global_state.emit() self.update_main_window_title.emit() self.update_global_state.emit() self._set_cbox_channel_items(items=[]) self._set_list_preview_items(items=[]) # Here we want to clear the range in the Total Count Map preview self.update_preview_map_range.emit("clear") self.signal_new_run_loaded.emit(False) # Failed to load data msg_str = (f"Incorrect format of input file '{file_path}': " f"PyXRF accepts only custom HDF (.h5) files." f"\n\nError message: {msg}") msgbox = QMessageBox(QMessageBox.Critical, "Error", msg_str, QMessageBox.Ok, parent=self) msgbox.exec() def pb_dbase_clicked(self): dlg = DialogSelectScan() if dlg.exec() == QDialog.Accepted: mode, id_uid = dlg.get_id_uid() self.signal_loading_new_run.emit() def cb(id_uid): result_dict = {} try: msg, file_name = self.gpc.load_run_from_db(id_uid) status = True except Exception as ex: msg, file_name = str(ex), "" status = False result_dict.update({ "status": status, "msg": msg, "id_uid": id_uid, "file_name": file_name }) return result_dict self._compute_in_background(cb, self.slot_dbase_clicked, id_uid=id_uid) @Slot(object) def slot_dbase_clicked(self, result): self._recover_after_compute(self.slot_dbase_clicked) status = result["status"] msg = result["msg"] # Message is empty if file loading failed id_uid = result["id_uid"] # file_name = result["file_name"] if status: file_text = f"'{self.gpc.get_loaded_file_name()}'" if self.gpc.is_scan_metadata_available(): file_text += f": ID#{self.gpc.get_metadata_scan_id()}" self.le_file.setText(file_text) self.gui_vars["gui_state"]["state_file_loaded"] = True # Invalidate fit. Fit must be rerun for new data. self.gui_vars["gui_state"]["state_model_fit_exists"] = False # Check if any datasets were loaded. self.gui_vars["gui_state"][ "state_xrf_map_exists"] = self.gpc.is_xrf_maps_available() # Disable the button for changing working directory. This is consistent # with the behavior of the old PyXRF, but will be changed in the future. self.pb_set_wd.setEnabled(False) # Enable/disable 'View Metadata' button self.pb_view_metadata.setEnabled( self.gpc.is_scan_metadata_available()) self.le_wd.setText(self.gpc.get_current_working_directory()) self.update_main_window_title.emit() self.update_global_state.emit() self._set_cbox_channel_items() self._set_list_preview_items() # Here we want to reset the range in the Total Count Map preview self.update_preview_map_range.emit("reset") self.signal_new_run_loaded.emit( True) # Data is loaded successfully if msg: # Display warning message if it was generated msgbox = QMessageBox(QMessageBox.Warning, "Warning", msg, QMessageBox.Ok, parent=self) msgbox.exec() else: self.le_file.setText(self.le_file_default) # Disable 'View Metadata' button self.pb_view_metadata.setEnabled(False) self.le_wd.setText(self.gpc.get_current_working_directory()) # Clear flags: the state now is "No data is loaded". clear_gui_state(self.gui_vars) self.update_global_state.emit() self.update_main_window_title.emit() self.update_global_state.emit() self._set_cbox_channel_items(items=[]) self._set_list_preview_items(items=[]) # Here we want to clear the range in the Total Count Map preview self.update_preview_map_range.emit("clear") self.signal_new_run_loaded.emit(False) # Failed to load data msg_str = f"Failed to load scan '{id_uid}'.\n\nError message: {msg}" msgbox = QMessageBox(QMessageBox.Critical, "Error", msg_str, QMessageBox.Ok, parent=self) msgbox.exec() def pb_apply_mask_clicked(self): map_size = self.gpc.get_dataset_map_size() map_size = map_size if (map_size is not None) else (0, 0) dlg = DialogLoadMask() dlg.set_image_size(n_rows=map_size[0], n_columns=map_size[1]) roi = self.gpc.get_preview_spatial_roi() dlg.set_roi( row_start=roi["row_start"], column_start=roi["col_start"], row_end=roi["row_end"], column_end=roi["col_end"], ) dlg.set_roi_active(self.gpc.is_roi_selection_active()) dlg.set_default_directory(self.gpc.get_current_working_directory()) dlg.set_mask_file_path(self.gpc.get_mask_selection_file_path()) dlg.set_mask_file_active(self.gpc.is_mask_selection_active()) if dlg.exec() == QDialog.Accepted: roi_keys = ("row_start", "col_start", "row_end", "col_end") roi_list = dlg.get_roi() roi_selected = {k: roi_list[n] for n, k in enumerate(roi_keys)} self.gpc.set_preview_spatial_roi(roi_selected) self.gpc.set_roi_selection_active(dlg.get_roi_active()) self.gpc.set_mask_selection_file_path(dlg.get_mask_file_path()) self.gpc.set_mask_selection_active(dlg.get_mask_file_active()) def cb(): try: # TODO: proper error processing is needed here (exception RuntimeError) self.gpc.apply_mask_to_datasets() success = True msg = "" except Exception as ex: success = False msg = str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_apply_mask_clicked) @Slot(object) def slot_apply_mask_clicked(self, result): if not result["success"]: msg = f"Error occurred while applying the ROI selection:\nException: {result['msg']}" logger.error(f"{msg}") mb_error = QMessageBox(QMessageBox.Critical, "Error", f"{msg}", QMessageBox.Ok, parent=self) mb_error.exec() # Here we want to expand the range in the Total Count Map preview if needed self.update_preview_map_range.emit("update") self._recover_after_compute(self.slot_apply_mask_clicked) def pb_view_metadata_clicked(self): dlg = DialogViewMetadata() metadata_string = self.gpc.get_formatted_metadata() dlg.setText(metadata_string) dlg.exec() def cb_file_all_channels_toggled(self, state): self.gpc.set_load_each_channel(state) def cbox_channel_index_changed(self, index): def cb(index): try: self.gpc.set_data_channel(index) success, msg = True, "" except Exception as ex: success = False msg = str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_channel_index_changed, index=index) @Slot(object) def slot_channel_index_changed(self, result): self._recover_after_compute(self.slot_channel_index_changed) if result["success"]: self.signal_data_channel_changed.emit(True) else: self.signal_data_channel_changed.emit(False) msg = result["msg"] msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() def list_preview_item_changed(self, list_item): # Find the index of the list item that was checked/unchecked ind = -1 for n in range(self.list_preview.count()): if self.list_preview.item(n) == list_item: ind = n # Get the state of the checkbox (checked -> 2, unchecked -> 0) state = list_item.checkState() # The name of the dataset dset_name = self.gpc.get_file_channel_list()[ind] def cb(): try: self.gpc.select_preview_dataset(dset_name=dset_name, is_visible=bool(state)) success, msg = True, "" except Exception as ex: success = False msg = str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_preview_items_changed) @Slot(object) def slot_preview_items_changed(self, result): if not result["success"]: # The error shouldn't actually happen here. This is to prevent potential crashes. msg = f"Error occurred: {result['msg']}.\nData may need to be reloaded to continue processing." msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() # Here we want to expand the range in the Total Count Map preview if needed self.update_preview_map_range.emit("expand") self._recover_after_compute(self.slot_preview_items_changed) def _compute_in_background(self, func, slot, *args, **kwargs): """ Run function `func` in a background thread. Send the signal `self.computations_complete` once computation is finished. Parameters ---------- func: function Reference to a function that is supposed to be executed at the background. The function return value is passed as a signal parameter once computation is complete. slot: qtpy.QtCore.Slot or None Reference to a slot. If not None, then the signal `self.computation_complete` is connected to this slot. args, kwargs arguments of the function `func`. """ signal_complete = self.computations_complete def func_to_run(func, *args, **kwargs): class LoadFile(QRunnable): def run(self): result_dict = func(*args, **kwargs) signal_complete.emit(result_dict) return LoadFile() if slot is not None: self.computations_complete.connect(slot) self.gui_vars["gui_state"]["running_computations"] = True self.update_global_state.emit() QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs)) def _recover_after_compute(self, slot): """ The function should be called after the signal `self.computations_complete` is received. The slot should be the same as the one used when calling `self.compute_in_background`. """ if slot is not None: self.computations_complete.disconnect(slot) self.gui_vars["gui_state"]["running_computations"] = False self.update_global_state.emit()
class CheckList(QWidget): def __init__(self, model, label="", help_link="", custom_filter_button=None): """ :param custom_filter_button: if needed, add a button that opens a custom filter menu. Useful when search alone isn't enough to filter the list. :type custom_filter_button: QToolButton """ QWidget.__init__(self) self._model = model if help_link != "": addHelpToWidget(self, help_link) layout = QVBoxLayout() self._createCheckButtons() self._list = QListWidget() self._list.setContextMenuPolicy(Qt.CustomContextMenu) self._list.setSelectionMode(QAbstractItemView.ExtendedSelection) self._search_box = SearchBox() check_button_layout = QHBoxLayout() check_button_layout.setContentsMargins(0, 0, 0, 0) check_button_layout.setSpacing(0) check_button_layout.addWidget(QLabel(label)) check_button_layout.addStretch(1) check_button_layout.addWidget(self._checkAllButton) check_button_layout.addWidget(self._uncheckAllButton) layout.addLayout(check_button_layout) layout.addWidget(self._list) """ Inserts the custom filter button, if provided. The caller is responsible for all related actions. """ if custom_filter_button is not None: search_bar_layout = QHBoxLayout() search_bar_layout.addWidget(self._search_box) search_bar_layout.addWidget(custom_filter_button) layout.addLayout(search_bar_layout) else: layout.addWidget(self._search_box) self.setLayout(layout) self._checkAllButton.clicked.connect(self.checkAll) self._uncheckAllButton.clicked.connect(self.uncheckAll) self._list.itemChanged.connect(self.itemChanged) self._search_box.filterChanged.connect(self.filterList) self._list.customContextMenuRequested.connect(self.showContextMenu) self._model.selectionChanged.connect(self.modelChanged) self._model.modelChanged.connect(self.modelChanged) self.modelChanged() def _createCheckButtons(self): self._checkAllButton = QToolButton() self._checkAllButton.setIcon(resourceIcon("check.svg")) self._checkAllButton.setIconSize(QSize(16, 16)) self._checkAllButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self._checkAllButton.setAutoRaise(True) self._checkAllButton.setToolTip("Select all") self._uncheckAllButton = QToolButton() self._uncheckAllButton.setIcon(resourceIcon("checkbox_outline.svg")) self._uncheckAllButton.setIconSize(QSize(16, 16)) self._uncheckAllButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self._uncheckAllButton.setAutoRaise(True) self._uncheckAllButton.setToolTip("Unselect all") def itemChanged(self, item): """@type item: QListWidgetItem""" if item.checkState() == Qt.Checked: self._model.selectValue(str(item.text())) elif item.checkState() == Qt.Unchecked: self._model.unselectValue(str(item.text())) else: raise AssertionError("Unhandled checkstate!") def modelChanged(self): self._list.clear() items = self._model.getList() for item in items: list_item = QListWidgetItem(item) list_item.setFlags(list_item.flags() | Qt.ItemIsUserCheckable) if self._model.isValueSelected(item): list_item.setCheckState(Qt.Checked) else: list_item.setCheckState(Qt.Unchecked) self._list.addItem(list_item) self.filterList(self._search_box.filter()) def setSelectionEnabled(self, enabled): self.setEnabled(enabled) self._checkAllButton.setEnabled(enabled) self._uncheckAllButton.setEnabled(enabled) def filterList(self, filter): filter = filter.lower() for index in range(0, self._list.count()): item = self._list.item(index) text = str(item.text()).lower() if filter == "": item.setHidden(False) elif filter in text: item.setHidden(False) else: item.setHidden(True) def checkAll(self): """ Checks all visible items in the list. """ for index in range(0, self._list.count()): item = self._list.item(index) if not item.isHidden(): self._model.selectValue(str(item.text())) def uncheckAll(self): """ Unchecks all items in the list, visible or not """ self._model.unselectAll() def checkSelected(self): items = [] for item in self._list.selectedItems(): items.append(str(item.text())) for item in items: self._model.selectValue(item) def uncheckSelected(self): items = [] for item in self._list.selectedItems(): items.append(str(item.text())) for item in items: self._model.unselectValue(item) def showContextMenu(self, point): p = self._list.mapToGlobal(point) menu = QMenu() check_selected = menu.addAction("Check selected") uncheck_selected = menu.addAction("Uncheck selected") menu.addSeparator() clear_selection = menu.addAction("Clear selection") selected_item = menu.exec_(p) if selected_item == check_selected: self.checkSelected() elif selected_item == uncheck_selected: self.uncheckSelected() elif selected_item == clear_selection: self._list.clearSelection()
class PreferencesDialog(QDialog): """Preferences Dialog for Napari user settings.""" resized = Signal(QSize) def __init__(self, parent=None): super().__init__(parent) self._list = QListWidget(self) self._stack = QStackedWidget(self) self._list.setObjectName("Preferences") # Set up buttons self._button_cancel = QPushButton(trans._("Cancel")) self._button_ok = QPushButton(trans._("OK")) self._default_restore = QPushButton(trans._("Restore defaults")) # Setup self.setWindowTitle(trans._("Preferences")) # Layout left_layout = QVBoxLayout() left_layout.addWidget(self._list) left_layout.addStretch() left_layout.addWidget(self._default_restore) left_layout.addWidget(self._button_cancel) left_layout.addWidget(self._button_ok) main_layout = QHBoxLayout() main_layout.addLayout(left_layout, 1) main_layout.addWidget(self._stack, 3) self.setLayout(main_layout) # Signals self._list.currentRowChanged.connect( lambda index: self._stack.setCurrentIndex(index)) self._button_cancel.clicked.connect(self.on_click_cancel) self._button_ok.clicked.connect(self.on_click_ok) self._default_restore.clicked.connect(self.restore_defaults) # Make widget self.make_dialog() self._list.setCurrentRow(0) def resizeEvent(self, event): """Override to emit signal.""" self.resized.emit(event.size()) super().resizeEvent(event) def make_dialog(self): """Removes settings not to be exposed to user and creates dialog pages.""" # Because there are multiple pages, need to keep a list of values sets. self._values_orig_set_list = [] self._values_set_list = [] for _key, setting in SETTINGS.schemas().items(): schema = json.loads(setting['json_schema']) # Need to remove certain properties that will not be displayed on the GUI properties = schema.pop('properties') model = setting['model'] values = model.dict() napari_config = getattr(model, "NapariConfig", None) if napari_config is not None: for val in napari_config.preferences_exclude: properties.pop(val) values.pop(val) schema['properties'] = properties self._values_orig_set_list.append(set(values.items())) self._values_set_list.append(set(values.items())) # Only add pages if there are any properties to add. if properties: self.add_page(schema, values) def restore_defaults(self): """Launches dialog to confirm restore settings choice.""" widget = ConfirmDialog( parent=self, text=trans._("Are you sure you want to restore default settings?"), ) widget.valueChanged.connect(self._reset_widgets) widget.exec_() def _reset_widgets(self): """Deletes the widgets and rebuilds with defaults.""" self.close() self._list.clear() for n in range(self._stack.count()): widget = self._stack.removeWidget(self._stack.currentWidget()) del widget self.make_dialog() self._list.setCurrentRow(0) self.show() def on_click_ok(self): """Keeps the selected preferences saved to SETTINGS.""" self.close() def on_click_cancel(self): """Restores the settings in place when dialog was launched.""" # Need to check differences for each page. for n in range(self._stack.count()): # Must set the current row so that the proper set list is updated # in check differences. self._list.setCurrentRow(n) self.check_differences( self._values_orig_set_list[n], self._values_set_list[n], ) self._list.setCurrentRow(0) self.close() def add_page(self, schema, values): """Creates a new page for each section in dialog. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ widget = self.build_page_dialog(schema, values) self._list.addItem(schema["title"]) self._stack.addWidget(widget) def build_page_dialog(self, schema, values): """Builds the preferences widget using the json schema builder. Parameters ---------- schema : dict Json schema including all information to build each page in the preferences dialog. values : dict Dictionary of current values set in preferences. """ builder = WidgetBuilder() form = builder.create_form(schema, {}) # set state values for widget form.widget.state = values form.widget.on_changed.connect(lambda d: self.check_differences( set(d.items()), self._values_set_list[self._list.currentIndex().row()], )) return form def check_differences(self, new_set, values_set): """Changes settings in settings manager with changes from dialog. Parameters ---------- new_set : set The set of new values, with tuples of key value pairs for each setting. values_set : set The old set of values. """ page = self._list.currentItem().text().split(" ")[0].lower() different_values = list(new_set - values_set) if len(different_values) > 0: # change the values in SETTINGS for val in different_values: try: setattr(SETTINGS._settings[page], val[0], val[1]) self._values_set_list[ self._list.currentIndex().row()] = new_set except: # noqa: E722 continue
class ApplicationsDialog(QDialog): """Dialog for selection of installed system/user applications.""" def __init__(self, parent=None): """Dialog for selection of installed system/user applications.""" super(ApplicationsDialog, self).__init__(parent=parent) # Widgets self.label = QLabel() self.label_browse = QLabel() self.edit_filter = QLineEdit() self.list = QListWidget() self.button_browse = QPushButton(_('Browse...')) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = self.button_box.button(QDialogButtonBox.Ok) self.button_cancel = self.button_box.button(QDialogButtonBox.Cancel) # Widget setup self.setWindowTitle(_('Applications')) self.edit_filter.setPlaceholderText(_('Type to filter by name')) self.list.setIconSize(QSize(16, 16)) # FIXME: Use metrics # Layout layout = QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.edit_filter) layout.addWidget(self.list) layout_browse = QHBoxLayout() layout_browse.addWidget(self.button_browse) layout_browse.addWidget(self.label_browse) layout.addLayout(layout_browse) layout.addSpacing(12) # FIXME: Use metrics layout.addWidget(self.button_box) self.setLayout(layout) # Signals self.edit_filter.textChanged.connect(self.filter) self.button_browse.clicked.connect(lambda x: self.browse()) self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.list.currentItemChanged.connect(self._refresh) self._refresh() self.setup() def setup(self, applications=None): """Load installed applications.""" QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.list.clear() if applications is None: apps = get_installed_applications() else: apps = applications for app in sorted(apps, key=lambda x: x.lower()): fpath = apps[app] icon = get_application_icon(fpath) item = QListWidgetItem(icon, app) item.setToolTip(fpath) item.fpath = fpath self.list.addItem(item) # FIXME: Use metrics self.list.setMinimumWidth(self.list.sizeHintForColumn(0) + 24) QApplication.restoreOverrideCursor() self._refresh() def _refresh(self): """Refresh the status of buttons on widget.""" self.button_ok.setEnabled(self.list.currentRow() != -1) def browse(self, fpath=None): """Prompt user to select an application not found on the list.""" app = None item = None if sys.platform == 'darwin': if fpath is None: basedir = '/Applications/' filters = _('Applications (*.app)') title = _('Select application') fpath, __ = getopenfilename(self, title, basedir, filters) if fpath and fpath.endswith('.app') and os.path.isdir(fpath): app = os.path.basename(fpath).split('.app')[0] for row in range(self.list.count()): item = self.list.item(row) if app == item.text() and fpath == item.fpath: break else: item = None elif os.name == 'nt': if fpath is None: basedir = 'C:\\' filters = _('Applications (*.exe *.bat *.com)') title = _('Select application') fpath, __ = getopenfilename(self, title, basedir, filters) if fpath: check_1 = fpath.endswith('.bat') and is_text_file(fpath) check_2 = (fpath.endswith(('.exe', '.com')) and not is_text_file(fpath)) if check_1 or check_2: app = os.path.basename(fpath).capitalize().rsplit('.')[0] for row in range(self.list.count()): item = self.list.item(row) if app == item.text() and fpath == item.fpath: break else: item = None else: if fpath is None: basedir = '/' filters = _('Applications (*.desktop)') title = _('Select application') fpath, __ = getopenfilename(self, title, basedir, filters) if fpath and fpath.endswith(('.desktop')) and is_text_file(fpath): entry_data = parse_linux_desktop_entry(fpath) app = entry_data['name'] for row in range(self.list.count()): item = self.list.item(row) if app == item.text() and fpath == item.fpath: break else: item = None if fpath: if item: self.list.setCurrentItem(item) elif app: icon = get_application_icon(fpath) item = QListWidgetItem(icon, app) item.fpath = fpath self.list.addItem(item) self.list.setCurrentItem(item) self.list.setFocus() self._refresh() def filter(self, text): """Filter the list of applications based on text.""" text = self.edit_filter.text().lower().strip() for row in range(self.list.count()): item = self.list.item(row) item.setHidden(text not in item.text().lower()) self._refresh() def set_extension(self, extension): """Set the extension on the label of the dialog.""" self.label.setText( _('Choose the application for files of type ') + extension) @property def application_path(self): """Return the selected application path to executable.""" item = self.list.currentItem() path = item.fpath if item else '' return path @property def application_name(self): """Return the selected application name.""" item = self.list.currentItem() text = item.text() if item else '' return text
class PreferencesDialog(QDialog): """Preferences Dialog for Napari user settings.""" ui_schema = { "call_order": { "ui:widget": "plugins" }, "highlight_thickness": { "ui:widget": "highlight" }, "shortcuts": { "ui:widget": "shortcuts" }, "extension2reader": { "ui:widget": "extension2reader" }, } resized = Signal(QSize) def __init__(self, parent=None): from ...settings import get_settings super().__init__(parent) self.setWindowTitle(trans._("Preferences")) self._settings = get_settings() self._stack = QStackedWidget(self) self._list = QListWidget(self) self._list.setObjectName("Preferences") self._list.currentRowChanged.connect(self._stack.setCurrentIndex) # Set up buttons self._button_cancel = QPushButton(trans._("Cancel")) self._button_cancel.clicked.connect(self.reject) self._button_ok = QPushButton(trans._("OK")) self._button_ok.clicked.connect(self.accept) self._button_ok.setDefault(True) self._button_restore = QPushButton(trans._("Restore defaults")) self._button_restore.clicked.connect(self._restore_default_dialog) # Layout left_layout = QVBoxLayout() left_layout.addWidget(self._list) left_layout.addStretch() left_layout.addWidget(self._button_restore) left_layout.addWidget(self._button_cancel) left_layout.addWidget(self._button_ok) self.setLayout(QHBoxLayout()) self.layout().addLayout(left_layout, 1) self.layout().addWidget(self._stack, 3) # Build dialog from settings self._rebuild_dialog() def keyPressEvent(self, e: 'QKeyEvent'): if e.key() == Qt.Key_Escape: # escape key should just close the window # which implies "accept" e.accept() self.accept() return super().keyPressEvent(e) def resizeEvent(self, event): """Override to emit signal.""" self.resized.emit(event.size()) super().resizeEvent(event) def _rebuild_dialog(self): """Removes settings not to be exposed to user and creates dialog pages.""" # FIXME: this dialog should not need to know about the plugin manager from ...plugins import plugin_manager self._starting_pm_order = plugin_manager.call_order() self._starting_values = self._settings.dict(exclude={'schema_version'}) self._list.clear() while self._stack.count(): self._stack.removeWidget(self._stack.currentWidget()) for field in self._settings.__fields__.values(): if isinstance(field.type_, type) and issubclass( field.type_, BaseModel): self._add_page(field) self._list.setCurrentRow(0) def _add_page(self, field: 'ModelField'): """Builds the preferences widget using the json schema builder. Parameters ---------- field : ModelField subfield for which to create a page. """ from ..._vendor.qt_json_builder.qt_jsonschema_form import WidgetBuilder schema, values = self._get_page_dict(field) name = field.field_info.title or field.name form = WidgetBuilder().create_form(schema, self.ui_schema) # set state values for widget form.widget.state = values # make settings follow state of the form widget form.widget.on_changed.connect( lambda d: getattr(self._settings, name.lower()).update(d)) # need to disable async if octree is enabled. # TODO: this shouldn't live here... if there is a coupling/dependency # between these settings, it should be declared in the settings schema if (name.lower() == 'experimental' and values['octree'] and self._settings.env_settings().get( 'experimental', {}).get('async_') not in (None, '0')): form_layout = form.widget.layout() for i in range(form_layout.count()): wdg = form_layout.itemAt(i, form_layout.FieldRole).widget() if getattr(wdg, '_name') == 'async_': wdg.opacity.setOpacity(0.3) wdg.setDisabled(True) break self._list.addItem(field.field_info.title or field.name) self._stack.addWidget(form) def _get_page_dict(self, field: 'ModelField') -> Tuple[dict, dict, dict]: """Provides the schema, set of values for each setting, and the properties for each setting.""" ftype = cast('BaseModel', field.type_) schema = json.loads(ftype.schema_json()) # find enums: for name, subfield in ftype.__fields__.items(): if isinstance(subfield.type_, EnumMeta): enums = [s.value for s in subfield.type_] # type: ignore schema["properties"][name]["enum"] = enums schema["properties"][name]["type"] = "string" # Need to remove certain properties that will not be displayed on the GUI setting = getattr(self._settings, field.name) with setting.enums_as_values(): values = setting.dict() napari_config = getattr(setting, "NapariConfig", None) if hasattr(napari_config, 'preferences_exclude'): for val in napari_config.preferences_exclude: schema['properties'].pop(val, None) values.pop(val, None) return schema, values def _restore_default_dialog(self): """Launches dialog to confirm restore settings choice.""" response = QMessageBox.question( self, trans._("Restore Settings"), trans._("Are you sure you want to restore default settings?"), QMessageBox.RestoreDefaults | QMessageBox.Cancel, QMessageBox.RestoreDefaults, ) if response == QMessageBox.RestoreDefaults: self._settings.reset() self._rebuild_dialog() # TODO: do we need this? def _restart_required_dialog(self): """Displays the dialog informing user a restart is required.""" QMessageBox.information( self, trans._("Restart required"), trans. _("A restart is required for some new settings to have an effect." ), ) def closeEvent(self, event: 'QCloseEvent') -> None: event.accept() self.accept() def reject(self): """Restores the settings in place when dialog was launched.""" self._settings.update(self._starting_values) # FIXME: this dialog should not need to know about the plugin manager if self._starting_pm_order: from ...plugins import plugin_manager plugin_manager.set_call_order(self._starting_pm_order) super().reject()
class FileSwitcher(QDialog): """A Sublime-like file switcher.""" sig_goto_file = Signal(int, object) # Constants that define the mode in which the list widget is working # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols # in a given file when using the '@' symbol. FILE_MODE, SYMBOL_MODE = [1, 2] MAX_WIDTH = 600 def __init__(self, parent, plugin, tabs, data, icon): QDialog.__init__(self, parent) # Variables self.plugins_tabs = [] self.plugins_data = [] self.plugins_instances = [] self.add_plugin(plugin, tabs, data, icon) self.plugin = None # Last plugin with focus self.mode = self.FILE_MODE # By default start in this mode self.initial_cursors = None # {fullpath: QCursor} self.initial_path = None # Fullpath of initial active editor self.initial_widget = None # Initial active editor self.line_number = None # Selected line number in filer self.is_visible = False # Is the switcher visible? help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to " "cancel.<br><br>Type to filter filenames.<br><br>" "Use <b>:number</b> to go to a line, e.g. " "<b><code>main:42</code></b><br>" "Use <b>@symbol_text</b> to go to a symbol, e.g. " "<b><code>@init</code></b>" "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>") # Either allow searching for a line number or a symbol but not both regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" + "([A-Za-z0-9_]{0,100}:{0,1}[0-9]{0,100})") # Widgets self.edit = FilesFilterLine(self) self.help = HelperToolButton() self.list = QListWidget(self) self.filter = KeyPressFilter() regex_validator = QRegExpValidator(regex, self.edit) # Widgets setup self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) self.setWindowOpacity(0.95) self.edit.installEventFilter(self.filter) self.edit.setValidator(regex_validator) self.help.setToolTip(help_text) self.list.setItemDelegate(HTMLDelegate(self)) # Layout edit_layout = QHBoxLayout() edit_layout.addWidget(self.edit) edit_layout.addWidget(self.help) layout = QVBoxLayout() layout.addLayout(edit_layout) layout.addWidget(self.list) self.setLayout(layout) # Signals self.rejected.connect(self.restore_initial_state) self.filter.sig_up_key_pressed.connect(self.previous_row) self.filter.sig_down_key_pressed.connect(self.next_row) self.edit.returnPressed.connect(self.accept) self.edit.textChanged.connect(self.setup) self.list.itemSelectionChanged.connect(self.item_selection_changed) self.list.clicked.connect(self.edit.setFocus) # --- Properties @property def widgets(self): widgets = [] for plugin in self.plugins_instances: tabs = self.get_plugin_tabwidget(plugin) widgets += [(tabs.widget(index), plugin) for index in range(tabs.count())] return widgets @property def line_count(self): line_count = [] for widget in self.widgets: try: current_line_count = widget[0].get_line_count() except AttributeError: current_line_count = 0 line_count.append(current_line_count) return line_count @property def save_status(self): save_status = [] for da, icon in self.plugins_data: save_status += [getattr(td, 'newly_created', False) for td in da] return save_status @property def paths(self): paths = [] for plugin in self.plugins_instances: da = self.get_plugin_data(plugin) paths += [getattr(td, 'filename', None) for td in da] return paths @property def filenames(self): filenames = [] for plugin in self.plugins_instances: da = self.get_plugin_data(plugin) filenames += [ os.path.basename(getattr(td, 'filename', None)) for td in da ] return filenames @property def icons(self): icons = [] for da, icon in self.plugins_data: icons += [icon for td in da] return icons @property def current_path(self): return self.paths_by_widget[self.get_widget()] @property def paths_by_widget(self): widgets = [w[0] for w in self.widgets] return dict(zip(widgets, self.paths)) @property def widgets_by_path(self): widgets = [w[0] for w in self.widgets] return dict(zip(self.paths, widgets)) @property def filter_text(self): """Get the normalized (lowecase) content of the filter text.""" return to_text_string(self.edit.text()).lower() def set_search_text(self, _str): self.edit.setText(_str) def save_initial_state(self): """Save initial cursors and initial active widget.""" paths = self.paths self.initial_widget = self.get_widget() self.initial_cursors = {} for i, editor in enumerate(self.widgets): if editor is self.initial_widget: self.initial_path = paths[i] # This try is needed to make the fileswitcher work with # plugins that does not have a textCursor. try: self.initial_cursors[paths[i]] = editor.textCursor() except AttributeError: pass def accept(self): self.is_visible = False QDialog.accept(self) self.list.clear() def restore_initial_state(self): """Restores initial cursors and initial active editor.""" self.list.clear() self.is_visible = False widgets = self.widgets_by_path if not self.edit.clicked_outside: for path in self.initial_cursors: cursor = self.initial_cursors[path] if path in widgets: self.set_editor_cursor(widgets[path], cursor) if self.initial_widget in self.paths_by_widget: index = self.paths.index(self.initial_path) self.sig_goto_file.emit(index) def set_dialog_position(self): """Positions the file switcher dialog.""" parent = self.parent() geo = parent.geometry() width = self.list.width() # This has been set in setup left = parent.geometry().width() / 2 - width / 2 # Note: the +1 pixel on the top makes it look better if isinstance(parent, QMainWindow): top = (parent.toolbars_menu.geometry().height() + parent.menuBar().geometry().height() + 1) else: top = self.plugins_tabs[0][0].tabBar().geometry().height() + 1 while parent: geo = parent.geometry() top += geo.top() left += geo.left() parent = parent.parent() self.move(left, top) def get_item_size(self, content): """ Get the max size (width and height) for the elements of a list of strings as a QLabel. """ strings = [] if content: for rich_text in content: label = QLabel(rich_text) label.setTextFormat(Qt.PlainText) strings.append(label.text()) fm = label.fontMetrics() return (max([fm.width(s) * 1.3 for s in strings]), fm.height()) def fix_size(self, content): """ Adjusts the width and height of the file switcher based on the relative size of the parent and content. """ # Update size of dialog based on relative size of the parent if content: width, height = self.get_item_size(content) # Width parent = self.parent() relative_width = parent.geometry().width() * 0.65 if relative_width > self.MAX_WIDTH: relative_width = self.MAX_WIDTH self.list.setMinimumWidth(relative_width) # Height if len(content) < 8: max_entries = len(content) else: max_entries = 8 max_height = height * max_entries * 2.5 self.list.setMinimumHeight(max_height) # Resize self.list.resize(relative_width, self.list.height()) # --- Helper methods: List widget def count(self): """Gets the item count in the list widget.""" return self.list.count() def current_row(self): """Returns the current selected row in the list widget.""" return self.list.currentRow() def set_current_row(self, row): """Sets the current selected row in the list widget.""" return self.list.setCurrentRow(row) def select_row(self, steps): """Select row in list widget based on a number of steps with direction. Steps can be positive (next rows) or negative (previous rows). """ row = self.current_row() + steps if 0 <= row < self.count(): self.set_current_row(row) def previous_row(self): """Select previous row in list widget.""" if self.mode == self.SYMBOL_MODE: self.select_row(-1) return prev_row = self.current_row() - 1 if prev_row >= 0: title = self.list.item(prev_row).text() else: title = '' if prev_row == 0 and '</b></big><br>' in title: self.list.scrollToTop() elif '</b></big><br>' in title: # Select the next previous row, the one following is a title self.select_row(-2) else: self.select_row(-1) def next_row(self): """Select next row in list widget.""" if self.mode == self.SYMBOL_MODE: self.select_row(+1) return next_row = self.current_row() + 1 if next_row < self.count(): if '</b></big><br>' in self.list.item(next_row).text(): # Select the next next row, the one following is a title self.select_row(+2) else: self.select_row(+1) def get_stack_index(self, stack_index, plugin_index): """Get the real index of the selected item.""" other_plugins_count = sum([other_tabs[0].count() \ for other_tabs in \ self.plugins_tabs[:plugin_index]]) real_index = stack_index - other_plugins_count return real_index # --- Helper methods: Widget def get_plugin_data(self, plugin): """Get the data object of the plugin's current tab manager.""" # The data object is named "data" in the editor plugin while it is # named "clients" in the notebook plugin. try: data = plugin.get_current_tab_manager().data except AttributeError: data = plugin.get_current_tab_manager().clients return data def get_plugin_tabwidget(self, plugin): """Get the tabwidget of the plugin's current tab manager.""" # The tab widget is named "tabs" in the editor plugin while it is # named "tabwidget" in the notebook plugin. try: tabwidget = plugin.get_current_tab_manager().tabs except AttributeError: tabwidget = plugin.get_current_tab_manager().tabwidget return tabwidget def get_widget(self, index=None, path=None, tabs=None): """Get widget by index. If no tabs and index specified the current active widget is returned. """ if (index and tabs) or (path and tabs): return tabs.widget(index) elif self.plugin: return self.get_plugin_tabwidget(self.plugin).currentWidget() else: return self.plugins_tabs[0][0].currentWidget() def set_editor_cursor(self, editor, cursor): """Set the cursor of an editor.""" pos = cursor.position() anchor = cursor.anchor() new_cursor = QTextCursor() if pos == anchor: new_cursor.movePosition(pos) else: new_cursor.movePosition(anchor) new_cursor.movePosition(pos, QTextCursor.KeepAnchor) editor.setTextCursor(cursor) def goto_line(self, line_number): """Go to specified line number in current active editor.""" if line_number: line_number = int(line_number) try: self.plugin.go_to_line(line_number) except AttributeError: pass # --- Helper methods: Outline explorer def get_symbol_list(self): """Get the list of symbols present in the file.""" try: oedata = self.get_widget().get_outlineexplorer_data() except AttributeError: oedata = {} return oedata # --- Handlers def item_selection_changed(self): """List widget item selection change handler.""" row = self.current_row() if self.count() and row >= 0: if '</b></big><br>' in self.list.currentItem().text() and row == 0: self.next_row() if self.mode == self.FILE_MODE: try: stack_index = self.paths.index(self.filtered_path[row]) self.plugin = self.widgets[stack_index][1] plugin_index = self.plugins_instances.index(self.plugin) # Count the real index in the tabWidget of the # current plugin real_index = self.get_stack_index(stack_index, plugin_index) self.sig_goto_file.emit( real_index, self.plugin.get_current_tab_manager()) self.goto_line(self.line_number) try: self.plugin.switch_to_plugin() self.raise_() except AttributeError: # The widget using the fileswitcher is not a plugin pass self.edit.setFocus() except ValueError: pass else: line_number = self.filtered_symbol_lines[row] self.goto_line(line_number) def setup_file_list(self, filter_text, current_path): """Setup list widget content for file list display.""" short_paths = shorten_paths(self.paths, self.save_status) paths = self.paths icons = self.icons results = [] trying_for_line_number = ':' in filter_text # Get optional line number if trying_for_line_number: filter_text, line_number = filter_text.split(':') if line_number == '': line_number = None # Get all the available filenames scores = get_search_scores('', self.filenames, template="<b>{0}</b>") else: line_number = None # Get all available filenames and get the scores for # "fuzzy" matching scores = get_search_scores(filter_text, self.filenames, template="<b>{0}</b>") # Get max width to determine if shortpaths should be used max_width = self.get_item_size(paths)[0] self.fix_size(paths) # Build the text that will appear on the list widget for index, score in enumerate(scores): text, rich_text, score_value = score if score_value != -1: text_item = '<big>' + rich_text.replace('&', '') + '</big>' if trying_for_line_number: text_item += " [{0:} {1:}]".format(self.line_count[index], _("lines")) if max_width > self.list.width(): text_item += u"<br><i>{0:}</i>".format(short_paths[index]) else: text_item += u"<br><i>{0:}</i>".format(paths[index]) if (trying_for_line_number and self.line_count[index] != 0 or not trying_for_line_number): results.append((score_value, index, text_item)) # Sort the obtained scores and populate the list widget self.filtered_path = [] plugin = None for result in sorted(results): index = result[1] path = paths[index] icon = icons[index] text = '' try: title = self.widgets[index][1].get_plugin_title().split(' - ') if plugin != title[0]: plugin = title[0] text += '<br><big><b>' + plugin + '</b></big><br>' item = QListWidgetItem(text) item.setToolTip(path) item.setSizeHint(QSize(0, 25)) item.setFlags(Qt.ItemIsEditable) self.list.addItem(item) self.filtered_path.append(path) except: # The widget using the fileswitcher is not a plugin pass text = '' text += result[-1] item = QListWidgetItem(icon, text) item.setToolTip(path) item.setSizeHint(QSize(0, 25)) self.list.addItem(item) self.filtered_path.append(path) # To adjust the delegate layout for KDE themes self.list.files_list = True # Move selected item in list accordingly and update list size if current_path in self.filtered_path: self.set_current_row(self.filtered_path.index(current_path)) elif self.filtered_path: self.set_current_row(0) # If a line number is searched look for it self.line_number = line_number self.goto_line(line_number) def setup_symbol_list(self, filter_text, current_path): """Setup list widget content for symbol list display.""" # Get optional symbol name filter_text, symbol_text = filter_text.split('@') # Fetch the Outline explorer data, get the icons and values oedata = self.get_symbol_list() icons = get_python_symbol_icons(oedata) # The list of paths here is needed in order to have the same # point of measurement for the list widget size as in the file list # See issue 4648 paths = self.paths # Update list size self.fix_size(paths) symbol_list = process_python_symbol_data(oedata) line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list] choices = [item[1] for item in symbol_list] scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>") # Build the text that will appear on the list widget results = [] lines = [] self.filtered_symbol_lines = [] for index, score in enumerate(scores): text, rich_text, score_value = score line, fold_level, token = line_fold_token[index] lines.append(text) if score_value != -1: results.append((score_value, line, text, rich_text, fold_level, icons[index], token)) template = '{0}{1}' for (score, line, text, rich_text, fold_level, icon, token) in sorted(results): fold_space = ' ' * (fold_level) line_number = line + 1 self.filtered_symbol_lines.append(line_number) textline = template.format(fold_space, rich_text) item = QListWidgetItem(icon, textline) item.setSizeHint(QSize(0, 16)) self.list.addItem(item) # To adjust the delegate layout for KDE themes self.list.files_list = False # Select edit line when using symbol search initially. # See issue 5661 self.edit.setFocus() # Move selected item in list accordingly # NOTE: Doing this is causing two problems: # 1. It makes the cursor to auto-jump to the last selected # symbol after opening or closing a different file # 2. It moves the cursor to the first symbol by default, # which is very distracting. # That's why this line is commented! # self.set_current_row(0) def setup(self): """Setup list widget content.""" if len(self.plugins_tabs) == 0: self.close() return self.list.clear() current_path = self.current_path filter_text = self.filter_text # Get optional line or symbol to define mode and method handler trying_for_symbol = ('@' in self.filter_text) if trying_for_symbol: self.mode = self.SYMBOL_MODE self.setup_symbol_list(filter_text, current_path) else: self.mode = self.FILE_MODE self.setup_file_list(filter_text, current_path) # Set position according to size self.set_dialog_position() def show(self): """ Override Qt method to force an update of the fileswitcher before showing it. See Issue #5317 and PR #5389. """ self.setup() super(FileSwitcher, self).show() def add_plugin(self, plugin, tabs, data, icon): """Add a plugin to display its files.""" self.plugins_tabs.append((tabs, plugin)) self.plugins_data.append((data, icon)) self.plugins_instances.append(plugin)
class MacReportWindow(SiriusMainWindow): """Machine Report Window.""" def __init__(self, parent=None): """Init.""" super().__init__(parent) self._macreport = MacReport() self._macreport.connector.timeout = 5 * 60 self.setWindowIcon(qta.icon('fa.book', color='gray')) self.setWindowTitle('Machine Reports') self._fsi = '{:8d}' self._fs1 = '{:8.3f}' self._fs2 = '{:8.3f} ± {:8.3f}' self._fst1 = '{:02d}h{:02d}' self._fst2 = '{:02d}h{:02d} ± {:02d}h{:02d}' self._update_task = None self._setupUi() self.setFocusPolicy(Qt.StrongFocus) self.setFocus(True) def _setupUi(self): cwid = QWidget(self) self.setCentralWidget(cwid) title = QLabel('<h3>Machine Reports</h3>', self, alignment=Qt.AlignCenter) self._timesel_gbox = self._setupTimePeriodSelWidget() self._timesel_gbox.setObjectName('timesel_gbox') self._timesel_gbox.setStyleSheet( "#timesel_gbox{min-height: 8em; max-height: 8em;}") self._progress_list = QListWidget(self) self._progress_list.setObjectName('progress_list') self._progress_list.setStyleSheet( "#progress_list{min-height: 8em; max-height: 8em;}") self._reports_wid = QTabWidget(cwid) self._reports_wid.setObjectName('ASTab') self._reports_wid.addTab(self._setupUserShiftStatsWidget(), 'User Shift Stats') self._reports_wid.addTab(self._setupLightSourceUsageStats(), 'Light Source Usage Stats') self._reports_wid.addTab(self._setupStoredCurrentStats(), 'Stored Current Stats') self._pb_showraw = QPushButton(qta.icon('mdi.chart-line'), 'Show Raw Data', self) self._pb_showraw.setEnabled(False) self._pb_showraw.clicked.connect(self._show_raw_data) self._pb_showpvsd = QPushButton(qta.icon('mdi.chart-line'), 'Show Progrmd.vs.Delivered Hours', self) self._pb_showpvsd.setEnabled(False) self._pb_showpvsd.clicked.connect(self._show_progmd_vs_delivd) lay = QGridLayout(cwid) lay.setVerticalSpacing(10) lay.setHorizontalSpacing(10) lay.setContentsMargins(18, 9, 18, 9) lay.addWidget(title, 0, 0, 1, 3) lay.addWidget(self._timesel_gbox, 1, 0) lay.addWidget(self._progress_list, 1, 1, 1, 2, alignment=Qt.AlignBottom) lay.addWidget(self._reports_wid, 2, 0, 1, 3) lay.addWidget(self._pb_showpvsd, 4, 0, alignment=Qt.AlignLeft) lay.addWidget(self._pb_showraw, 4, 2, alignment=Qt.AlignRight) self._updateUserShiftStats(setup=True) self._updateStoredCurrentStats(setup=True) self._updateLightSourceUsageStats(setup=True) def _setupTimePeriodSelWidget(self): tnow = Time.now() ld_tstart = QLabel('Time start: ') self.dt_start = QDateTimeEdit(tnow - 10 * 60, self) self.dt_start.setCalendarPopup(True) self.dt_start.setMinimumDate(Time(2020, 1, 1)) self.dt_start.setDisplayFormat('dd/MM/yyyy hh:mm') ld_tstop = QLabel('Time stop: ') self.dt_stop = QDateTimeEdit(tnow, self) self.dt_stop.setCalendarPopup(True) self.dt_stop.setMinimumDate(Time(2020, 1, 1)) self.dt_stop.setDisplayFormat('dd/MM/yyyy hh:mm') self.pb_search = QPushButton(qta.icon('fa5s.search'), 'Search', self) self.pb_search.clicked.connect(self._do_update) self.pb_search.setObjectName('pb_search') self.pb_search.setStyleSheet(""" #pb_search{ min-width:100px; max-width:100px; min-height:25px; max-height:25px; icon-size:20px;} """) wid = QGroupBox('Select interval: ', self) lay = QGridLayout(wid) lay.addWidget(ld_tstart, 0, 0) lay.addWidget(self.dt_start, 0, 1) lay.addWidget(ld_tstop, 1, 0) lay.addWidget(self.dt_stop, 1, 1) lay.addWidget(self.pb_search, 2, 1, alignment=Qt.AlignRight) return wid def _setupUserShiftStatsWidget(self): self.lb_uspt = LbData('') self.lb_usdt = LbData('') self.lb_ustt = LbData('') self.lb_uset = LbData('') self.lb_uspc = LbData('') self.lb_cav = LbData('') self.lb_cbav = LbData('') self.lb_ceav = LbData('') self.lb_tft = LbData('') self.lb_bdc = LbData('') self.lb_mttr = LbData('') self.lb_mtbf = LbData('') self.lb_reli = LbData('') self.lb_tsbt = LbData('') self.lb_tubt = LbData('') self.lb_mtbu = LbData('') self.lb_rsbt = LbData('') self.lb_itav = LbData('') wid = QWidget(self) lay = QGridLayout(wid) lay.setVerticalSpacing(0) lay.setHorizontalSpacing(0) lay.setAlignment(Qt.AlignTop) lay.addItem(QSpacerItem(120, 1, QSzPlcy.Fixed, QSzPlcy.Ignored), 0, 0) lay.addWidget(LbHHeader('Programmed Time (h)'), 0, 1) lay.addWidget(self.lb_uspt, 0, 2) lay.addWidget(LbHHeader('Delivered Time (h)'), 1, 1) lay.addWidget(self.lb_usdt, 1, 2) lay.addWidget(LbHHeader('Total Time (h)'), 2, 1) lay.addWidget(self.lb_ustt, 2, 2) lay.addWidget(LbHHeader('Extra Time (h)'), 3, 1) lay.addWidget(self.lb_uset, 3, 2) lay.addWidget(LbHHeader('# Programmed Shifts'), 4, 1) lay.addWidget(self.lb_uspc, 4, 2) lay.addWidget(LbHHeader('Current (avg ± std) (mA)'), 5, 1) lay.addWidget(self.lb_cav, 5, 2) lay.addWidget( LbHHeader('Current at the Beg. of the Shift (avg ± std) (mA)'), 6, 1) lay.addWidget(self.lb_cbav, 6, 2) lay.addWidget( LbHHeader('Current at the End of the Shift (avg ± std) (mA)'), 7, 1) lay.addWidget(self.lb_ceav, 7, 2) lay.addWidget(LbHHeader('Total Failures Time (h)'), 8, 1) lay.addWidget(self.lb_tft, 8, 2) lay.addWidget(LbHHeader('# Beam Dumps'), 9, 1) lay.addWidget(self.lb_bdc, 9, 2) lay.addWidget(LbHHeader('Time To Recover (avg ± std) (h)'), 10, 1) lay.addWidget(self.lb_mttr, 10, 2) lay.addWidget(LbHHeader('Time Between Failures (avg) (h)'), 11, 1) lay.addWidget(self.lb_mtbf, 11, 2) lay.addWidget(LbHHeader('Beam Reliability (%)'), 12, 1) lay.addWidget(self.lb_reli, 12, 2) lay.addWidget(LbHHeader('Total stable beam time (h)'), 13, 1) lay.addWidget(self.lb_tsbt, 13, 2) lay.addWidget(LbHHeader('Total unstable beam time (h)'), 14, 1) lay.addWidget(self.lb_tubt, 14, 2) lay.addWidget(LbHHeader('Time between unstable beams (avg) (h)'), 15, 1) lay.addWidget(self.lb_mtbu, 15, 2) lay.addWidget(LbHHeader('Relative stable beam time (%)'), 16, 1) lay.addWidget(self.lb_rsbt, 16, 2) lay.addWidget(LbHHeader('Injection time (avg ± std) (h)'), 17, 1) lay.addWidget(self.lb_itav, 17, 2) lay.addItem(QSpacerItem(120, 1, QSzPlcy.Fixed, QSzPlcy.Ignored), 0, 3) return wid def _updateUserShiftStats(self, setup=False): w2r = { 'uspt': [ 'usershift_progmd_time', ], 'usdt': [ 'usershift_delivd_time', ], 'ustt': [ 'usershift_total_time', ], 'uset': [ 'usershift_extra_time', ], 'uspc': [ 'usershift_progmd_count', ], 'cav': ['usershift_current_average', 'usershift_current_stddev'], 'cbav': ['usershift_current_beg_average', 'usershift_current_beg_stddev'], 'ceav': ['usershift_current_end_average', 'usershift_current_end_stddev'], 'tft': [ 'usershift_total_failures_time', ], 'bdc': [ 'usershift_beam_dump_count', ], 'mttr': [ 'usershift_time_to_recover_average', 'usershift_time_to_recover_stddev' ], 'mtbf': [ 'usershift_time_between_failures_average', ], 'reli': [ 'usershift_beam_reliability', ], 'tsbt': [ 'usershift_total_stable_beam_time', ], 'tubt': [ 'usershift_total_unstable_beam_time', ], 'mtbu': ['usershift_time_between_unstable_beams_average'], 'rsbt': ['usershift_relative_stable_beam_time'], 'itav': [ 'usershift_injection_time_average', 'usershift_injection_time_stddev' ] } for wname, rname in w2r.items(): wid = getattr(self, 'lb_' + wname) items = [getattr(self._macreport, n) for n in rname] if 'time' in rname[0] and 'relative' not in rname[0]: if len(items) == 2: if items[0] not in [None, _np.inf]: hour1 = int(items[0]) minu1 = int((items[0] - hour1) * 60) hour2 = int(items[1]) minu2 = int((items[1] - hour2) * 60) items = [hour1, minu1, hour2, minu2] str2fmt = self._fst2 else: str2fmt = self._fs1 elif items[0] not in [None, _np.inf]: hour = int(items[0]) minu = int((items[0] - hour) * 60) items = [hour, minu] str2fmt = self._fst1 else: str2fmt = self._fs1 else: str2fmt = getattr( self, '_fsi' if 'count' in rname[0] else '_fs' + str(len(rname))) text = '' if any([i is None for i in items]) \ else str2fmt.format(*items) wid.setText(text) if setup: wid.setToolTip(getattr(MacReport, rname[0]).__doc__) def _setupStoredCurrentStats(self): self.lb_user_mb_avg = LbData('') self.lb_user_mb_intvl = LbData('') self.lb_user_sb_avg = LbData('') self.lb_user_sb_intvl = LbData('') self.lb_user_tt_avg = LbData('') self.lb_user_tt_intvl = LbData('') self.lb_commi_mb_avg = LbData('') self.lb_commi_mb_intvl = LbData('') self.lb_commi_sb_avg = LbData('') self.lb_commi_sb_intvl = LbData('') self.lb_commi_tt_avg = LbData('') self.lb_commi_tt_intvl = LbData('') self.lb_condi_mb_avg = LbData('') self.lb_condi_mb_intvl = LbData('') self.lb_condi_sb_avg = LbData('') self.lb_condi_sb_intvl = LbData('') self.lb_condi_tt_avg = LbData('') self.lb_condi_tt_intvl = LbData('') self.lb_mstdy_mb_avg = LbData('') self.lb_mstdy_mb_intvl = LbData('') self.lb_mstdy_sb_avg = LbData('') self.lb_mstdy_sb_intvl = LbData('') self.lb_mstdy_tt_avg = LbData('') self.lb_mstdy_tt_intvl = LbData('') self.lb_stord_mb_avg = LbData('') self.lb_stord_mb_intvl = LbData('') self.lb_stord_sb_avg = LbData('') self.lb_stord_sb_intvl = LbData('') self.lb_stord_tt_avg = LbData('') self.lb_stord_tt_intvl = LbData('') wid = QWidget(self) lay = QGridLayout(wid) lay.setVerticalSpacing(0) lay.setHorizontalSpacing(0) lay.setAlignment(Qt.AlignTop) lay.addWidget(LbHHeader('Current (avg ± std) (mA) (MB)'), 1, 0) lay.addWidget(LbHHeader('Time in MB mode (h)'), 2, 0) lay.addWidget(LbHHeader('Current (avg ± std) (mA) (SB)'), 3, 0) lay.addWidget(LbHHeader('Time in SB mode (h)'), 4, 0) lay.addWidget(LbHHeader('Current (avg ± std) (mA) (SB+MB)'), 5, 0) lay.addWidget(LbHHeader('Total Time (h) (SB+MB)'), 6, 0) lay.addWidget(LbVHeader('Users'), 0, 1) lay.addWidget(self.lb_user_mb_avg, 1, 1) lay.addWidget(self.lb_user_mb_intvl, 2, 1) lay.addWidget(self.lb_user_sb_avg, 3, 1) lay.addWidget(self.lb_user_sb_intvl, 4, 1) lay.addWidget(self.lb_user_tt_avg, 5, 1) lay.addWidget(self.lb_user_tt_intvl, 6, 1) lay.addWidget(LbVHeader('Commissioning'), 0, 2) lay.addWidget(self.lb_commi_mb_avg, 1, 2) lay.addWidget(self.lb_commi_mb_intvl, 2, 2) lay.addWidget(self.lb_commi_sb_avg, 3, 2) lay.addWidget(self.lb_commi_sb_intvl, 4, 2) lay.addWidget(self.lb_commi_tt_avg, 5, 2) lay.addWidget(self.lb_commi_tt_intvl, 6, 2) lay.addWidget(LbVHeader('Conditioning'), 0, 3) lay.addWidget(self.lb_condi_mb_avg, 1, 3) lay.addWidget(self.lb_condi_mb_intvl, 2, 3) lay.addWidget(self.lb_condi_sb_avg, 3, 3) lay.addWidget(self.lb_condi_sb_intvl, 4, 3) lay.addWidget(self.lb_condi_tt_avg, 5, 3) lay.addWidget(self.lb_condi_tt_intvl, 6, 3) lay.addWidget(LbVHeader('Machine Study'), 0, 4) lay.addWidget(self.lb_mstdy_mb_avg, 1, 4) lay.addWidget(self.lb_mstdy_mb_intvl, 2, 4) lay.addWidget(self.lb_mstdy_sb_avg, 3, 4) lay.addWidget(self.lb_mstdy_sb_intvl, 4, 4) lay.addWidget(self.lb_mstdy_tt_avg, 5, 4) lay.addWidget(self.lb_mstdy_tt_intvl, 6, 4) lay.addWidget(LbVHeader('All Stored Beam'), 0, 5) lay.addWidget(self.lb_stord_mb_avg, 1, 5) lay.addWidget(self.lb_stord_mb_intvl, 2, 5) lay.addWidget(self.lb_stord_sb_avg, 3, 5) lay.addWidget(self.lb_stord_sb_intvl, 4, 5) lay.addWidget(self.lb_stord_tt_avg, 5, 5) lay.addWidget(self.lb_stord_tt_intvl, 6, 5) return wid def _updateStoredCurrentStats(self, setup=False): shifttype = { 'mstdy': 'machinestudy', 'commi': 'commissioning', 'condi': 'conditioning', 'stord': 'ebeam', 'user': '******' } fillmode = {'mb': 'multibunch', 'sb': 'singlebunch', 'tt': 'total'} stats = { 'avg': ['average', 'stddev'], 'intvl': [ 'time', ] } for wsht, rsht in shifttype.items(): for wfm, rfm in fillmode.items(): for wstt, rstt in stats.items(): wid = getattr(self, 'lb_' + wsht + '_' + wfm + '_' + wstt) pname = 'current_' + rsht + '_' + rfm + '_' items = [getattr(self._macreport, pname + i) for i in rstt] if 'time' in rstt[0] and items[0] is not None: hour = int(items[0]) minu = int((items[0] - hour) * 60) items = [hour, minu] str2fmt = getattr( self, '_fst1' if ('time' in rstt[0]) else '_fs' + str(len(rstt))) text = '' if any([i is None for i in items]) \ else str2fmt.format(*items) wid.setText(text) if setup: wid.setToolTip( getattr(MacReport, pname + rstt[0]).__doc__) def _setupLightSourceUsageStats(self): self.lb_mstdy_fail_intvl = LbData('') self.lb_mstdy_fail_pcntl = LbData('') self.lb_mstdy_oper_intvl = LbData('') self.lb_mstdy_oper_pcntl = LbData('') self.lb_mstdy_total_intvl = LbData('') self.lb_mstdy_total_pcntl = LbData('') self.lb_commi_fail_intvl = LbData('') self.lb_commi_fail_pcntl = LbData('') self.lb_commi_oper_intvl = LbData('') self.lb_commi_oper_pcntl = LbData('') self.lb_commi_total_intvl = LbData('') self.lb_commi_total_pcntl = LbData('') self.lb_condi_fail_intvl = LbData('') self.lb_condi_fail_pcntl = LbData('') self.lb_condi_oper_intvl = LbData('') self.lb_condi_oper_pcntl = LbData('') self.lb_condi_total_intvl = LbData('') self.lb_condi_total_pcntl = LbData('') self.lb_maint_fail_intvl = LbData('') self.lb_maint_fail_pcntl = LbData('') self.lb_maint_oper_intvl = LbData('') self.lb_maint_oper_pcntl = LbData('') self.lb_maint_total_intvl = LbData('') self.lb_maint_total_pcntl = LbData('') self.lb_user_fail_intvl = LbData('') self.lb_user_fail_pcntl = LbData('') self.lb_user_oper_intvl = LbData('') self.lb_user_oper_pcntl = LbData('') self.lb_user_total_intvl = LbData('') self.lb_user_total_pcntl = LbData('') self.lb_total_intvl = LbHHeader('Total Usage Time (h): - ') wid = QWidget(self) lay = QGridLayout(wid) lay.setVerticalSpacing(0) lay.setHorizontalSpacing(0) lay.setAlignment(Qt.AlignTop) lay.addWidget(LbHHeader('Operational Time (h)'), 1, 0) lay.addWidget(LbHHeader('Operational Percentage (%)'), 2, 0) lay.addWidget(LbHHeader('Failures Time (h)'), 3, 0) lay.addWidget(LbHHeader('Failures Percentage (%)'), 4, 0) lay.addWidget(LbHHeader('Shift Time (h)'), 5, 0) lay.addWidget(LbHHeader('Shift Percentage (%)'), 6, 0) lay.addWidget(self.lb_total_intvl, 7, 0, 1, 6) lay.addWidget(LbVHeader('Users'), 0, 1) lay.addWidget(self.lb_user_oper_intvl, 1, 1) lay.addWidget(self.lb_user_oper_pcntl, 2, 1) lay.addWidget(self.lb_user_fail_intvl, 3, 1) lay.addWidget(self.lb_user_fail_pcntl, 4, 1) lay.addWidget(self.lb_user_total_intvl, 5, 1) lay.addWidget(self.lb_user_total_pcntl, 6, 1) lay.addWidget(LbVHeader('Commissioning'), 0, 2) lay.addWidget(self.lb_commi_oper_intvl, 1, 2) lay.addWidget(self.lb_commi_oper_pcntl, 2, 2) lay.addWidget(self.lb_commi_fail_intvl, 3, 2) lay.addWidget(self.lb_commi_fail_pcntl, 4, 2) lay.addWidget(self.lb_commi_total_intvl, 5, 2) lay.addWidget(self.lb_commi_total_pcntl, 6, 2) lay.addWidget(LbVHeader('Conditioning'), 0, 3) lay.addWidget(self.lb_condi_oper_intvl, 1, 3) lay.addWidget(self.lb_condi_oper_pcntl, 2, 3) lay.addWidget(self.lb_condi_fail_intvl, 3, 3) lay.addWidget(self.lb_condi_fail_pcntl, 4, 3) lay.addWidget(self.lb_condi_total_intvl, 5, 3) lay.addWidget(self.lb_condi_total_pcntl, 6, 3) lay.addWidget(LbVHeader('Machine Study'), 0, 4) lay.addWidget(self.lb_mstdy_oper_intvl, 1, 4) lay.addWidget(self.lb_mstdy_oper_pcntl, 2, 4) lay.addWidget(self.lb_mstdy_fail_intvl, 3, 4) lay.addWidget(self.lb_mstdy_fail_pcntl, 4, 4) lay.addWidget(self.lb_mstdy_total_intvl, 5, 4) lay.addWidget(self.lb_mstdy_total_pcntl, 6, 4) lay.addWidget(LbVHeader('Maintenance'), 0, 5) lay.addWidget(self.lb_maint_oper_intvl, 1, 5) lay.addWidget(self.lb_maint_oper_pcntl, 2, 5) lay.addWidget(self.lb_maint_fail_intvl, 3, 5) lay.addWidget(self.lb_maint_fail_pcntl, 4, 5) lay.addWidget(self.lb_maint_total_intvl, 5, 5) lay.addWidget(self.lb_maint_total_pcntl, 6, 5) return wid def _updateLightSourceUsageStats(self, setup=False): shifttype = { 'mstdy': 'machinestudy', 'commi': 'commissioning', 'condi': 'conditioning', 'maint': 'maintenance', 'user': '******' } intervaltype = { 'fail': '_failures', 'oper': '_operational', 'total': '' } for wst, rst in shifttype.items(): for wit, rit in intervaltype.items(): widt = getattr(self, 'lb_' + wst + '_' + wit + '_intvl') tname = 'lsusage_' + rst + rit + '_time' tval = getattr(self._macreport, tname) if tval is None: text = '' else: hour = int(tval) minu = int((tval - hour) * 60) text = self._fst1.format(hour, minu) widt.setText(text) if setup: widt.setToolTip(getattr(MacReport, tname).__doc__) widp = getattr(self, 'lb_' + wst + '_' + wit + '_pcntl') pname = 'lsusage_' + rst + rit pval = getattr(self._macreport, pname) text = '' if pval is None else self._fs1.format(pval) widp.setText(text) if setup: widp.setToolTip(getattr(MacReport, pname).__doc__) text = 'Total Usage Time (h): ' if self._macreport.lsusage_total_time is not None: val = self._macreport.lsusage_total_time hour = int(val) minu = int((val - hour) * 60) text += self._fst1.format(hour, minu) self.lb_total_intvl.setText(text) def _do_update(self): if self.sender().text() == 'Abort': self._update_task.terminate() now = Time.now().strftime('%Y/%m/%d-%H:%M:%S') item = QListWidgetItem(now + ' Aborted.') self._progress_list.addItem(item) self._progress_list.scrollToBottom() self._setup_search_button() else: if self.dt_start.dateTime() >= self.dt_stop.dateTime() or \ self.dt_start.dateTime() > Time.now() or \ self.dt_stop.dateTime() > Time.now(): QMessageBox.warning(self, 'Ops...', 'Insert a valid time interval.') return self._macreport.timestamp_start = \ self.dt_start.dateTime().toSecsSinceEpoch() self._macreport.timestamp_stop = \ self.dt_stop.dateTime().toSecsSinceEpoch() self._progress_list.clear() self._pb_showraw.setEnabled(False) self._pb_showpvsd.setEnabled(False) self._setup_search_button() self._update_task = UpdateTask(self._macreport) self._update_task.updated.connect(self._update_progress) self._update_task.start() def _update_progress(self, message): item = QListWidgetItem(message) self._progress_list.addItem(item) self._progress_list.scrollToBottom() if 'Collected' in message: self._setup_search_button() self._updateUserShiftStats() self._updateStoredCurrentStats() self._updateLightSourceUsageStats() self._pb_showraw.setEnabled(True) self._pb_showpvsd.setEnabled(True) def _setup_search_button(self): if self.pb_search.text() == 'Abort': self.pb_search.setIcon(qta.icon('fa5s.search')) self.pb_search.setText('Search') else: self.pb_search.setIcon( qta.icon('fa5s.spinner', animation=qta.Spin(self.pb_search))) self.pb_search.setText('Abort') def _show_raw_data(self): fig = self._macreport.plot_raw_data() wid = MatplotlibWidget(fig) wid.setWindowTitle('Machine Reports - Raw Data (' + str(self._macreport.time_start) + ' -> ' + str(self._macreport.time_stop) + ')') wid.show() def _show_progmd_vs_delivd(self): fig = self._macreport.plot_progmd_vs_delivd_hours() wid = MatplotlibWidget(fig) wid.setWindowTitle( 'Machine Reports - Programmed vs. Delivered Hours (' + str(self._macreport.time_start) + ' -> ' + str(self._macreport.time_stop) + ')') wid.show()
def refresh_profiles(list_widget: QListWidget, new_values: typing.List[str], index: int): list_widget.clear() list_widget.addItems(new_values) if index != -1: list_widget.setCurrentRow(index)
class PSTestWindow(SiriusMainWindow): """PS test window.""" def __init__(self, parent=None, adv_mode=False): """Constructor.""" super().__init__(parent) self.setWindowTitle('PS/PU Test') self.setObjectName('ASApp') cor = get_appropriate_color(section='AS') self.setWindowIcon(qta.icon('mdi.test-tube', color=cor)) # auxiliar data for initializing SI Fam PS self._is_adv_mode = adv_mode self._si_fam_psnames = PSSearch.get_psnames(filters={ 'sec': 'SI', 'sub': 'Fam', 'dis': 'PS' }) # auxiliary data for SI fast correctors self._si_fastcorrs = PSSearch.get_psnames(filters={ 'sec': 'SI', 'dis': 'PS', 'dev': 'FC.*' }) self._needs_update_setup = False self._setup_ui() self._update_setup_timer = QTimer(self) self._update_setup_timer.timeout.connect(self._update_setup) self._update_setup_timer.setInterval(250) self._update_setup_timer.start() def _setup_ui(self): # setup central widget self.central_widget = QFrame() self.central_widget.setStyleSheet(""" #OkList { background-color: #eafaea; } #NokList { background-color: #ffebe6; } QLabel{ max-height: 1.29em; } QTabWidget::pane { border-left: 2px solid gray; border-bottom: 2px solid gray; border-right: 2px solid gray; }""") self.setCentralWidget(self.central_widget) self.tab = QTabWidget(self) self.tab.setObjectName('ASTab') # # PS self.ps_wid = QWidget(self) lay_ps = QGridLayout(self.ps_wid) lay_ps.setContentsMargins(0, 9, 0, 0) lay_ps.setHorizontalSpacing(0) # PS selection self.ps_tree = PVNameTree(items=self._get_ps_tree_names(), tree_levels=('sec', 'mag_group'), parent=self) self.ps_tree.tree.setHeaderHidden(True) self.ps_tree.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.ps_tree.tree.setColumnCount(1) self.ps_tree.tree.doubleClicked.connect(self._open_detail) self.ps_tree.tree.itemChanged.connect( self._handle_checked_items_changed) gbox_ps_select = QGroupBox('Select PS: ', self) gbox_ps_select.setObjectName('select') gbox_ps_select.setStyleSheet(""" #select{ border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; }""") lay_ps_select = QVBoxLayout(gbox_ps_select) lay_ps_select.addWidget(self.ps_tree) lay_ps.addWidget(gbox_ps_select, 0, 0) lay_ps.setColumnStretch(0, 1) # PS commands self.checkcomm_ps_bt = QPushButton('Check Communication', self) self.checkcomm_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.checkcomm_ps_bt.clicked.connect(self._check_comm) self.checkcomm_ps_bt.setToolTip( 'Check PS and DCLinks communication status (verify invalid alarms ' 'and, if LI, the value of Connected-Mon PV)') self.checkstatus_ps_bt = QPushButton('Show Status Summary', self) self.checkstatus_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.checkstatus_ps_bt.clicked.connect(_part(self._check_status, 'PS')) self.checkstatus_ps_bt.setToolTip( 'Check PS and DCLinks interlock status and, if powered on, ' 'check if it is following reference') self.dsbltrigger_ps_bt = QPushButton('Disable PS triggers', self) self.dsbltrigger_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.dsbltrigger_ps_bt.clicked.connect( _part(self._set_check_trigger_state, 'PS', 'dsbl')) self.setsofbmode_ps_bt = QPushButton('Turn Off SOFBMode', self) self.setsofbmode_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.setsofbmode_ps_bt.clicked.connect( _part(self._set_check_fbp_sofbmode, 'off')) self.setslowref_ps_bt = QPushButton('Set PS and DCLinks to SlowRef', self) self.setslowref_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.setslowref_ps_bt.clicked.connect(self._set_check_opmode_slowref) self.currzero_ps_bt1 = QPushButton('Set PS Current to zero', self) self.currzero_ps_bt1.clicked.connect(_part(self._set_lastcomm, 'PS')) self.currzero_ps_bt1.clicked.connect(self._set_zero_ps) self.reset_ps_bt = QPushButton('Reset PS and DCLinks', self) self.reset_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.reset_ps_bt.clicked.connect(_part(self._reset_intlk, 'PS')) self.prep_sidclink_bt = QPushButton('Prepare SI Fam DCLinks', self) self.prep_sidclink_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.prep_sidclink_bt.clicked.connect(self._prepare_sidclinks) self.prep_sidclink_bt.setVisible(False) self.init_sips_bt = QPushButton('Initialize SI Fam PS', self) self.init_sips_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.init_sips_bt.clicked.connect(self._set_check_pwrstateinit) self.init_sips_bt.setVisible(False) self.aux_label = QLabel('') self.aux_label.setVisible(False) self.turnon_dcl_bt = QPushButton('Turn DCLinks On', self) self.turnon_dcl_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.turnon_dcl_bt.clicked.connect( _part(self._set_check_pwrstate_dclinks, 'on')) self.checkctrlloop_dcl_bt = QPushButton('Check DCLinks CtrlLoop', self) self.checkctrlloop_dcl_bt.clicked.connect( _part(self._set_lastcomm, 'PS')) self.checkctrlloop_dcl_bt.clicked.connect( _part(self._set_check_ctrlloop, 'dclink')) self.setvolt_dcl_bt = QPushButton('Set DCLinks Voltage', self) self.setvolt_dcl_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.setvolt_dcl_bt.clicked.connect(self._set_check_dclinks_capvolt) self.turnon_ps_bt = QPushButton('Turn PS On', self) self.turnon_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.turnon_ps_bt.clicked.connect( _part(self._set_check_pwrstate, 'PS', 'on', True)) self.setcheckctrlloop_ps_bt = QPushButton('Check PS CtrlLoop', self) self.setcheckctrlloop_ps_bt.clicked.connect( _part(self._set_lastcomm, 'PS')) self.setcheckctrlloop_ps_bt.clicked.connect( _part(self._set_check_ctrlloop, 'pwrsupply')) self.test_ps_bt = QPushButton('Set PS Current to test value', self) self.test_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS')) self.test_ps_bt.clicked.connect(self._set_test_ps) self.currzero_ps_bt2 = QPushButton('Set PS Current to zero', self) self.currzero_ps_bt2.clicked.connect(_part(self._set_lastcomm, 'PS')) self.currzero_ps_bt2.clicked.connect(self._set_zero_ps) self.restoretrigger_ps_bt = QPushButton('Restore PS triggers', self) self.restoretrigger_ps_bt.clicked.connect( _part(self._set_lastcomm, 'PS')) self.restoretrigger_ps_bt.clicked.connect( _part(self._restore_triggers_state, 'PS')) gbox_ps_comm = QGroupBox('Commands', self) gbox_ps_comm.setObjectName('comm') gbox_ps_comm.setStyleSheet('#comm{border: 0px solid transparent;}') lay_ps_comm = QVBoxLayout(gbox_ps_comm) lay_ps_comm.setContentsMargins(20, 9, 20, 9) lay_ps_comm.addWidget(QLabel('')) lay_ps_comm.addWidget( QLabel('<h4>Check</h4>', self, alignment=Qt.AlignCenter)) lay_ps_comm.addWidget(self.checkcomm_ps_bt) lay_ps_comm.addWidget(self.checkstatus_ps_bt) lay_ps_comm.addWidget(QLabel('')) lay_ps_comm.addWidget( QLabel('<h4>Prepare</h4>', self, alignment=Qt.AlignCenter)) lay_ps_comm.addWidget(self.dsbltrigger_ps_bt) lay_ps_comm.addWidget(self.setsofbmode_ps_bt) lay_ps_comm.addWidget(self.setslowref_ps_bt) lay_ps_comm.addWidget(self.currzero_ps_bt1) lay_ps_comm.addWidget(self.reset_ps_bt) lay_ps_comm.addWidget(QLabel('')) lay_ps_comm.addWidget(self.prep_sidclink_bt) lay_ps_comm.addWidget(self.init_sips_bt) lay_ps_comm.addWidget(self.aux_label) lay_ps_comm.addWidget( QLabel('<h4>Config DCLinks</h4>', self, alignment=Qt.AlignCenter)) lay_ps_comm.addWidget(self.turnon_dcl_bt) lay_ps_comm.addWidget(self.checkctrlloop_dcl_bt) lay_ps_comm.addWidget(self.setvolt_dcl_bt) lay_ps_comm.addWidget(QLabel('')) lay_ps_comm.addWidget( QLabel('<h4>Test</h4>', self, alignment=Qt.AlignCenter)) lay_ps_comm.addWidget(self.turnon_ps_bt) lay_ps_comm.addWidget(self.setcheckctrlloop_ps_bt) lay_ps_comm.addWidget(self.test_ps_bt) lay_ps_comm.addWidget(self.currzero_ps_bt2) lay_ps_comm.addWidget(QLabel('')) lay_ps_comm.addWidget( QLabel('<h4>Restore</h4>', self, alignment=Qt.AlignCenter)) lay_ps_comm.addWidget(self.restoretrigger_ps_bt) lay_ps_comm.addWidget(QLabel('')) lay_ps.addWidget(gbox_ps_comm, 0, 1) lay_ps.setColumnStretch(1, 1) self.tab.addTab(self.ps_wid, 'PS') # # PU self.pu_wid = QWidget(self) lay_pu = QGridLayout(self.pu_wid) lay_pu.setContentsMargins(0, 9, 0, 0) lay_pu.setHorizontalSpacing(0) # PU selection self.pu_tree = PVNameTree(items=self._get_pu_tree_names(), tree_levels=('sec', ), parent=self) self.pu_tree.tree.setHeaderHidden(True) self.pu_tree.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.pu_tree.tree.setColumnCount(1) self.pu_tree.tree.doubleClicked.connect(self._open_detail) gbox_pu_select = QGroupBox('Select PU: ', self) gbox_pu_select.setObjectName('select') gbox_pu_select.setStyleSheet(""" #select{ border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; }""") lay_pu_select = QVBoxLayout(gbox_pu_select) lay_pu_select.addWidget(self.pu_tree) lay_pu.addWidget(gbox_pu_select, 0, 0) lay_pu.setColumnStretch(0, 1) # PU commands self.checkstatus_pu_bt = QPushButton('Show Status Summary', self) self.checkstatus_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU')) self.checkstatus_pu_bt.clicked.connect(_part(self._check_status, 'PU')) self.checkstatus_pu_bt.setToolTip( 'Check PU interlock status and, if powered on, ' 'check if it is following voltage setpoint') self.voltzero_pu_bt1 = QPushButton('Set PU Voltage to zero', self) self.voltzero_pu_bt1.clicked.connect(_part(self._set_lastcomm, 'PU')) self.voltzero_pu_bt1.clicked.connect(_part(self._set_zero_pu, False)) self.reset_pu_bt = QPushButton('Reset PU', self) self.reset_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU')) self.reset_pu_bt.clicked.connect(_part(self._reset_intlk, 'PU')) self.turnon_pu_bt = QPushButton('Turn PU On', self) self.turnon_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU')) self.turnon_pu_bt.clicked.connect( _part(self._set_check_pwrstate, 'PU', 'on', True)) self.enblpulse_pu_bt = QPushButton('Enable PU Pulse', self) self.enblpulse_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU')) self.enblpulse_pu_bt.clicked.connect(_part(self._set_check_pulse, 'on')) self.enbltrigger_pu_bt = QPushButton('Enable PU triggers', self) self.enbltrigger_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU')) self.enbltrigger_pu_bt.clicked.connect( _part(self._set_check_trigger_state, 'PU', 'on')) self.test_pu_bt = QPushButton('Set PU Voltage to test value', self) self.test_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU')) self.test_pu_bt.clicked.connect(self._set_test_pu) self.voltzero_pu_bt2 = QPushButton('Set PU Voltage to zero', self) self.voltzero_pu_bt2.clicked.connect(_part(self._set_lastcomm, 'PU')) self.voltzero_pu_bt2.clicked.connect(_part(self._set_zero_pu, True)) self.restoretrigger_pu_bt = QPushButton('Restore PU triggers', self) self.restoretrigger_pu_bt.clicked.connect( _part(self._set_lastcomm, 'PU')) self.restoretrigger_pu_bt.clicked.connect( _part(self._restore_triggers_state, 'PU')) gbox_pu_comm = QGroupBox('Commands', self) gbox_pu_comm.setObjectName('comm') gbox_pu_comm.setStyleSheet('#comm{border: 0px solid transparent;}') lay_pu_comm = QVBoxLayout(gbox_pu_comm) lay_pu_comm.setContentsMargins(20, 9, 20, 9) lay_pu_comm.addWidget(QLabel('')) lay_pu_comm.addWidget( QLabel('<h4>Check</h4>', self, alignment=Qt.AlignCenter)) lay_pu_comm.addWidget(self.checkstatus_pu_bt) lay_pu_comm.addWidget(QLabel('')) lay_pu_comm.addWidget( QLabel('<h4>Prepare</h4>', self, alignment=Qt.AlignCenter)) lay_pu_comm.addWidget(self.voltzero_pu_bt1) lay_pu_comm.addWidget(self.reset_pu_bt) lay_pu_comm.addWidget(QLabel('')) lay_pu_comm.addWidget( QLabel('<h4>Test</h4>', self, alignment=Qt.AlignCenter)) lay_pu_comm.addWidget(self.turnon_pu_bt) lay_pu_comm.addWidget(self.enblpulse_pu_bt) lay_pu_comm.addWidget(self.enbltrigger_pu_bt) lay_pu_comm.addWidget(self.test_pu_bt) lay_pu_comm.addWidget(self.voltzero_pu_bt2) lay_pu_comm.addWidget(QLabel('')) lay_pu_comm.addWidget( QLabel('<h4>Restore</h4>', self, alignment=Qt.AlignCenter)) lay_pu_comm.addWidget(self.restoretrigger_pu_bt) lay_pu_comm.addWidget(QLabel('')) lay_pu.addWidget(gbox_pu_comm, 0, 1) lay_pu.setColumnStretch(1, 1) self.tab.addTab(self.pu_wid, 'PU') # lists self.label_lastcomm = QLabel('Last Command: ', self) self.ok_ps = QListWidget(self) self.ok_ps.setObjectName('OkList') self.ok_ps.doubleClicked.connect(self._open_detail) self.ok_ps.setSelectionMode(QAbstractItemView.MultiSelection) self.ok_ps.setToolTip( 'Select rows and press Ctrl+C to copy and Esc to deselect.') self.nok_ps = QListWidget(self) self.nok_ps.setObjectName('NokList') self.nok_ps.doubleClicked.connect(self._open_detail) self.nok_ps.setSelectionMode(QAbstractItemView.MultiSelection) self.nok_ps.setToolTip( 'Select rows and press Ctrl+C to copy and Esc to deselect.') self.clearlists_bt = QPushButton('Clear', self) self.clearlists_bt.clicked.connect(self._clear_lastcomm) self.ok_ps_aux_list = list() self.nok_ps_aux_list = list() hbox = QHBoxLayout() hbox.addWidget(self.label_lastcomm) hbox.addWidget(self.clearlists_bt, alignment=Qt.AlignRight) list_layout = QGridLayout() list_layout.setContentsMargins(0, 0, 0, 0) list_layout.setVerticalSpacing(6) list_layout.setHorizontalSpacing(9) list_layout.addLayout(hbox, 0, 0, 1, 2) list_layout.addWidget( QLabel('<h4>Ok</h4>', self, alignment=Qt.AlignCenter), 1, 0) list_layout.addWidget(self.ok_ps, 2, 0) list_layout.addWidget( QLabel('<h4>Failed</h4>', self, alignment=Qt.AlignCenter), 1, 1) list_layout.addWidget(self.nok_ps, 2, 1) # menu self.menu = self.menuBar() self.act_cycle = self.menu.addAction('Open Cycle Window') connect_newprocess(self.act_cycle, 'sirius-hla-as-ps-cycle.py', parent=self) self.aux_comm = self.menu.addMenu('Auxiliary commands') # # auxiliary PS self.ps_menu = self.aux_comm.addMenu('PS') self.act_turnoff_ps = self.ps_menu.addAction('Turn PS Off') self.act_turnoff_ps.triggered.connect(_part(self._set_lastcomm, 'PS')) self.act_turnoff_ps.triggered.connect( _part(self._set_check_pwrstate, 'PS', 'off', True)) self.act_turnoff_dclink = self.ps_menu.addAction('Turn DCLinks Off') self.act_turnoff_dclink.triggered.connect( _part(self._set_lastcomm, 'PS')) self.act_turnoff_dclink.triggered.connect( _part(self._set_check_pwrstate_dclinks, 'off')) self.act_setcurrent_ps = self.ps_menu.addAction('Set PS Current') self.act_setcurrent_ps.triggered.connect( _part(self._set_lastcomm, 'PS')) self.act_setcurrent_ps.triggered.connect(self._set_check_current) self.act_opmode_ps = self.ps_menu.addAction('Set PS OpMode') self.act_opmode_ps.triggered.connect(_part(self._set_lastcomm, 'PS')) self.act_opmode_ps.triggered.connect(self._set_check_opmode) # # auxiliary PU self.pu_menu = self.aux_comm.addMenu('PU') self.act_turnoff_pu = self.pu_menu.addAction('Turn PU Off') self.act_turnoff_pu.triggered.connect(_part(self._set_lastcomm, 'PU')) self.act_turnoff_pu.triggered.connect( _part(self._set_check_pwrstate, 'PU', 'off', True)) self.act_dsblpulse_pu = self.pu_menu.addAction('Disable PU Pulse') self.act_dsblpulse_pu.triggered.connect(_part(self._set_lastcomm, 'PU')) self.act_dsblpulse_pu.triggered.connect( _part(self._set_check_pulse, 'off')) # layout lay = QGridLayout() lay.setHorizontalSpacing(15) lay.setVerticalSpacing(5) lay.addWidget( QLabel('<h3>Power Supplies Test</h3>', self, alignment=Qt.AlignCenter), 0, 0, 1, 3) lay.addWidget(self.tab, 1, 0) lay.addLayout(list_layout, 1, 1) lay.setColumnStretch(0, 2) lay.setColumnStretch(1, 2) lay.setRowStretch(0, 2) lay.setRowStretch(1, 18) self.central_widget.setLayout(lay) # ---------- commands ------------ def _check_comm(self): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_ps() if not devices: return dclinks = self._get_related_dclinks(devices, include_regatrons=True) devices.extend(dclinks) task0 = CreateTesters(devices, parent=self) task1 = CheckComm(devices, parent=self) task1.itemDone.connect(self._log) labels = [ 'Connecting to devices...', 'Checking PS and DCLinks Comm. Status...' ] tasks = [task0, task1] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _check_status(self, dev_type): self.ok_ps.clear() self.nok_ps.clear() if dev_type == 'PS': devices = self._get_selected_ps() if not devices: return dclinks = self._get_related_dclinks(devices, include_regatrons=True) devices.extend(dclinks) label1 = 'Reading PS and DCLinks Status...' elif dev_type == 'PU': devices = self._get_selected_pu() if not devices: return label1 = 'Reading PU Status...' task0 = CreateTesters(devices, parent=self) task1 = CheckStatus(devices, parent=self) task1.itemDone.connect(self._log) labels = ['Connecting to devices...', label1] tasks = [task0, task1] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_trigger_state(self, dev_type, state): self.ok_ps.clear() self.nok_ps.clear() if dev_type == 'PS': devices = self._get_selected_ps() elif dev_type == 'PU': devices = self._get_selected_pu() if not devices: return task1 = SetTriggerState(parent=self, dis=dev_type, state=state, devices=devices) task2 = CheckTriggerState(parent=self, dis=dev_type, state=state, devices=devices) task2.itemDone.connect(self._log) tasks = [task1, task2] labels = [ 'Disabling ' + dev_type + ' triggers...', 'Checking ' + dev_type + ' trigger states...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_opmode_slowref(self): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_ps() if not devices: return dclinks = self._get_related_dclinks(devices) devices.extend(dclinks) devices = [dev for dev in devices if 'LI-' not in dev] task0 = CreateTesters(devices, parent=self) task1 = SetOpMode(devices, state=_PSC.OpMode.SlowRef, parent=self) task2 = CheckOpMode(devices, state=[ _PSC.States.SlowRef, _PSC.States.Off, _PSC.States.Interlock ], parent=self) task2.itemDone.connect(self._log) labels = [ 'Connecting to devices...', 'Setting PS OpMode to SlowRef...', 'Checking PS OpMode...' ] tasks = [task0, task1, task2] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _reset_intlk(self, dev_type): self.ok_ps.clear() self.nok_ps.clear() if dev_type == 'PS': devices = self._get_selected_ps() if not devices: return dclinks = self._get_related_dclinks(devices, include_regatrons=True) devices.extend(dclinks) dev_label = 'PS and DCLinks' elif dev_type == 'PU': devices = self._get_selected_pu() if not devices: return dev_label = 'PU' devices_not_rst = { dev for dev in devices if dev.sec != 'LI' and dev.dev not in ('FCH', 'FCV') } task0 = CreateTesters(devices, parent=self) task1 = ResetIntlk(devices_not_rst, parent=self) task2 = CheckIntlk(devices, parent=self) task2.itemDone.connect(self._log) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Reseting ' + dev_label + '...', 'Checking ' + dev_label + ' Interlocks...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_pwrstate(self, dev_type, state, show=True): self.ok_ps.clear() self.nok_ps.clear() if isinstance(dev_type, list): devices = list(dev_type) dev_type = devices[0].dis else: if dev_type == 'PS': devices = self._get_selected_ps() elif dev_type == 'PU': devices = self._get_selected_pu() if not devices: return if state == 'on' and dev_type == 'PS': dev2ctrl = list(set(devices) - set(self._si_fam_psnames)) else: dev2ctrl = devices task0 = CreateTesters(devices, parent=self) task1 = SetPwrState(dev2ctrl, state=state, parent=self) task2 = CheckPwrState(devices, state=state, is_test=True, parent=self) task2.itemDone.connect(_part(self._log, show=show)) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Turning ' + dev_type + ' ' + state + '...', 'Checking ' + dev_type + ' powered ' + state + '...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _prepare_sidclinks(self): self.ok_ps.clear() self.nok_ps.clear() selected = self._get_selected_ps() ps2check = [ps for ps in selected if ps in self._si_fam_psnames] dclinks = self._get_related_dclinks(ps2check, include_regatrons=True) if not ps2check: return # if power state is on, do nothing self.ok_ps_aux_list.clear() self.nok_ps_aux_list.clear() self._check_pwrstate(ps2check, state='on', is_test=False, show=False) if set(self.ok_ps_aux_list) == set(ps2check): for dev in dclinks: self._log(dev, True) return ps2act = list(self.nok_ps_aux_list) # act in PS off dcl2act = self._get_related_dclinks(ps2act, include_regatrons=True) # print('act', ps2act, dcl2act) # if need initializing, check if DCLinks are turned off self.ok_ps_aux_list.clear() self.nok_ps_aux_list.clear() self._check_pwrstate(dcl2act, state='off', is_test=False, show=False) if not self.nok_ps_aux_list: for dev in dclinks: self._log(dev, True) return dcl2ctrl = list(self.nok_ps_aux_list) # control DCLink on dcl_ok = set(dclinks) - set(dcl2ctrl) ps2ctrl = set() # get related psnames for dcl in dcl2ctrl: pss = PSSearch.conv_dclink_2_psname(dcl) ps2ctrl.update(pss) # print('ctrl', ps2ctrl, dcl2ctrl) # if some DCLink is on, make sure related PS are turned off self.ok_ps_aux_list.clear() self.nok_ps_aux_list.clear() self._check_pwrstate(ps2ctrl, state='off', is_test=False, show=False) if self.nok_ps_aux_list: ps2ctrl = list(self.nok_ps_aux_list) self.ok_ps_aux_list.clear() self.nok_ps_aux_list.clear() self._set_zero_ps(ps2ctrl, show=False) self.ok_ps_aux_list.clear() self.nok_ps_aux_list.clear() self._set_check_pwrstate(dev_type=ps2ctrl, state='off', show=False) if self.nok_ps_aux_list: for dev in self.ok_ps_aux_list: self._log(dev, True) for dev in self.nok_ps_aux_list: self._log(dev, False) text = 'The listed PS seems to be taking too\n'\ 'long to turn off.\n'\ 'Try to execute this step once again.' QMessageBox.warning(self, 'Message', text) return # finally, turn DCLinks off self._set_check_pwrstate_dclinks(state='off', devices=dcl2ctrl, ps2check=ps2ctrl) # log DCLinks Ok for dev in dcl_ok: self._log(dev, True) def _set_check_pwrstateinit(self): self.ok_ps.clear() self.nok_ps.clear() selected = self._get_selected_ps() devices = [ps for ps in selected if ps in self._si_fam_psnames] if not devices: return # if power state is on, do nothing self.ok_ps_aux_list.clear() self.nok_ps_aux_list.clear() self._check_pwrstate(devices, state='on', is_test=False, show=False) if len(self.ok_ps_aux_list) == len(devices): for dev in self.ok_ps_aux_list: self._log(dev, True) return # if need initializing, check if DCLinks are turned off before continue ps_ok = list(self.ok_ps_aux_list) ps2ctrl = list(self.nok_ps_aux_list) # check PS off dcl2check = self._get_related_dclinks(ps2ctrl, include_regatrons=True) # print('set_check_pwrstateinit', ps2ctrl) self.ok_ps_aux_list.clear() self.nok_ps_aux_list.clear() self._check_pwrstate(dcl2check, state='off', is_test=False, show=False) if len(self.nok_ps_aux_list) > 0: for dev in self.ok_ps_aux_list: self._log(dev, True) for dev in self.nok_ps_aux_list: self._log(dev, False) QMessageBox.critical( self, 'Message', 'Make sure related DCLinks are turned\n' 'off before initialize SI Fam PS!') return # list in Ok PS already on for dev in ps_ok: self._log(dev, True) # then, initialize SI Fam PS task0 = CreateTesters(ps2ctrl, parent=self) task1 = SetPwrState(ps2ctrl, state='on', parent=self) task2 = CheckOpMode(ps2ctrl, state=_PSC.States.Initializing, parent=self) task2.itemDone.connect(self._log) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Initializing SI Fam PS...', 'Checking SI Fam PS initialized...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_pulse(self, state): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_pu() if not devices: return task0 = CreateTesters(devices, parent=self) task1 = SetPulse(devices, state=state, parent=self) task2 = CheckPulse(devices, state=state, parent=self) task2.itemDone.connect(self._log) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Turning PU Pulse ' + state + '...', 'Checking PU Pulse ' + state + '...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_pwrstate_dclinks(self, state, devices=list(), ps2check=list()): self.ok_ps.clear() self.nok_ps.clear() if not devices: pwrsupplies = self._get_selected_ps() if not pwrsupplies: return devices = self._get_related_dclinks(pwrsupplies, include_regatrons=True) ps2check = set() for dev in devices: ps2check.update(PSSearch.conv_dclink_2_psname(dev)) if not devices: return if state == 'off': self.ok_ps_aux_list.clear() self.nok_ps_aux_list.clear() self._check_pwrstate(ps2check, state='offintlk', show=False) if len(self.nok_ps_aux_list) > 0: for dev in self.ok_ps_aux_list: self._log(dev, True) for dev in self.nok_ps_aux_list: self._log(dev, False) QMessageBox.critical( self, 'Message', 'Make sure all related power supplies\n' 'are turned off before turning DCLinks off!') return task0 = CreateTesters(devices, parent=self) task1 = SetPwrState(devices, state=state, parent=self) task2 = CheckPwrState(devices, state=state, is_test=True, parent=self) tasks = [task0, task1, task2] labels = [ 'Connecting to DCLinks...', 'Turning DCLinks ' + state + '...', 'Checking DCLinks powered ' + state + '...' ] if state == 'on': task3 = CheckInitOk(devices, parent=self) task3.itemDone.connect(self._log) tasks.append(task3) labels.append('Wait DCLinks initialize...') else: task2.itemDone.connect(self._log) dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _check_pwrstate(self, devices, state, is_test=True, show=True): self.ok_ps.clear() self.nok_ps.clear() task0 = CreateTesters(devices, parent=self) if state == 'offintlk': text = 'off or interlock' task1 = CheckOpMode(devices, state=[_PSC.States.Off, _PSC.States.Interlock], parent=self) else: text = state task1 = CheckPwrState(devices, state=state, is_test=is_test, parent=self) task1.itemDone.connect(_part(self._log, show=show)) tasks = [task0, task1] labels = [ 'Connecting to devices...', 'Checking devices powered ' + text + '...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_ctrlloop(self, dev_type='pwrsupply'): self.ok_ps.clear() self.nok_ps.clear() pwrsupplies = self._get_selected_ps() if not pwrsupplies: return devices_set = {} if dev_type == 'pwrsupply': devices = {dev for dev in pwrsupplies if 'LI' not in dev} devices_set = {dev for dev in devices if dev.dev in ('FCH', 'FCV')} else: devices = self._get_related_dclinks(pwrsupplies) if not devices: return tasks = [ CreateTesters(devices, parent=self), ] labels = [ 'Connecting to devices...', ] if devices_set: task1 = SetCtrlLoop(devices_set, parent=self) task2 = CheckCtrlLoop(devices, parent=self) task2.itemDone.connect(self._log) tasks.extend([task1, task2]) labels.extend( ['Setting CtrlLoop...', 'Checking CtrlLoop state...']) else: task1 = CheckCtrlLoop(devices, parent=self) task1.itemDone.connect(self._log) tasks.append(task1) labels.append('Checking CtrlLoop state...') dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_dclinks_capvolt(self): self.ok_ps.clear() self.nok_ps.clear() pwrsupplies = self._get_selected_ps() if not pwrsupplies: return devices = self._get_related_dclinks(pwrsupplies, include_regatrons=True) dev_exc_regatrons = { dev for dev in devices if PSSearch.conv_psname_2_psmodel(dev) != 'REGATRON_DCLink' } if not devices: return task0 = CreateTesters(devices, parent=self) task1 = SetCapBankVolt(dev_exc_regatrons, parent=self) task2 = CheckCapBankVolt(devices, parent=self) task2.itemDone.connect(self._log) labels = [ 'Connecting to devices...', 'Setting capacitor bank voltage...', 'Checking capacitor bank voltage...' ] tasks = [task0, task1, task2] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_fbp_sofbmode(self, state): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_ps() devices = [ dev for dev in devices if PSSearch.conv_psname_2_psmodel(dev) == 'FBP' ] if not devices: return task0 = CreateTesters(devices, parent=self) task1 = SetSOFBMode(devices, state=state, parent=self) task2 = CheckSOFBMode(devices, state=state, parent=self) task2.itemDone.connect(self._log) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Turning PS SOFBMode ' + state + '...', 'Checking PS SOFBMode ' + state + '...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_test_ps(self): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_ps() if not devices: return task0 = CreateTesters(devices, parent=self) task1 = SetCurrent(devices, is_test=True, parent=self) task2 = CheckCurrent(devices, is_test=True, parent=self) task2.itemDone.connect(self._log) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Testing PS... Setting current...', 'Testing PS... Checking current value...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_zero_ps(self, devices=list(), show=True): self.ok_ps.clear() self.nok_ps.clear() if not devices: devices = self._get_selected_ps() if not devices: return task0 = CreateTesters(devices, parent=self) task1 = SetCurrent(devices, parent=self) task2 = CheckCurrent(devices, parent=self) task2.itemDone.connect(_part(self._log, show=show)) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Setting current to zero...', 'Checking current value...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_current(self): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_ps() if not devices: return value, ok = QInputDialog.getDouble(self, "Setpoint Input", "Insert current setpoint: ") if not ok: return task0 = CreateTesters(devices, parent=self) task1 = SetCurrent(devices, value=value, parent=self) task2 = CheckCurrent(devices, value=value, parent=self) task2.itemDone.connect(self._log) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Setting current...', 'Checking current value...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_check_opmode(self): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_ps() devices = [ dev for dev in devices if dev.sec != 'LI' and dev.dev not in ('FCH', 'FCV') ] if not devices: return state, ok = QInputDialog.getItem(self, "OpMode Input", "Select OpMode: ", _PSE.OPMODES, editable=False) if not ok: return state2set = getattr(_PSC.OpMode, state) state2check = getattr(_PSC.States, state) task0 = CreateTesters(devices, parent=self) task1 = SetOpMode(devices, state=state2set, parent=self) task2 = CheckOpMode(devices, state=state2check, parent=self) task2.itemDone.connect(self._log) labels = [ 'Connecting to devices...', 'Setting PS OpMode to ' + state + '...', 'Checking PS OpMode in ' + state + '...' ] tasks = [task0, task1, task2] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_test_pu(self): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_pu() if not devices: return task0 = CreateTesters(devices, parent=self) task1 = SetVoltage(devices, is_test=True, parent=self) task2 = CheckVoltage(devices, is_test=True, parent=self) task2.itemDone.connect(self._log) tasks = [task0, task1, task2] labels = [ 'Connecting to devices...', 'Testing PU... Setting voltage...', 'Testing PU... Checking voltage value...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _set_zero_pu(self, check=True): self.ok_ps.clear() self.nok_ps.clear() devices = self._get_selected_pu() if not devices: return task0 = CreateTesters(devices, parent=self) task1 = SetVoltage(devices, parent=self) tasks = [task0, task1] labels = ['Connecting to devices...', 'Setting voltage to zero...'] if check: task2 = CheckVoltage(devices, parent=self) task2.itemDone.connect(self._log) tasks.append(task2) labels.append('Checking voltage value...') else: task1.itemDone.connect(self._log) dlg = ProgressDialog(labels, tasks, self) dlg.exec_() def _restore_triggers_state(self, dev_type): self.ok_ps.clear() self.nok_ps.clear() if dev_type == 'PS': devices = self._get_selected_ps() elif dev_type == 'PU': devices = self._get_selected_pu() if not devices: return task1 = SetTriggerState(parent=self, restore_initial_value=True, dis=dev_type, devices=devices) task2 = CheckTriggerState(parent=self, restore_initial_value=True, dis=dev_type, devices=devices) task2.itemDone.connect(self._log) tasks = [task1, task2] labels = [ 'Restoring ' + dev_type + ' trigger states...', 'Checking ' + dev_type + ' trigger states...' ] dlg = ProgressDialog(labels, tasks, self) dlg.exec_() # ---------- log updates ----------- def _set_lastcomm(self, dev_type): sender_text = self.sender().text() self.label_lastcomm.setText('Last Command: ' + dev_type + ' - ' + sender_text) def _clear_lastcomm(self): self.ok_ps.clear() self.nok_ps.clear() self.label_lastcomm.setText('Last Command: ') def _log(self, name, status, show=True): if show: if status: self.ok_ps.addItem(name) else: self.nok_ps.addItem(name) else: if status: self.ok_ps_aux_list.append(name) else: self.nok_ps_aux_list.append(name) # ---------- devices control ---------- def _get_ps_tree_names(self): # add LI, TB, BO, TS psnames = PSSearch.get_psnames({'sec': '(LI|TB|BO|TS)', 'dis': 'PS'}) # add SI Fams psnames.extend( PSSearch.get_psnames({ 'sec': 'SI', 'sub': 'Fam', 'dis': 'PS', 'dev': '(B.*|Q.*|S.*)' })) # add SI Corrs psnames.extend( PSSearch.get_psnames({ 'sec': 'SI', 'sub': '[0-2][0-9].*', 'dis': 'PS', 'dev': '(CH|CV)' })) # add SI QTrims psnames.extend( PSSearch.get_psnames({ 'sec': 'SI', 'sub': '[0-2][0-9].*', 'dis': 'PS', 'dev': '(QD.*|QF.*|Q[1-4])' })) # add SI QSkews psnames.extend( PSSearch.get_psnames({ 'sec': 'SI', 'dis': 'PS', 'dev': 'QS' })) # add SI Fast Corrs psnames.extend( PSSearch.get_psnames({ 'sec': 'SI', 'dis': 'PS', 'dev': 'FC.*' })) return psnames def _get_pu_tree_names(self): punames = PSSearch.get_psnames({ 'sec': '(TB|BO|TS|SI)', 'dis': 'PU', 'dev': '.*(Kckr|Sept).*' }) return punames def _get_selected_ps(self): devices = self.ps_tree.checked_items() if not devices: QMessageBox.critical(self, 'Message', 'No power supply selected!') return False devices = [PVName(dev) for dev in devices] return devices def _get_selected_pu(self): devices = self.pu_tree.checked_items() if not devices: QMessageBox.critical(self, 'Message', 'No pulsed power supply selected!') return False devices = [PVName(dev) for dev in devices] return devices def _get_related_dclinks(self, psnames, include_regatrons=False): if isinstance(psnames, str): psnames = [ psnames, ] alldclinks = set() for name in psnames: if 'LI' in name: continue dclinks = PSSearch.conv_psname_2_dclink(name) if dclinks: dclink_model = PSSearch.conv_psname_2_psmodel(dclinks[0]) if dclink_model != 'REGATRON_DCLink': alldclinks.update(dclinks) elif include_regatrons: for dcl in dclinks: dcl_typ = PSSearch.conv_psname_2_pstype(dcl) if dcl_typ == 'as-dclink-regatron-master': alldclinks.add(dcl) alldclinks = [PVName(dev) for dev in alldclinks] return alldclinks def _open_detail(self, index): name = PVName(index.data()) if name.dis == 'TI': app = QApplication.instance() wind = create_window_from_widget(HLTriggerDetailed, title=name, is_main=True) app.open_window(wind, parent=self, **{'device': name}) return elif not _re.match('.*-.*:.*-.*', name): if index.model().rowCount(index) == 1: name = PVName(index.child(0, 0).data()) else: return if name.dis == 'PS': run_newprocess(['sirius-hla-as-ps-detail.py', name]) elif name.dis == 'PU': run_newprocess(['sirius-hla-as-pu-detail.py', name]) # ---------- update setup ---------- def _handle_checked_items_changed(self, item): devname = PVName(item.data(0, Qt.DisplayRole)) if not _re.match('.*-.*:.*-.*', devname): return state2set = item.checkState(0) # ensure power supplies that share dclinks are checked together if devname in self._si_fam_psnames and not self._is_adv_mode: dclinks = PSSearch.conv_psname_2_dclink(devname) if dclinks: psname2check = set() for dcl in dclinks: relps = PSSearch.conv_dclink_2_psname(dcl) relps.remove(devname) psname2check.update(relps) self.ps_tree.tree.blockSignals(True) for psn in psname2check: item2check = self.ps_tree._item_map[psn] if item2check.checkState(0) != state2set: item2check.setData(0, Qt.CheckStateRole, state2set) self.ps_tree.tree.blockSignals(False) self._needs_update_setup = True def _update_setup(self): if not self._needs_update_setup: return self._needs_update_setup = False # show/hide buttons to initialize SI Fam PS has_sifam = False for psn in self._si_fam_psnames: item = self.ps_tree._item_map[psn] has_sifam |= item.checkState(0) != 0 self.prep_sidclink_bt.setVisible(has_sifam) self.init_sips_bt.setVisible(has_sifam) self.aux_label.setVisible(has_sifam) # set CtrlLoop button label has_fast = False for psn in self._si_fastcorrs: item = self.ps_tree._item_map[psn] has_fast |= item.checkState(0) != 0 text = 'Set(FC) and Check PS CtrlLoop' \ if has_fast else 'Check PS CtrlLoop' self.setcheckctrlloop_ps_bt.setText(text) # ---------- events ---------- def keyPressEvent(self, evt): """Implement keyPressEvent.""" if evt.matches(QKeySequence.Copy): if self.ok_ps.underMouse(): items = self.ok_ps.selectedItems() elif self.nok_ps.underMouse(): items = self.nok_ps.selectedItems() items = '\n'.join([i.text() for i in items]) QApplication.clipboard().setText(items) if evt.key() == Qt.Key_Escape: if self.ok_ps.underMouse(): items = self.ok_ps.clearSelection() elif self.nok_ps.underMouse(): items = self.nok_ps.clearSelection() super().keyPressEvent(evt)
class FileSwitcher(QDialog): """A Sublime-like file switcher.""" sig_goto_file = Signal(int) sig_close_file = Signal(int) # Constants that define the mode in which the list widget is working # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols # in a given file when using the '@' symbol. FILE_MODE, SYMBOL_MODE = [1, 2] def __init__(self, parent, tabs, data): QDialog.__init__(self, parent) # Variables self.tabs = tabs # Editor stack tabs self.data = data # Editor data self.mode = self.FILE_MODE # By default start in this mode self.initial_cursors = None # {fullpath: QCursor} self.initial_path = None # Fullpath of initial active editor self.initial_editor = None # Initial active editor self.line_number = None # Selected line number in filer self.is_visible = False # Is the switcher visible? help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to " "cancel.<br><br>Type to filter filenames.<br><br>" "Use <b>:number</b> to go to a line, e.g. " "<b><code>main:42</code></b><br>" "Use <b>@symbol_text</b> to go to a symbol, e.g. " "<b><code>@init</code></b>" "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>") # Either allow searching for a line number or a symbol but not both regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" + "([A-Za-z]{0,100}:{0,1}[0-9]{0,100})") # Widgets self.edit = QLineEdit(self) self.help = HelperToolButton() self.list = QListWidget(self) self.filter = KeyPressFilter() regex_validator = QRegExpValidator(regex, self.edit) # Widgets setup self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) self.setWindowOpacity(0.95) self.edit.installEventFilter(self.filter) self.edit.setValidator(regex_validator) self.help.setToolTip(help_text) self.list.setItemDelegate(HTMLDelegate(self)) # Layout edit_layout = QHBoxLayout() edit_layout.addWidget(self.edit) edit_layout.addWidget(self.help) layout = QVBoxLayout() layout.addLayout(edit_layout) layout.addWidget(self.list) self.setLayout(layout) # Signals self.rejected.connect(self.restore_initial_state) self.filter.sig_up_key_pressed.connect(self.previous_row) self.filter.sig_down_key_pressed.connect(self.next_row) self.edit.returnPressed.connect(self.accept) self.edit.textChanged.connect(self.setup) self.list.itemSelectionChanged.connect(self.item_selection_changed) self.list.clicked.connect(self.edit.setFocus) # Setup self.save_initial_state() self.set_dialog_position() self.setup() # --- Properties @property def editors(self): return [self.tabs.widget(index) for index in range(self.tabs.count())] @property def line_count(self): return [editor.get_line_count() for editor in self.editors] @property def save_status(self): return [getattr(td, 'newly_created', False) for td in self.data] @property def paths(self): return [getattr(td, 'filename', None) for td in self.data] @property def filenames(self): return [self.tabs.tabText(index) for index in range(self.tabs.count())] @property def current_path(self): return self.paths_by_editor[self.get_editor()] @property def paths_by_editor(self): return dict(zip(self.editors, self.paths)) @property def editors_by_path(self): return dict(zip(self.paths, self.editors)) @property def filter_text(self): """Get the normalized (lowecase) content of the filter text.""" return to_text_string(self.edit.text()).lower() def save_initial_state(self): """Saves initial cursors and initial active editor.""" paths = self.paths self.initial_editor = self.get_editor() self.initial_cursors = {} for i, editor in enumerate(self.editors): if editor is self.initial_editor: self.initial_path = paths[i] self.initial_cursors[paths[i]] = editor.textCursor() def accept(self): self.is_visible = False QDialog.accept(self) self.list.clear() def restore_initial_state(self): """Restores initial cursors and initial active editor.""" self.list.clear() self.is_visible = False editors = self.editors_by_path for path in self.initial_cursors: cursor = self.initial_cursors[path] if path in editors: self.set_editor_cursor(editors[path], cursor) if self.initial_editor in self.paths_by_editor: index = self.paths.index(self.initial_path) self.sig_goto_file.emit(index) def set_dialog_position(self): """Positions the file switcher dialog in the center of the editor.""" parent = self.parent() geo = parent.geometry() width = self.list.width() # This has been set in setup left = parent.geometry().width()/2 - width/2 top = 0 while parent: geo = parent.geometry() top += geo.top() left += geo.left() parent = parent.parent() # Note: the +1 pixel on the top makes it look better self.move(left, top + self.tabs.tabBar().geometry().height() + 1) def fix_size(self, content, extra=50): """Adjusts the width of the file switcher, based on the content.""" # Update size of dialog based on longest shortened path strings = [] if content: for rich_text in content: label = QLabel(rich_text) label.setTextFormat(Qt.PlainText) strings.append(label.text()) fm = label.fontMetrics() max_width = max([fm.width(s)*1.3 for s in strings]) self.list.setMinimumWidth(max_width + extra) self.set_dialog_position() # --- Helper methods: List widget def count(self): """Gets the item count in the list widget.""" return self.list.count() def current_row(self): """Returns the current selected row in the list widget.""" return self.list.currentRow() def set_current_row(self, row): """Sets the current selected row in the list widget.""" return self.list.setCurrentRow(row) def select_row(self, steps): """Select row in list widget based on a number of steps with direction. Steps can be positive (next rows) or negative (previous rows). """ row = self.current_row() + steps if 0 <= row < self.count(): self.set_current_row(row) def previous_row(self): """Select previous row in list widget.""" self.select_row(-1) def next_row(self): """Select next row in list widget.""" self.select_row(+1) # --- Helper methods: Editor def get_editor(self, index=None, path=None): """Get editor by index or path. If no path or index specified the current active editor is returned """ if index: return self.tabs.widget(index) elif path: return self.tabs.widget(index) else: return self.parent().get_current_editor() def set_editor_cursor(self, editor, cursor): """Set the cursor of an editor.""" pos = cursor.position() anchor = cursor.anchor() new_cursor = QTextCursor() if pos == anchor: new_cursor.movePosition(pos) else: new_cursor.movePosition(anchor) new_cursor.movePosition(pos, QTextCursor.KeepAnchor) editor.setTextCursor(cursor) def goto_line(self, line_number): """Go to specified line number in current active editor.""" if line_number: line_number = int(line_number) editor = self.get_editor() editor.go_to_line(min(line_number, editor.get_line_count())) # --- Helper methods: Outline explorer def get_symbol_list(self): """Get the object explorer data.""" return self.get_editor().highlighter.get_outlineexplorer_data() # --- Handlers def item_selection_changed(self): """List widget item selection change handler.""" row = self.current_row() if self.count() and row >= 0: if self.mode == self.FILE_MODE: try: stack_index = self.paths.index(self.filtered_path[row]) self.sig_goto_file.emit(stack_index) self.goto_line(self.line_number) self.edit.setFocus() except ValueError: pass else: line_number = self.filtered_symbol_lines[row] self.goto_line(line_number) def setup_file_list(self, filter_text, current_path): """Setup list widget content for file list display.""" short_paths = shorten_paths(self.paths, self.save_status) paths = self.paths results = [] trying_for_line_number = ':' in filter_text # Get optional line number if trying_for_line_number: filter_text, line_number = filter_text.split(':') else: line_number = None # Get all available filenames and get the scores for "fuzzy" matching scores = get_search_scores(filter_text, self.filenames, template="<b>{0}</b>") # Build the text that will appear on the list widget for index, score in enumerate(scores): text, rich_text, score_value = score if score_value != -1: text_item = '<big>' + rich_text + '</big>' if trying_for_line_number: text_item += " [{0:} {1:}]".format(self.line_count[index], _("lines")) text_item += "<br><i>{0:}</i>".format( short_paths[index]) results.append((score_value, index, text_item)) # Sort the obtained scores and populate the list widget self.filtered_path = [] for result in sorted(results): index = result[1] text = result[-1] path = paths[index] item = QListWidgetItem(self.tabs.tabIcon(index), text) item.setToolTip(path) item.setSizeHint(QSize(0, 25)) self.list.addItem(item) self.filtered_path.append(path) # Move selected item in list accordingly and update list size if current_path in self.filtered_path: self.set_current_row(self.filtered_path.index(current_path)) elif self.filtered_path: self.set_current_row(0) self.fix_size(short_paths) # If a line number is searched look for it self.line_number = line_number self.goto_line(line_number) def setup_symbol_list(self, filter_text, current_path): """Setup list widget content for symbol list display.""" # Get optional symbol name filter_text, symbol_text = filter_text.split('@') # Fetch the Outline explorer data, get the icons and values oedata = self.get_symbol_list() icons = get_python_symbol_icons(oedata) symbol_list = process_python_symbol_data(oedata) line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list] choices = [item[1] for item in symbol_list] scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>") # Build the text that will appear on the list widget results = [] lines = [] self.filtered_symbol_lines = [] for index, score in enumerate(scores): text, rich_text, score_value = score line, fold_level, token = line_fold_token[index] lines.append(text) if score_value != -1: results.append((score_value, line, text, rich_text, fold_level, icons[index], token)) template_1 = '<code>{0}<big>{1} {2}</big></code>' template_2 = '<br><code>{0}</code><i>[Line {1}]</i>' for (score, line, text, rich_text, fold_level, icon, token) in sorted(results): fold_space = ' '*(fold_level) line_number = line + 1 self.filtered_symbol_lines.append(line_number) textline = template_1.format(fold_space, token, rich_text) textline += template_2.format(fold_space, line_number) item = QListWidgetItem(icon, textline) item.setSizeHint(QSize(0, 16)) self.list.addItem(item) # Move selected item in list accordingly # NOTE: Doing this is causing two problems: # 1. It makes the cursor to auto-jump to the last selected # symbol after opening or closing a different file # 2. It moves the cursor to the first symbol by default, # which is very distracting. # That's why this line is commented! # self.set_current_row(0) # Update list size self.fix_size(lines, extra=125) def setup(self): """Setup list widget content.""" if not self.tabs.count(): self.close() return self.list.clear() current_path = self.current_path filter_text = self.filter_text # Get optional line or symbol to define mode and method handler trying_for_symbol = ('@' in self.filter_text) if trying_for_symbol: self.mode = self.SYMBOL_MODE self.setup_symbol_list(filter_text, current_path) else: self.mode = self.FILE_MODE self.setup_file_list(filter_text, current_path)
class PathManager(QDialog): """Path manager dialog.""" redirect_stdio = Signal(bool) sig_path_changed = Signal(object) def __init__(self, parent, path=None, read_only_path=None, not_active_path=None, sync=True): """Path manager dialog.""" super(PathManager, self).__init__(parent) assert isinstance(path, (tuple, None)) self.path = path or () self.read_only_path = read_only_path or () self.not_active_path = not_active_path or () self.last_path = getcwd_or_home() self.original_path_dict = None # Widgets self.add_button = None self.remove_button = None self.movetop_button = None self.moveup_button = None self.movedown_button = None self.movebottom_button = None self.sync_button = None self.selection_widgets = [] self.top_toolbar_widgets = self._setup_top_toolbar() self.bottom_toolbar_widgets = self._setup_bottom_toolbar() self.listwidget = QListWidget(self) self.bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_ok = self.bbox.button(QDialogButtonBox.Ok) # Widget setup # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(_("PYTHONPATH manager")) self.setWindowIcon(ima.icon('pythonpath')) self.resize(500, 300) self.sync_button.setVisible(os.name == 'nt' and sync) # Layouts top_layout = QHBoxLayout() self._add_widgets_to_layout(self.top_toolbar_widgets, top_layout) bottom_layout = QHBoxLayout() self._add_widgets_to_layout(self.bottom_toolbar_widgets, bottom_layout) bottom_layout.addWidget(self.bbox) layout = QVBoxLayout() layout.addLayout(top_layout) layout.addWidget(self.listwidget) layout.addLayout(bottom_layout) self.setLayout(layout) # Signals self.listwidget.currentRowChanged.connect(lambda x: self.refresh()) self.listwidget.itemChanged.connect(lambda x: self.refresh()) self.bbox.accepted.connect(self.accept) self.bbox.rejected.connect(self.reject) # Setup self.setup() def _add_widgets_to_layout(self, widgets, layout): """Helper to add toolbar widgets to top and bottom layout.""" layout.setAlignment(Qt.AlignLeft) for widget in widgets: if widget is None: layout.addStretch(1) else: layout.addWidget(widget) def _setup_top_toolbar(self): """Create top toolbar and actions.""" self.movetop_button = create_toolbutton( self, text=_("Move to top"), icon=ima.icon('2uparrow'), triggered=lambda: self.move_to(absolute=0), text_beside_icon=True) self.moveup_button = create_toolbutton( self, text=_("Move up"), icon=ima.icon('1uparrow'), triggered=lambda: self.move_to(relative=-1), text_beside_icon=True) self.movedown_button = create_toolbutton( self, text=_("Move down"), icon=ima.icon('1downarrow'), triggered=lambda: self.move_to(relative=1), text_beside_icon=True) self.movebottom_button = create_toolbutton( self, text=_("Move to bottom"), icon=ima.icon('2downarrow'), triggered=lambda: self.move_to(absolute=1), text_beside_icon=True) toolbar = [ self.movetop_button, self.moveup_button, self.movedown_button, self.movebottom_button ] self.selection_widgets.extend(toolbar) return toolbar def _setup_bottom_toolbar(self): """Create bottom toolbar and actions.""" self.add_button = create_toolbutton( self, text=_('Add path'), icon=ima.icon('edit_add'), triggered=lambda x: self.add_path(), text_beside_icon=True) self.remove_button = create_toolbutton( self, text=_('Remove path'), icon=ima.icon('edit_remove'), triggered=lambda x: self.remove_path(), text_beside_icon=True) self.sync_button = create_toolbutton( self, text=_("Synchronize..."), icon=ima.icon('fileimport'), triggered=self.synchronize, tip=_("Synchronize Spyder's path list with PYTHONPATH " "environment variable"), text_beside_icon=True) self.selection_widgets.append(self.remove_button) return [self.add_button, self.remove_button, None, self.sync_button] def _create_item(self, path): """Helper to create a new list item.""" item = QListWidgetItem(path) item.setIcon(ima.icon('DirClosedIcon')) if path in self.read_only_path: item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) elif path in self.not_active_path: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Unchecked) else: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) return item @property def editable_bottom_row(self): """Maximum bottom row count that is editable.""" read_only_count = len(self.read_only_path) if read_only_count == 0: max_row = self.listwidget.count() - 1 else: max_row = self.listwidget.count() - read_only_count - 1 return max_row def setup(self): """Populate list widget.""" self.listwidget.clear() for path in self.path + self.read_only_path: item = self._create_item(path) self.listwidget.addItem(item) self.listwidget.setCurrentRow(0) self.original_path_dict = self.get_path_dict() self.refresh() @Slot() def synchronize(self): """ Synchronize Spyder's path list with PYTHONPATH environment variable Only apply to: current user, on Windows platforms. """ answer = QMessageBox.question( self, _("Synchronize"), _("This will synchronize Spyder's path list with " "<b>PYTHONPATH</b> environment variable for the current user, " "allowing you to run your Python modules outside Spyder " "without having to configure sys.path. " "<br>" "Do you want to clear contents of PYTHONPATH before " "adding Spyder's path list?"), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if answer == QMessageBox.Cancel: return elif answer == QMessageBox.Yes: remove = True else: remove = False from spyder.utils.environ import (get_user_env, listdict2envdict, set_user_env) env = get_user_env() # Includes read only paths active_path = tuple(k for k, v in self.get_path_dict(True).items() if v) if remove: ppath = active_path else: ppath = env.get('PYTHONPATH', []) if not isinstance(ppath, list): ppath = [ppath] ppath = tuple(p for p in ppath if p not in active_path) ppath = ppath + active_path env['PYTHONPATH'] = list(ppath) set_user_env(listdict2envdict(env), parent=self) def get_path_dict(self, read_only=False): """ Return an ordered dict with the path entries as keys and the active state as the value. If `read_only` is True, the read_only entries are also included. `read_only` entry refers to the project path entry. """ odict = OrderedDict() for row in range(self.listwidget.count()): item = self.listwidget.item(row) path = item.text() if path in self.read_only_path and not read_only: continue odict[path] = item.checkState() == Qt.Checked return odict def refresh(self): """Refresh toolbar widgets.""" enabled = self.listwidget.currentItem() is not None for widget in self.selection_widgets: widget.setEnabled(enabled) # Disable buttons based on row row = self.listwidget.currentRow() disable_widgets = [] # Move up/top disabled for top item if row == 0: disable_widgets.extend([self.movetop_button, self.moveup_button]) # Move down/bottom disabled for bottom item if row == self.editable_bottom_row: disable_widgets.extend( [self.movebottom_button, self.movedown_button]) for widget in disable_widgets: widget.setEnabled(False) self.sync_button.setEnabled(self.listwidget.count() > 0) # Ok button only enabled if actual changes occur self.button_ok.setEnabled( self.original_path_dict != self.get_path_dict()) def check_path(self, path): """Check that the path is not a [site|dist]-packages folder.""" if os.name == 'nt': pat = re.compile(r'.*lib/(?:site|dist)-packages.*') else: pat = re.compile(r'.*lib/python.../(?:site|dist)-packages.*') path_norm = path.replace('\\', '/') return pat.match(path_norm) is None @Slot() def add_path(self, directory=None): """ Add path to list widget. If `directory` is provided, the folder dialog is overridden. """ if directory is None: self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), self.last_path) self.redirect_stdio.emit(True) if PY2: is_unicode = False try: directory.decode('ascii') except (UnicodeEncodeError, UnicodeDecodeError): is_unicode = True if is_unicode: QMessageBox.warning( self, _("Add path"), _("You are using Python 2 and the selected path has " "Unicode characters." "<br> " "Therefore, this path will not be added."), QMessageBox.Ok) return directory = osp.abspath(directory) self.last_path = directory if directory in self.get_path_dict(): item = self.listwidget.findItems(directory, Qt.MatchExactly)[0] item.setCheckState(Qt.Checked) answer = QMessageBox.question( self, _("Add path"), _("This directory is already included in the list." "<br> " "Do you want to move it to the top of it?"), QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.Yes: item = self.listwidget.takeItem(self.listwidget.row(item)) self.listwidget.insertItem(0, item) self.listwidget.setCurrentRow(0) else: if self.check_path(directory): item = self._create_item(directory) self.listwidget.insertItem(0, item) self.listwidget.setCurrentRow(0) else: answer = QMessageBox.warning( self, _("Add path"), _("This directory cannot be added to the path!" "<br><br>" "If you want to set a different Python interpreter, " "please go to <tt>Preferences > Main interpreter</tt>" "."), QMessageBox.Ok) self.refresh() @Slot() def remove_path(self, force=False): """ Remove path from list widget. If `force` is True, the message box is overridden. """ if self.listwidget.currentItem(): if not force: answer = QMessageBox.warning( self, _("Remove path"), _("Do you really want to remove the selected path?"), QMessageBox.Yes | QMessageBox.No) if force or answer == QMessageBox.Yes: self.listwidget.takeItem(self.listwidget.currentRow()) self.refresh() def move_to(self, absolute=None, relative=None): """Move items of list widget.""" index = self.listwidget.currentRow() if absolute is not None: if absolute: new_index = self.listwidget.count() - 1 else: new_index = 0 else: new_index = index + relative new_index = max(0, min(self.editable_bottom_row, new_index)) item = self.listwidget.takeItem(index) self.listwidget.insertItem(new_index, item) self.listwidget.setCurrentRow(new_index) self.refresh() def current_row(self): """Returns the current row of the list.""" return self.listwidget.currentRow() def set_current_row(self, row): """Set the current row of the list.""" self.listwidget.setCurrentRow(row) def row_check_state(self, row): """Return the checked state for item in row.""" item = self.listwidget.item(row) return item.checkState() def set_row_check_state(self, row, value): """Set the current checked state for item in row.""" item = self.listwidget.item(row) item.setCheckState(value) def count(self): """Return the number of items.""" return self.listwidget.count() def accept(self): """Override Qt method.""" path_dict = self.get_path_dict() if self.original_path_dict != path_dict: self.sig_path_changed.emit(path_dict) super(PathManager, self).accept()