class Sidebar(QWidget): def __init__(self, *args, **kwargs): super(Sidebar, self).__init__(*args, **kwargs) self.settings_scroll_area = QScrollArea() self.settings_scroll_area.setWidgetResizable(True) self.navigation = ImagesNav(parent=self) self.log = QTextEdit() self.log.setReadOnly(True) self.tabs = QTabWidget() self.tabs.addTab(self.navigation, "Images list") self.tabs.addTab(self.log, "Application log") self.layout = QStackedLayout() self.layout.addWidget(self.settings_scroll_area) self.layout.addWidget(self.tabs) self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.MinimumExpanding) self.setLayout(self.layout) self.layout.setCurrentWidget(self.tabs) def log_text(self, text, color="black"): time = datetime.datetime.now().time() if color == "red": self.log.setTextColor(QColor("red")) else: self.log.setTextColor(QColor("black")) self.log.append(f"{time}:\n {text}") main_window = self.parent().parent() main_window.statusBar().showMessage(text) self.log.setTextColor(QColor("grey")) self.log.append("--------------") def init_font_settings(self, text_item): font_layout = QVBoxLayout() font_layout.setAlignment(Qt.AlignTop) font_layout.addWidget(QLabel("Family")) font_layout.addWidget(init_font_families_widget(text_item)) font_layout.addWidget(QLabel("Size")) font_layout.addLayout(init_font_size_layout(text_item)) font_layout.addWidget(QLabel("Color")) font_layout.addWidget(init_font_color_widget(text_item)) font_layout.addWidget(QLabel("Style")) font_layout.addWidget(init_font_style_widget(text_item)) font_layout.addWidget(QLabel("Weight")) font_layout.addWidget(init_font_weight_widget(text_item)) font_layout.addWidget(QLabel("Capitalization")) font_layout.addWidget(init_capitalization_widget(text_item)) font_layout.addWidget(QLabel("Stretch")) font_layout.addWidget(init_stretch_layout(text_item)) font_layout.addWidget(init_kerning_widget(text_item)) font_layout.addWidget(init_overline_widget(text_item)) font_layout.addWidget(init_strikeout_widget(text_item)) font_layout.addWidget(init_underline_widget(text_item)) font_layout.addWidget(init_letter_spacing_widget(text_item)) font_group_box = QGroupBox("Font") font_group_box.setLayout(font_layout) layout = QVBoxLayout() layout.addWidget(font_group_box) text_item_group_box = QGroupBox("Text item") text_item_layout = QVBoxLayout() text_item_layout.setAlignment(Qt.AlignTop) text_item_layout.addLayout( init_item_opacity_layout(text_item)) text_item_layout.addLayout(init_item_rotation_layout(text_item)) text_item_group_box.setLayout(text_item_layout) layout.addWidget(text_item_group_box) layout.addWidget(self.init_item_duplicate_widget(text_item)) layout.addWidget(init_item_delete_widget(text_item)) settings = QWidget() settings.setLayout(layout) self.settings_scroll_area.setWidget(settings) self.layout.setCurrentWidget(self.settings_scroll_area) def init_image_settings(self, image_item): image_layout = QVBoxLayout() image_layout.setAlignment(Qt.AlignTop) image_group_box = QGroupBox("Image") image_layout.addLayout(init_item_opacity_layout(image_item)) image_layout.addSpacing(30) image_layout.addLayout(init_item_rotation_layout(image_item)) image_layout.addSpacing(30) image_layout.addLayout(init_image_scale_layout(image_item)) image_group_box.setLayout(image_layout) layout = QVBoxLayout() layout.addWidget(image_group_box) layout.addWidget(self.init_item_duplicate_widget(image_item)) layout.addWidget(init_item_delete_widget(image_item)) settings = QWidget() settings.setLayout(layout) self.settings_scroll_area.setWidget(settings) self.layout.setCurrentWidget(self.settings_scroll_area) def duplicate_item(self, item): item_config = item.get_config() if item_config["item_type"] == "text": new_item = custom_items.CustomQGraphicsTextItem() new_item.setParent(self) else: new_item = custom_items.CustomQGraphicsPixmapItem(item_config[ "image_path"]) new_item.parent = self new_item.path = item_config["image_path"] item.scene().addItem(new_item) new_item.load_config(item_config) new_item.scene().clearSelection() new_item.setSelected(True) def init_item_duplicate_widget(self, item): button = QPushButton("Duplicate") button.clicked.connect(lambda: self.duplicate_item(item)) return button
class SongDetailPage(QWidget): def __init__(self, url=None): super(SongDetailPage, self).__init__() self.url = url self.loading = False self.artwork_content = None self.artwork_size = 400 self.song = None self.layout = QStackedLayout() self.layout.setMargin(0) self.loading_label = QLabel('Loading...', alignment=Qt.AlignCenter) self.layout.addWidget(self.loading_label) self.layout.setCurrentWidget(self.loading_label) self.page_widget = QScrollArea() self.page_widget.setWidgetResizable(True) widget = QWidget(self.page_widget) widget.setMinimumWidth(800) self.page_widget.setWidget(widget) self.page_layout = QVBoxLayout(widget, alignment=Qt.AlignTop) self.page_layout.setContentsMargins(25, 25, 25, 25) self.layout.addWidget(self.page_widget) self.setLayout(self.layout) self.thread = RunThread(self.get_song_info, self.on_song_info) def render_song_info(self): # Header self.page_layout.addWidget(Header(song=self.song)) # Title title = H2(self.song['title']) title.setWordWrap(True) title.setStyleSheet('padding-top: 20px;') self.page_layout.addWidget(title) # Inner container that contains Image + Songlist inner_container = QWidget() inner_container_layout = QHBoxLayout(alignment=Qt.AlignTop | Qt.AlignLeft) inner_container_layout.setMargin(0) inner_container_layout.setSpacing(25) inner_container_layout.setContentsMargins(0, 25, 0, 0) inner_container.setLayout(inner_container_layout) self.page_layout.addWidget(inner_container) # Image self.artwork_label = QLabel() self.artwork_label.setStyleSheet( css('background-color: {{color}};', color=colors.PLACEHOLDER_COLOR)) self.artwork_label.setFixedWidth(self.artwork_size) self.artwork_label.setFixedHeight(self.artwork_size) inner_container_layout.addWidget(self.artwork_label, alignment=Qt.AlignTop) self.get_artwork_thread = RunThread(self.fetch_artwork, self.on_artwork_loaded) # Songlist inner_container_layout.addWidget( Songlist(songlist=self.song['songlist']), alignment=Qt.AlignTop) def fetch_artwork(self): time.sleep(1) logging.info('GET {}'.format(self.song['artwork'])) try: response = requests.get(self.song['artwork']) self.artwork_content = response.content except Exception: return True return True def on_artwork_loaded(self): if self.artwork_content: imgWidget = QImage() imgWidget.loadFromData(self.artwork_content) picture = QPixmap(imgWidget) picture = picture.scaled(self.artwork_size, self.artwork_size, Qt.KeepAspectRatio) self.artwork_label.setPixmap(picture) else: logging.warn('[FAILED] GET {}'.format(self.song['artwork'])) def get_song_info(self): self.loading = True spider = CoreRadioSpider() self.song = spider.get_song_info(url=self.url) return True def on_song_info(self): if self.song is not None: self.render_song_info() self.loading = False self.layout.setCurrentWidget(self.page_widget) else: self.loading_label.setText( "Something wen't wrong, please try again")
class NodeChoiceWidget(QWidget): def __init__(self, flow, nodes): super(NodeChoiceWidget, self).__init__() self.flow = flow self.all_nodes = sort_nodes(nodes) # copy, no ref self.nodes = [] # 'current_nodes' are the currently selectable ones, they get recreated every time update_view() is called self.current_nodes = [] self.all_current_node_widgets = [] self.active_node_widget_index = -1 self.active_node_widget = None self.reset_list() self.node_widget_index_counter = 0 self.setMinimumWidth(260) self.setFixedHeight(450) self.main_layout = QVBoxLayout(self) self.main_layout.setAlignment(Qt.AlignTop) self.setLayout(self.main_layout) # adding all stuff to the layout self.search_line_edit = QLineEdit(self) self.search_line_edit.setPlaceholderText('search for node...') self.search_line_edit.textChanged.connect(self.update_view) self.layout().addWidget(self.search_line_edit) self.list_scroll_area = QScrollArea(self) self.list_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.list_scroll_area.setHorizontalScrollBarPolicy( Qt.ScrollBarAsNeeded) self.list_scroll_area.setWidgetResizable(True) self.list_scroll_area_widget = QWidget() self.list_scroll_area.setWidget(self.list_scroll_area_widget) self.list_layout = QVBoxLayout() self.list_layout.setAlignment(Qt.AlignTop) self.list_scroll_area_widget.setLayout(self.list_layout) self.layout().addWidget(self.list_scroll_area) self.setFixedHeight(400) self.update_view('') try: f = open('resources/stylesheets/dark_node_choice_widget.txt') self.setStyleSheet(f.read()) f.close() except FileNotFoundError: pass self.search_line_edit.setFocus() def mousePressEvent(self, event): QWidget.mousePressEvent(self, event) event.accept() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.flow.hide_node_choice_widget() if event.key() == Qt.Key_Down: index = self.active_node_widget_index+1 if \ self.active_node_widget_index+1 < len(self.all_current_node_widgets) else 0 self.set_active_node_widget_index(index) if event.key() == Qt.Key_Up: index = self.active_node_widget_index-1 if \ self.active_node_widget_index-1 > -1 else len(self.all_current_node_widgets)-1 self.set_active_node_widget_index(index) if event.key() == Qt.Key_Return: if len(self.all_current_node_widgets) > 0: self.place_node(self.active_node_widget_index) def wheelEvent(self, event): QWidget.wheelEvent(self, event) event.accept() def refocus(self): self.search_line_edit.setFocus() self.search_line_edit.selectAll() def update_list(self, nodes): self.nodes = sort_nodes(nodes) def reset_list(self): self.nodes = self.all_nodes def update_view(self, text=''): text = text.lower() for i in reversed(range(self.list_layout.count())): self.list_layout.itemAt(i).widget().setParent(None) self.current_nodes.clear() self.all_current_node_widgets.clear() self.node_widget_index_counter = 0 # select valid elements from text # nodes nodes_names_dict = {} for n in self.nodes: nodes_names_dict[n] = n.title.lower() sorted_indices = self.get_sorted_dict_matching_search( nodes_names_dict, text) for n, index in sorted_indices.items(): self.current_nodes.append(n) # nodes if len(self.current_nodes) > 0: nodes_label = QLabel('Hover for description') nodes_label_font = QFont('Poppins') nodes_label_font.setPixelSize(15) nodes_label.setStyleSheet('color: #9bbf9dff; border: none;') nodes_label.setFont(nodes_label_font) self.list_layout.addWidget(nodes_label) for n in self.current_nodes: node_widget = self.create_list_item_widget(n) self.list_layout.addWidget(node_widget) self.all_current_node_widgets.append(node_widget) if len(self.all_current_node_widgets) > 0: self.set_active_node_widget_index(0) # self.setFixedWidth(self.minimumWidth()) # print(self.list_scroll_area_widget.width()) def get_sorted_dict_matching_search(self, items_dict, text): indices_dict = {} for item, name in items_dict.items( ): # the strings are already lowered here Debugger.debug(item, name, text) if name.__contains__(text): index = name.index(text) indices_dict[item] = index return { k: v for k, v in sorted(indices_dict.items(), key=lambda i: i[1]) } def create_list_item_widget(self, node): node_widget = NodeWidget(self, node) # , self.node_images[node]) node_widget.custom_focused_from_inside.connect( self.node_widget_focused_from_inside) node_widget.setObjectName('node_widget_' + str(self.node_widget_index_counter)) self.node_widget_index_counter += 1 node_widget.chosen.connect(self.node_widget_chosen) return node_widget def node_widget_focused_from_inside(self): self.set_active_node_widget_index( self.all_current_node_widgets.index(self.sender())) def set_active_node_widget_index(self, index): self.active_node_widget_index = index node_widget = self.all_current_node_widgets[index] if self.active_node_widget: self.active_node_widget.set_custom_focus(False) node_widget.set_custom_focus(True) self.active_node_widget = node_widget self.list_scroll_area.ensureWidgetVisible(self.active_node_widget) def node_widget_chosen( self ): # gets called when the user clicks on a node widget with the mouse self.flow.ignore_mouse_event = True # otherwise, it will receive a mouse press event index = int( self.sender().objectName()[self.sender().objectName().rindex('_') + 1:]) self.place_node(index) def place_node(self, index): node_index = index node = self.current_nodes[node_index] self.flow.place_node__cmd(node) self.flow.hide_node_choice_widget()
def init_meta_tab(self): # Center panels ------------------------------------------------------- self.groups_list = [] # Left-side panel: forms self.btn_load_meta = QPushButton('Load metafile') self.btn_load_meta.setIcon(self.style().standardIcon( QStyle.SP_ArrowDown)) self.btn_load_meta.clicked.connect( lambda: self.load_meta_file(filename=None)) self.btn_load_meta.setToolTip( "The YAML file with metadata for this conversion.\n" "You can customize the metadata in the forms below.") self.btn_save_meta = QPushButton('Save metafile') self.btn_save_meta.setIcon(self.style().standardIcon( QStyle.SP_DriveFDIcon)) self.btn_save_meta.clicked.connect(self.save_meta_file) self.btn_run_conversion = QPushButton('Run conversion') self.btn_run_conversion.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) self.btn_run_conversion.clicked.connect(self.run_conversion) self.btn_form_editor = QPushButton('Form -> Editor') self.btn_form_editor.clicked.connect(self.form_to_editor) self.lbl_nwb_file = QLabel('Output nwb file:') self.lbl_nwb_file.setToolTip( "Path to the NWB file that will be created.") self.lin_nwb_file = QLineEdit('') self.btn_nwb_file = QPushButton() self.btn_nwb_file.setIcon(self.style().standardIcon( QStyle.SP_DialogOpenButton)) self.btn_nwb_file.clicked.connect(self.load_nwb_file) l_grid1 = QGridLayout() l_grid1.setColumnStretch(3, 1) l_grid1.addWidget(self.btn_load_meta, 0, 0, 1, 1) l_grid1.addWidget(self.btn_save_meta, 0, 1, 1, 1) l_grid1.addWidget(self.btn_run_conversion, 0, 2, 1, 1) l_grid1.addWidget(QLabel(), 0, 3, 1, 1) l_grid1.addWidget(self.btn_form_editor, 0, 4, 1, 2) l_grid1.addWidget(self.lbl_nwb_file, 1, 0, 1, 1) l_grid1.addWidget(self.lin_nwb_file, 1, 1, 1, 3) l_grid1.addWidget(self.btn_nwb_file, 1, 4, 1, 1) # Adds custom files/dir paths fields if len(self.source_paths.keys()) == 0: self.lbl_source_file = QLabel('source files:') self.lin_source_file = QLineEdit('') self.btn_source_file = QPushButton() self.btn_source_file.setIcon(self.style().standardIcon( QStyle.SP_DialogOpenButton)) self.btn_source_file.clicked.connect(self.load_source_files) l_grid1.addWidget(self.lbl_source_file, 3, 0, 1, 1) l_grid1.addWidget(self.lin_source_file, 3, 1, 1, 3) l_grid1.addWidget(self.btn_source_file, 3, 4, 1, 1) else: self.group_source_paths = QGroupBox('Source paths') self.grid_source = QGridLayout() self.grid_source.setColumnStretch(3, 1) ii = -1 for k, v in self.source_paths.items(): ii += 1 lbl_src = QLabel(k + ':') setattr(self, 'lbl_src_' + str(ii), lbl_src) lin_src = QLineEdit('') setattr(self, 'lin_src_' + str(ii), lin_src) btn_src = QPushButton() btn_src.setIcon(self.style().standardIcon( QStyle.SP_DialogOpenButton)) setattr(self, 'btn_src_' + str(ii), btn_src) if v['type'] == 'file': btn_src.clicked.connect( (lambda x: lambda: self.load_source_files(x[0], x[1]))( [ii, k])) else: btn_src.clicked.connect( (lambda x: lambda: self.load_source_dir(x[0], x[1]))( [ii, k])) self.grid_source.addWidget(lbl_src, ii, 0, 1, 1) self.grid_source.addWidget(lin_src, ii, 1, 1, 3) self.grid_source.addWidget(btn_src, ii, 4, 1, 1) self.group_source_paths.setLayout(self.grid_source) l_grid1.addWidget(self.group_source_paths, 3, 0, 1, 6) # Adds custom kwargs checkboxes if len(self.kwargs_fields.keys()) > 0: self.group_kwargs = QGroupBox('KWARGS') self.grid_kwargs = QGridLayout() self.grid_kwargs.setColumnStretch(4, 1) ii = -1 for k, v in self.kwargs_fields.items(): ii += 1 chk_kwargs = QCheckBox(k) chk_kwargs.setChecked(v) chk_kwargs.clicked.connect( (lambda x: lambda: self.update_kwargs(x[0], x[1]))([ii, k])) setattr(self, 'chk_kwargs_' + str(ii), chk_kwargs) self.grid_kwargs.addWidget(chk_kwargs, ii // 4, ii % 4, 1, 1) self.group_kwargs.setLayout(self.grid_kwargs) l_grid1.addWidget(self.group_kwargs, 4, 0, 1, 6) self.l_vbox1 = QVBoxLayout() self.l_vbox1.addStretch() scroll_aux = QWidget() scroll_aux.setLayout(self.l_vbox1) l_scroll = QScrollArea() l_scroll.setWidget(scroll_aux) l_scroll.setWidgetResizable(True) self.l_vbox2 = QVBoxLayout() self.l_vbox2.addLayout(l_grid1) self.l_vbox2.addWidget(l_scroll) # Right-side panel # Metadata text editor_label = QLabel('Metafile preview:') r_grid1 = QGridLayout() r_grid1.setColumnStretch(1, 1) r_grid1.addWidget(editor_label, 0, 0, 1, 1) r_grid1.addWidget(QLabel(), 0, 1, 1, 1) self.editor = QTextEdit() r_vbox1 = QVBoxLayout() r_vbox1.addLayout(r_grid1) r_vbox1.addWidget(self.editor) # Logger log_label = QLabel('Log:') r_grid2 = QGridLayout() r_grid2.setColumnStretch(1, 1) r_grid2.addWidget(log_label, 0, 0, 1, 1) r_grid2.addWidget(QLabel(), 0, 1, 1, 1) self.logger = QTextEdit() self.logger.setReadOnly(True) r_vbox2 = QVBoxLayout() r_vbox2.addLayout(r_grid2) r_vbox2.addWidget(self.logger) r_vsplitter = QSplitter(QtCore.Qt.Vertical) ru_w = QWidget() ru_w.setLayout(r_vbox1) rb_w = QWidget() rb_w.setLayout(r_vbox2) r_vsplitter.addWidget(ru_w) r_vsplitter.addWidget(rb_w) # Metadata/conversion tab Layout self.left_w = QWidget() self.left_w.setLayout(self.l_vbox2) self.splitter = QSplitter(QtCore.Qt.Horizontal) self.splitter.addWidget(self.left_w) self.splitter.addWidget(r_vsplitter) self.metadata_layout = QVBoxLayout() self.metadata_layout.addWidget(self.splitter) self.tab_metadata = QWidget() self.tab_metadata.setLayout(self.metadata_layout) self.tabs.addTab(self.tab_metadata, 'Metadata/Conversion') # Background color p = self.palette() p.setColor(self.backgroundRole(), QtCore.Qt.white) self.setPalette(p)
class Category(QWidget): def __init__(self, name, categoryTotal, transactions, state): ## initialCollapsed = True ## QWidget.__init__(self) self.name = name self.state = state self.transactions = [] self.categoryTotal = categoryTotal self.collapsed = initialCollapsed self.sectionLayout = QVBoxLayout() self.gridContainer = QWidget() self.transactionArea = QScrollArea() self.progressBar = ProgressBar() self.addHeader() self.addBody(transactions) self.setLayout(self.sectionLayout) self.setAcceptDrops(True) def addHeader(self): self.headerText = QLabel() self.headerText.setText(self.name) # self.headerText.setFont(self.boldFont) self.headerText.setCursor(Qt.PointingHandCursor) self.headerText.mousePressEvent = self.toggleCollapsed self.headerText.setMinimumWidth(100) self.collapseButton = QLabel() self.collapseButton.setText('⊞' if self.collapsed else '⊟') self.collapseButton.setMaximumWidth(12) self.collapseButton.setCursor(Qt.PointingHandCursor) self.collapseButton.setToolTip('collapse') self.collapseButton.mousePressEvent = self.toggleCollapsed self.progressBar = ProgressBar(self.name == 'Income') self.progressBar.setCursor(Qt.PointingHandCursor) self.progressBar.mousePressEvent = lambda event: self.promptEditVal( self.name, self.categoryTotal) self.progressBar.setToolTip('Edit Max') self.uncategorizedAmtDisplay = QLabel() self.uncategorizedAmtDisplay.setText('${:.2f}'.format( self.categoryTotal)) self.uncategorizedAmtDisplay.setAlignment(Qt.AlignRight | Qt.AlignVCenter) header = QHBoxLayout() header.addWidget(self.collapseButton) header.addWidget(self.headerText) if self.name != 'Uncategorized': header.addWidget(self.progressBar) if self.name != 'Income': self.headerText.setContextMenuPolicy(Qt.CustomContextMenu) self.connect( self.headerText, SIGNAL('customContextMenuRequested(const QPoint &)'), self.titleContextMenu) else: header.addWidget(self.uncategorizedAmtDisplay) self.sectionLayout.addLayout(header) def addBody(self, transactions): self.transactionGrid = QVBoxLayout() self.addTransactions(transactions) def addTransactions(self, transactions): self.transactions += transactions clearLayout(self.transactionGrid) self.transactionGrid = QVBoxLayout() self.transactions.sort(key=lambda t: t.date) for idx, transaction in enumerate(self.transactions): line = TransactionLine(transaction) line.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(line, SIGNAL('customContextMenuRequested(const QPoint &)'), lambda event, t=line: self.lineContextMenu(t)) line.adjustSize() self.transactionGrid.addWidget(line) self.gridContainer.deleteLater() self.gridContainer = QWidget() self.gridContainer.setLayout(self.transactionGrid) self.transactionArea.deleteLater() self.transactionArea.setParent(None) self.transactionArea = QScrollArea() self.transactionArea.setWidget(self.gridContainer) self.transactionArea.setWidgetResizable(True) if self.collapsed: self.transactionArea.hide() self.sectionLayout.addWidget(self.transactionArea) self.sectionLayout.setMargin(0) self.updateAmtDisplay() # if self.name != 'Uncategorized': # self.toggleCollapsed(None) def removeTransaction(self, transaction): newTransactions = self.transactions newTransactions.remove(transaction) self.transactions = [] self.addTransactions(newTransactions) def updateAmtDisplay(self): totalAmount = 0 if len(self.transactions): for idx, transaction in enumerate(self.transactions): totalAmount += float(transaction.amt) if self.name == 'Uncategorized': self.uncategorizedAmtDisplay.setText('$' + str(totalAmount)) else: self.progressBar.updateValues(self.categoryTotal, totalAmount) def lineContextMenu(self, transactionLine): menu = QMenu(self) editAction = QAction('Edit Transaction Name') # editAction.triggered.connect(lambda evt: self.editTransaction(transactionLine)) menu.addAction(editAction) menu.exec_(QCursor.pos()) def titleContextMenu(self, event): menu = QMenu(self) editAction = QAction('Edit Name') editAction.triggered.connect(self.promptEditTitle) removeAction = QAction('Remove Category') removeAction.triggered.connect(self.removeCategory) menu.addAction(editAction) menu.addAction(removeAction) menu.exec_(QCursor.pos()) def toggleCollapsed(self, event): if not event or event.button() == Qt.MouseButton.LeftButton: self.collapsed = not self.collapsed if self.collapsed: self.collapseButton.setText('⊞') self.transactionArea.hide() self.collapseButton.setToolTip('expand') else: self.collapseButton.setText('⊟') self.transactionArea.show() self.collapseButton.setToolTip('collapse') def dragEnterEvent(self, event): if event.mimeData().hasText() and self.name != 'Income': event.setDropAction(Qt.CopyAction) event.accept() else: event.ignore() def dropEvent(self, event): if event.mimeData().hasText(): transactionTitle = event.mimeData().text() self.state.next(Events.transaction_drop_event, transactionTitle, self.name) def getTransactions(self): return self.transactions def removeCategory(self): self.state.next(Events.remove_category, self.name, self.transactions) def promptEditVal(self, title, currentAmount): modal = EditCategoryTotalModal() data = modal.getData(currentAmount) if data: self.state.next(Events.update_category_total, self.name, int(data)) self.categoryTotal = int(data) self.updateAmtDisplay() def promptEditTitle(self): modal = EditTextModal(self.name, 'Edit Title') data = modal.getData() if data: self.state.next(Events.update_category_title, self.name, data) self.name = data self.headerText.setText(self.name)
class TabbedToolBox(QTabWidget): object_icon_clicked: SignalInstance = Signal(ObjectIcon) def __init__(self, parent=None): super(TabbedToolBox, self).__init__(parent) self.setTabPosition(self.East) self._recent_toolbox = ObjectToolBox(self) self._recent_toolbox.object_icon_clicked.connect( self.object_icon_clicked) self._recent_toolbox.object_placed.connect(self._on_object_dragged) self._objects_toolbox = ObjectToolBox(self) self._objects_toolbox.object_icon_clicked.connect( self.object_icon_clicked) self._objects_toolbox.object_placed.connect(self._on_object_dragged) self._enemies_toolbox = ObjectToolBox(self) self._enemies_toolbox.object_icon_clicked.connect( self.object_icon_clicked) self._enemies_toolbox.object_placed.connect(self._on_object_dragged) self._object_scroll_area = QScrollArea(self) self._object_scroll_area.setWidgetResizable(True) self._object_scroll_area.setWidget(self._objects_toolbox) self._enemies_scroll_area = QScrollArea(self) self._enemies_scroll_area.setWidgetResizable(True) self._enemies_scroll_area.setWidget(self._enemies_toolbox) self.addTab(self._recent_toolbox, "Recent") self.addTab(self._object_scroll_area, "Objects") self.addTab(self._enemies_scroll_area, "Enemies") self.show_level_object_tab() self.setWhatsThis( "<b>Object Toolbox</b><br/>" "Contains all objects and enemies/items, that can be placed in this type of level. Which are " "available depends on the object set, that is selected for this level.<br/>" "You can drag and drop objects into the level or click to select them. After selecting " "an object, you can place it by clicking the middle mouse button anywhere in the level." "<br/><br/>" "Note: Some items, like blocks with items in them, are displayed as they appear in the ROM, " "mouse over them and check their names in the ToolTip, or use the object dropdown to find " "them directly.") def sizeHint(self): size = super().sizeHint() width = self._recent_toolbox.sizeHint().width() width = max(width, self._objects_toolbox.sizeHint().width()) width = max(width, self._enemies_toolbox.sizeHint().width()) size.setWidth( max(width, size.width()) + self.tabBar().width() + self._object_scroll_area.verticalScrollBar().width() + 5) return size def show_recent_tab(self): self.setCurrentIndex(self.indexOf(self._recent_toolbox)) def show_level_object_tab(self): self.setCurrentIndex(self.indexOf(self._object_scroll_area)) def show_enemy_item_tab(self): self.setCurrentIndex(self.indexOf(self._enemies_scroll_area)) def select_object(self, level_object): recent_tab_showing = self.currentIndex() == self.indexOf( self._recent_toolbox) if self._recent_toolbox.has_object( level_object) and recent_tab_showing: pass elif isinstance(level_object, LevelObject): self.show_level_object_tab() elif isinstance(level_object, EnemyObject): self.show_enemy_item_tab() def set_object_set(self, object_set_index, graphic_set_index=-1): self._recent_toolbox.clear() self._objects_toolbox.clear() self._objects_toolbox.add_from_object_set(object_set_index, graphic_set_index) self._enemies_toolbox.clear() self._enemies_toolbox.add_from_enemy_set(object_set_index) def add_recent_object(self, level_object: Union[EnemyObject, LevelObject]): self._recent_toolbox.place_at_front(level_object) def _on_object_dragged(self, object_icon: ObjectIcon): self.add_recent_object(object_icon.object)
class Ui_MainWindow(object): def setupUi(self, MainWindow): if not MainWindow.objectName(): MainWindow.setObjectName("MainWindow") MainWindow.resize(1121, 770) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.scrollArea = QScrollArea(MainWindow) self.scrollArea.setObjectName("scrollArea") self.scrollArea.setGeometry(QRect(10, 10, 1101, 531)) self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 1099, 529)) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.tableWidget = QTableWidget(self.scrollAreaWidgetContents) self.tableWidget.setObjectName("tableWidget") self.tableWidget.setGeometry(QRect(0, 0, 1099, 529)) self.tableWidget.setAutoFillBackground(True) self.tableWidget.horizontalHeader().setCascadingSectionResizes(False) self.tableWidget.setColumnCount(24) self.column_label = ['begin', 'end', 'time interval', 'login', 'mac ab', 'ULSK1', 'BRAS ip', 'start count', 'alive count', 'stop count', 'incoming', 'outcoming', 'error_count', 'code 0', 'code 1011', 'code 1100', 'code -3', 'code -52', 'code -42', 'code -21', 'code -40', ' code -44', 'code -46', ' code -38'] self.tableWidget.setHorizontalHeaderLabels(self.column_label) self.PathFile = QLineEdit(self.centralwidget) self.PathFile.setGeometry(QRect(200, 10, 831, 90)) self.PathFile.setObjectName("PathFile") self.progressBar_2 = QProgressBar(MainWindow) self.progressBar_2.setObjectName("progressBar_2") self.progressBar_2.setGeometry(QRect(400, 590, 651, 23)) self.progressBar_2.setProperty("value", 0) self.progressBar = QProgressBar(MainWindow) self.progressBar.setObjectName("progressBar") self.progressBar.setGeometry(QRect(400, 650, 651, 23)) self.progressBar.setProperty("value", 0) self.comboBox = QComboBox(MainWindow) self.comboBox.addItem("") self.comboBox.addItem("") self.comboBox.addItem("") self.comboBox.setObjectName("comboBox") self.comboBox.setGeometry(QRect(20, 690, 161, 28)) self.comboBox.setEditable(False) self.comboBox.setDuplicatesEnabled(False) self.dateTimeEdit = QDateTimeEdit(MainWindow) self.dateTimeEdit.setObjectName("dateTimeEdit") self.dateTimeEdit.setGeometry(QRect(190, 690, 151, 28)) self.dateTimeEdit.setCurrentSection(QDateTimeEdit.DaySection) self.saveButt = QPushButton(self.centralwidget) self.saveButt.setGeometry(QRect(20, 610, 321, 28)) self.saveButt.setObjectName("saveButt") self.AnalizButt = QPushButton(self.centralwidget) self.AnalizButt.setGeometry(QRect(20, 570, 321, 28)) self.AnalizButt.setObjectName("AnalizButt") self.OpenButt = QPushButton(self.centralwidget) self.OpenButt.setGeometry(QRect(400, 710, 615, 40)) self.OpenButt.setObjectName("OpenButt") self.change_cbButt = QPushButton(self.centralwidget) self.change_cbButt.setObjectName("change_cbButt") self.change_cbButt.setGeometry(QRect(20, 730, 321, 28)) self.change_sizeButt = QPushButton(self.centralwidget) self.change_sizeButt.setObjectName(u"change_sizeButt") self.change_sizeButt.setGeometry(QRect(20, 650, 161, 28)) self.change_sizeLine = QLineEdit(self.centralwidget) self.change_sizeLine.setObjectName("change_sizeLine") self.change_sizeLine.setGeometry(QRect(190, 650, 151, 28)) self.OpenButt.raise_() self.AnalizButt.raise_() self.change_cbButt.raise_() self.progressBar.raise_() self.PathFile.raise_() self.tableWidget.raise_() self.saveButt.raise_() self.comboBox.raise_() self.dateTimeEdit.raise_() self.change_sizeButt.raise_() MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) QMetaObject.connectSlotsByName(MainWindow) # setupUi def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", "MainWindow", None)) #if QT_CONFIG(accessibility) self.tableWidget.setAccessibleName("") #endif // QT_CONFIG(accessibility) self.comboBox.setItemText(0, QCoreApplication.translate("MainWindow", "Парамметры выборки", None)) self.comboBox.setItemText(1, QCoreApplication.translate("MainWindow", "Выборка по логину", None)) self.comboBox.setItemText(2, QCoreApplication.translate("MainWindow", "Выборка по дате", None)) self.comboBox.setCurrentText(QCoreApplication.translate("MainWindow", "\u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a", None)) self.comboBox.setPlaceholderText("") self.dateTimeEdit.setDisplayFormat(QCoreApplication.translate("MainWindow", "dd.MM.yyyy H:mm:ss")) self.saveButt.setText(QCoreApplication.translate("MainWindow", "Сохранить данные", None)) self.OpenButt.setText(QCoreApplication.translate("MainWindow", "Выбрать папку", None)) self.change_cbButt.setText(QCoreApplication.translate("MainWindow", "Изменить парамметры", None)) self.AnalizButt.setText(QCoreApplication.translate("MainWindow", "Анализировать", None)) self.change_sizeLine.setPlaceholderText(QCoreApplication.translate("MainWindow", "1-1000", None)) self.change_sizeButt.setText(QCoreApplication.translate("MainWindow", u"Изменить выборку", None))
class TraceWindow(QMainWindow): def __init__(self, qmp): QMainWindow.__init__(self) self.qmp = qmp os.system('rm /tmp/errors.log 2>/dev/null') self.trace_events = self.qmp.hmp_command('info trace-events') self.qmp.hmp_command('logfile /tmp/errors.log') self.trace_events = sorted( self.trace_events['return'].split('\r\n'))[1:] self.activated = [] self.length = 100 self.timer = QTimer(self) self.timer.timeout.connect(self.disp_output) self.timer.start(100) self.init_ui() def init_ui(self): self.setWindowTitle('Trace Event Window') self.setGeometry(100, 100, 800, 600) bar = self.menuBar() file_ = bar.addMenu('File') export_log = QAction('Save to File', self, triggered=lambda: self.save_log()) options = bar.addMenu('Options') auto_refresh = QAction( 'Auto Refresh', self, checkable=True, triggered=lambda: self.timer.start(100) if auto_refresh.isChecked() else self.timer.stop()) auto_refresh.setChecked(True) options.addAction(auto_refresh) file_.addAction(export_log) vgrid = QVBoxLayout() grid = QHBoxLayout() self.tree = QTreeWidget() self.tree.setHeaderLabels(['Name']) self.top = [] self.lst = [] for n, event in enumerate(self.trace_events): word = event.split('_')[0] if word not in self.top: self.top.append(word) item = QTreeWidgetItem(self.tree) self.lst.append(item) item.setText(0, word) subitem = QTreeWidgetItem(item) subitem.setText(0, ' ' + event.split(' : ')[0]) # subitem.setCheckState(0, Qt.Unchecked) cbox = QCheckBox() cbox.stateChanged.connect(lambda state, text=subitem.text(0): self. handle_checked(state, text)) self.tree.setItemWidget(subitem, 0, cbox) # self.tree.setColumnWidth(0, 25) self.tracelist = QLabel() self.disp_output() self.traceview = QScrollArea() self.traceview.setWidget(self.tracelist) self.traceview.setWidgetResizable(True) search = QHBoxLayout() self.search_bar = QLineEdit(self) self.completer = QCompleter(self.top, self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.search_bar.setCompleter(self.completer) search_button = QPushButton('Search') search_button.clicked.connect(lambda: self.tree.setCurrentItem( self.lst[self.top.index(self.search_bar.text())])) expand = QPushButton('▼') expand.setFixedSize(QSize(25, 25)) expand.clicked.connect(lambda: self.tree.expandAll()) collapse = QPushButton('▲') collapse.setFixedSize(QSize(25, 25)) collapse.clicked.connect(lambda: self.tree.collapseAll()) self.search_bar.returnPressed.connect(lambda: search_button.click()) search.addWidget(self.search_bar) search.addWidget(search_button) search.addWidget(expand) search.addWidget(collapse) self.digest = QLabel() vgrid.addLayout(search) vgrid.addWidget(self.tree) vgridwid = QWidget() vgridwid.setLayout(vgrid) split = QSplitter(Qt.Horizontal) split.addWidget(vgridwid) split.addWidget(self.traceview) split.setStretchFactor(1, 1) # grid.addLayout(vgrid) grid.addWidget(split) # grid.addWidget(self.tracelist) self.disp_output() center = QWidget() center.setLayout(grid) self.setCentralWidget(center) self.show() def disp_output(self): self.shorten_file() with open('/tmp/errors.log', 'r') as errors: self.digest = [] lines = 0 for line in errors: if re.match(r"\d+@\d+\.\d+:.*", line): self.digest.append(line) lines += 1 if not self.digest: self.digest = ['<font color="grey">Empty...</font>'] self.digest = ''.join(self.digest[-self.length:]) self.tracelist.setText(self.digest) self.tracelist.setFont(QFont('Monospace', 10)) self.tracelist.setTextInteractionFlags(Qt.TextSelectableByMouse) def shorten_file(self): with open('/tmp/errors.log', 'r+') as tracefile: content = ''.join(tracefile.readlines()[-(self.length * 3):]) tracefile.seek(0) tracefile.truncate() tracefile.write(content) def save_log(self): name = QFileDialog.getSaveFileName(self, 'Save File', '', 'Text files (*.txt)') log_file = open(name[0], 'w') log_file.write(self.digest) log_file.close() def handle_checked(self, state, text): if state: self.qmp.hmp_command('trace-event %s on' % text.strip()) self.activated.append(text) else: self.qmp.hmp_command('trace-event %s off' % text.strip()) self.activated.remove(text) def closeEvent(self, event): self.timer.stop() for e in self.activated: self.qmp.hmp_command('trace-event %s off' % e.strip()) os.system('rm /tmp/errors.log') event.accept()
class MainWindow(QWidget): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setWindowTitle('Commander V 0.1') self.resize(1600, 1200) #Functions self.commands = { # GUI commands 'CLEARALL': self.clear_all_results, # Basic Text Commands 'transform': TextFunctions().transform, '-tf': TextFunctions().transform, # Translation Commands 'translate': TextFunctions().translate, '-tr': TextFunctions().translate, # Information Commands 'extract': ExtractFunctions().extract, '-ext': ExtractFunctions().extract, 'regex': ExtractFunctions().regex, '-re': ExtractFunctions().regex, # Image Functions 'grayscale': ImageFunctions().grayscale, 'bw': ImageFunctions().bw, 'flip': ImageFunctions().flip, 'invert': ImageFunctions().invert } # Fonts self.text_font = QFont('monospace', 16) self.button_font = QFont('monospace', 18) self.console_font = QFont('monospace', 14) # Create widgets self.input_area = QTabWidget() self.input_area.setFont(self.text_font) self.text_area = QTextEdit() self.text_area.setFont(self.text_font) self.file_area = FileArea() self.image_area = ImageArea() self.result_scroll_area = QScrollArea() self.result_area = QWidget() self.result_area.layout = QVBoxLayout() self.result_area.setLayout(self.result_area.layout) self.result_scroll_area.setWidget(self.result_area) self.result_scroll_area.setWidgetResizable(True) self.console = QTextEdit() self.console.setMaximumHeight(300) self.console.setReadOnly(True) self.console.setStyleSheet( 'background-color: #0F0E0D; color: white; border: 0;') self.console.setFont(self.console_font) def set_command_line_focus(event): self.command_line.setFocus() self.console.mousePressEvent = set_command_line_focus self.command_line = QLineEdit() # self.command_line.setStyleSheet('background-color: #0F0E0D; color: white; border: 0;') self.command_line.setFont(self.console_font) self.command_line.setPlaceholderText('Enter command') self.command_line.setTextMargins(5, 0, 0, 0) self.execute_button = QPushButton("Execute") self.execute_button.setFont(self.button_font) self.execute_button.setStyleSheet( 'background-color: red; color: white;') self.command_line.returnPressed.connect(self.execute_button.click) self.execute_button.setVisible(False) # Create layout and add widgets self.layout = QGridLayout() self.top_layout = QGridLayout() # Tabbed input area self.top_layout.addWidget(self.input_area, 0, 0) self.input_area.insertTab(0, self.text_area, 'Text') self.input_area.insertTab(1, self.file_area, 'File') self.input_area.insertTab(2, self.image_area, 'Image') self.top_layout.addWidget(self.result_scroll_area, 0, 2) self.bottom_layout = QGridLayout() self.bottom_layout.setSpacing(0) self.bottom_layout.addWidget(self.console, 0, 0) self.bottom_layout.addWidget(self.command_line, 1, 0) self.bottom_layout.addWidget(self.execute_button, 2, 0) # Set layout self.setLayout(self.layout) self.layout.addLayout(self.top_layout, 0, 0) self.layout.addLayout(self.bottom_layout, 1, 0) # Add button signal to execution function self.execute_button.clicked.connect(self.execute_command) # Set focus to command line self.command_line.setFocus() def clear_all_results(self, *args, **kwargs): for i in reversed(range(self.result_area.layout.count())): self.result_area.layout.itemAt(i).widget().setParent(None) # Executes command def execute_command(self): command = self.command_line.text().split(' ') command_action = command[0] if len(command) == 1: command_arguments = [] else: command_arguments = ' '.join(command[1:]) if self.input_area.currentIndex() == 0: data_input = self.text_area elif self.input_area.currentIndex() == 1: data_input = self.file_area.file_preview elif self.input_area.currentIndex() == 2: data_input = self.image_area.selected_file_name.text() # Data Types: if self.input_area.currentIndex() == 0 or self.input_area.currentIndex( ) == 1: plain_data = data_input.toPlainText() html_data = data_input.toHtml() markdown_data = data_input.toMarkdown() data = { 'plain': plain_data, 'html': html_data, 'markdown': markdown_data } elif self.input_area.currentIndex() == 2: image_data = data_input data = {'image': image_data} if command_action in self.commands: function = self.commands[command_action] result = function(data, command_arguments) # Check to see if function returns a result, then sets variables if result: result_output = result['output'] result_data_type = result['type'] result_console_message = result['console_message'] command_print = QLineEdit(self.command_line.text()) command_print.setFont(self.console_font) command_print.setReadOnly(True) # Clears command line self.command_line.clear() # Inserts the command and the result as widgets if result_output: self.result_area.layout.addWidget(command_print) result_block = ResultBlock(result_output, result_data_type) self.result_area.layout.addWidget(result_block) # Make sure scrollbar is always at the bottom scroll_bar = self.result_scroll_area.verticalScrollBar() scroll_bar.rangeChanged.connect( lambda x, y: scroll_bar.setValue(y)) # Inserts console_message to console if result_console_message: self.console.append(result['console_message'])
class AddIssue(QWidget): def __init__(self, parent): QWidget.__init__(self) self.setWindowTitle("Add issue") self.setWindowIcon(QIcon("assets/icons/icon.ico")) self.setGeometry(450, 150, 750, 950) # self.setFixedSize(self.size()) self.Parent = parent self.filePathName = "" self.UI() self.show() def UI(self): self.widgets() self.layouts() def widgets(self): self.scroll = QScrollArea() self.scroll.setWidgetResizable(True) self.dropdownData = IssuesDropdownData() # Top layout widgets self.addIssueImg = QLabel() self.img = QPixmap('assets/icons/create-issue.png') self.addIssueImg.setPixmap(self.img) self.addIssueImg.setAlignment(Qt.AlignCenter) self.titleText = QLabel("ADD ISSUE") self.titleText.setObjectName("add_issue_title_txt") self.titleText.setAlignment(Qt.AlignCenter) # Middle layout widgets self.dateEntry = QDateTimeEdit(calendarPopup=True) self.dateEntry.setDateTime(QDateTime.currentDateTime()) self.priorityEntry = QComboBox() self.priorityEntry.addItems(self.dropdownData.priorityItems()) self.observerEntry = QComboBox() self.observerEntry.addItems(self.dropdownData.observerItems()) self.revisionTeamEntry = QComboBox() self.revisionTeamEntry.addItems(self.dropdownData.revTeamItems()) self.inspectionNameEntry = QComboBox() self.inspectionNameEntry.addItems(self.dropdownData.inspNameItems()) self.observationThemeEntry = QComboBox() self.observationThemeEntry.addItems(self.dropdownData.hseThemeItems()) self.facilityEntry = QComboBox() self.facilityEntry.addItems(self.dropdownData.facilityItems()) self.facilitySupervisorEntry = QComboBox() self.facilitySupervisorEntry.addItems( self.dropdownData.facSupervisorItems()) self.specificLocationEntry = QTextEdit() self.inspectedDepartmentEntry = QComboBox() self.inspectedDepartmentEntry.addItems( self.dropdownData.inspDeptItems()) self.inspectedContractorEntry = QComboBox() self.inspectedContractorEntry.addItems( self.dropdownData.inspContrItems()) self.inspectedSubcontractorEntry = QComboBox() self.inspectedSubcontractorEntry.addItems( self.dropdownData.inspSubcontrItems()) self.deadlineEntry = QDateTimeEdit(calendarPopup=True) self.deadlineEntry.setDateTime(QDateTime.currentDateTime().addDays(14)) # Bottom layout widgets self.attachFilesBtn = QPushButton("Attach files") self.attachFilesBtn.clicked.connect(self.funcAttachFiles) self.addActionBtn = QPushButton("Add action") self.rootCauseEntry = QComboBox() self.rootCauseEntry.setEditable(True) self.rootCauseDetailsEntry = QTextEdit() self.rootCauseActionPartyEntry = QComboBox() self.rootCauseActionPartyEntry.setEditable(True) self.addRootCauseBtn = QPushButton("Add root cause") self.submitObservationBtn = QPushButton("Add issue") self.submitObservationBtn.clicked.connect(self.addIssue) self.cancelBtn = QPushButton("Cancel") self.cancelBtn.clicked.connect(self.closeWindow) def layouts(self): self.mainLayout = QVBoxLayout() self.topLayout = QHBoxLayout() self.bottomLayout = QFormLayout() self.bottomLayout.setVerticalSpacing(20) self.bottomBtnLayout = QHBoxLayout() # Put elements into frames for visual distinction self.topFrame = QFrame() self.bottomFrame = QFrame() # Add widgets to top layout # self.topLayout.addWidget(self.addIssueImg) self.topLayout.addWidget(self.titleText) self.topFrame.setLayout(self.topLayout) # Add widgets to middle layout # self.bottomLayout.addRow(self.issueInfoTitleText) self.bottomLayout.addRow(QLabel("Inspection Date: "), self.dateEntry) self.bottomLayout.addRow(QLabel("Priority: "), self.priorityEntry) self.bottomLayout.addRow(QLabel("Observer: "), self.observerEntry) self.bottomLayout.addRow(QLabel("Revision Team: "), self.revisionTeamEntry) self.bottomLayout.addRow(QLabel("Inspection Name: "), self.inspectionNameEntry) self.bottomLayout.addRow(QLabel("HSE Theme: "), self.observationThemeEntry) self.bottomLayout.addRow(QLabel("Facility: "), self.facilityEntry) self.bottomLayout.addRow(QLabel("Facility supervisor: "), self.facilitySupervisorEntry) self.bottomLayout.addRow(QLabel("Specific location: "), self.specificLocationEntry) self.bottomLayout.addRow(QLabel("Inspected department: "), self.inspectedDepartmentEntry) self.bottomLayout.addRow(QLabel("Inspected contractor: "), self.inspectedContractorEntry) self.bottomLayout.addRow(QLabel("Inspected subcontractor: "), self.inspectedSubcontractorEntry) self.bottomLayout.addRow(QLabel("Deadline: "), self.deadlineEntry) self.bottomLayout.addRow(QLabel(""), self.attachFilesBtn) # self.bottomLayout.addRow(QLabel(""), self.addActionBtn) # self.bottomLayout.addRow(QLabel(""), self.addRootCauseBtn) # self.bottomLayout.addRow(QLabel(""), self.submitObservationBtn) self.bottomBtnLayout.addWidget(self.cancelBtn) self.bottomBtnLayout.addItem( QSpacerItem(200, 5, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.bottomBtnLayout.addWidget(self.submitObservationBtn) self.bottomBtnLayout.setAlignment(Qt.AlignBottom) self.bottomLayout.addRow(self.bottomBtnLayout) self.bottomFrame.setLayout(self.bottomLayout) # Make bottom frame scollable self.scroll.setWidget(self.bottomFrame) # Add frames to main layout self.mainLayout.addWidget(self.topFrame) self.mainLayout.addWidget(self.scroll) self.setLayout(self.mainLayout) @Slot() def closeWindow(self): self.close() @Slot() def addIssue(self): date = self.dateEntry.text() priority = self.priorityEntry.currentText() observer = self.observerEntry.currentText() revisionTeam = self.revisionTeamEntry.currentText() inspectionName = self.inspectionNameEntry.currentText() observationTheme = self.observationThemeEntry.currentText() facility = self.facilityEntry.currentText() facilitySupervisor = self.facilitySupervisorEntry.currentText() specificLocation = self.specificLocationEntry.toPlainText() inspectedDept = self.inspectedDepartmentEntry.currentText() inspectedContr = self.inspectedContractorEntry.currentText() inspectedSubcontr = self.inspectedSubcontractorEntry.currentText() deadline = self.deadlineEntry.text() # If user selected a file to attach, rename the file and copy it to media folder if self.filePathName != "": self.newFilePath = ShCopy2(self.filePathName, self.attachedFilePath) im = Image.open(self.filePathName) im_resized = self.crop_max_square(im).resize((800, 800), Image.LANCZOS) im_resized.save(self.attachedResizedFilePath) else: self.attachedFilePath = "" self.attachedResizedFilePath = "" if date and priority and observer and revisionTeam and inspectionName and observationTheme and facility \ and facilitySupervisor and specificLocation and inspectedDept and inspectedContr \ and inspectedSubcontr and deadline != "": try: query = "INSERT INTO issues (issue_date, issue_priority, issue_observer, issue_team," \ "issue_inspection, issue_theme, issue_facility, issue_fac_supervisor," \ "issue_spec_loc, issue_insp_dept, issue_insp_contr, issue_insp_subcontr, issue_deadline, " \ "created_on, photo_original_path, photo_resized_path) " \ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" # The purpose of this block is to make created_on timestamp the same format as other dates currentTime = QDateTimeEdit() currentTime.setDateTime(QDateTime.currentDateTime()) now = currentTime.text() db.cur.execute( query, (date, priority, observer, revisionTeam, inspectionName, observationTheme, facility, facilitySupervisor, specificLocation, inspectedDept, inspectedContr, inspectedSubcontr, deadline, now, self.attachedFilePath, self.attachedResizedFilePath)) db.conn.commit() QMessageBox.information(self, "Info", "Issue has been added") self.Parent.funcDisplayIssues() self.close() except: QMessageBox.information(self, "Info", "Issue has not been added") else: QMessageBox.information(self, "Info", "Fields cannot be empty") # Need to figure out how attach files to items in db @Slot() def funcAttachFiles(self): self.filePathName = QFileDialog.getOpenFileName( self, "Attach file...", "/", "Image files (*.jpg *.jpeg *.png)")[0] if osPath.isfile(self.filePathName): fileName, fileExt = osPath.splitext(self.filePathName) if fileExt == '.jpg' or fileExt == '.jpeg' or fileExt == '.png': date = datetime.now() randomSuffix = "".join( random.choice(string.ascii_lowercase) for i in range(15)) self.attachedFilePath = osPath.join( "assets", "media", "issues-media", "photos", ("{:%d%b%Y_%Hh%Mm}".format(date) + randomSuffix + fileExt)) self.attachedResizedFilePath = osPath.join( "assets", "media", "issues-media", "photos_resized", ("{:%d%b%Y_%Hh%Mm}".format(date) + randomSuffix + "_resized" + fileExt)) QMessageBox.information(self, "Info", "File attached successfully") else: QMessageBox.information(self, "Info", "Wrong file type!") else: QMessageBox.information(self, "Info", "No file selected") # Image processing functions @Slot() def crop_center(self, pil_img, crop_width, crop_height): img_width, img_height = pil_img.size fill_color = 'rgba(255, 255, 255, 1)' if pil_img.mode in ('RGBA', 'LA'): background = Image.new(pil_img.mode[:-1], pil_img.size, fill_color) background.paste(pil_img, pil_img.split()[-1]) image = background return pil_img.crop( ((img_width - crop_width) // 2, (img_height - crop_height) // 2, (img_width + crop_width) // 2, (img_height + crop_height) // 2)) # Crop the largest possible square from a rectangle @Slot() def crop_max_square(self, pil_img): return self.crop_center(pil_img, min(pil_img.size), min(pil_img.size))
class DeviceDetectionTab(QWidget, Form): def __init__(self, parent=None, database=None): super(DeviceDetectionTab, self).__init__(parent) Form.__init__(self, parent) self._database = database self._table_name = "DEVICE_DETECTION" self._table = DeviceDetection self._layout = QFormLayout(self) self._scroll = QScrollArea(self) self._scroll.setWidgetResizable(True) self._layout.addRow(self._scroll) self._scroll_contents = QWidget(self) self._scroll_contents.setObjectName("ddscroll") self._scroll_contents.setStyleSheet( 'QWidget[objectName^="ddscroll"] {background-color: #FFFFFF;}') self._scroll_contents.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._scroll.setWidget(self._scroll_contents) self._scroll_layout = QFormLayout(self._scroll_contents) self._child_widgets_not_set = True self.connectWidgets() self.setDeviceStatusArea() self.setDetectedLeakArea() self.setLeakDiscoveredArea() self._scroll_layout.addRow(VerticalFiller(self)) def setDeviceStatusArea(self): form_box = FormGroupBox("Device Status", self) form_box.frame_layout.addRow(DeviceDetection.ALERT_PRESENT.value) # Alert type inner group box alert_form_box = FormGroupBox("Check all that apply:", self) alert_grid = QWidget(self) alert_lay = QGridLayout(alert_grid) alert_lay.setContentsMargins(0, 0, 0, 0) alert_lay.addWidget(DeviceDetection.AUDIO_AT_DEVICE.value, 0, 0) alert_lay.addWidget(DeviceDetection.IN_APP_ALERT.value, 0, 1) alert_lay.addWidget(DeviceDetection.AUTO_CALL.value, 0, 2) alert_lay.addWidget(DeviceDetection.VISUAL_AT_DEVICE.value, 1, 0) alert_lay.addWidget(DeviceDetection.TEXT_MSG.value, 1, 1) alert_lay.addWidget(DeviceDetection.CALL_BY_RETAILER.value, 1, 2) alert_lay.addWidget(DeviceDetection.PUSH_NOTIFY.value, 2, 0) alert_lay.addWidget(DeviceDetection.EMAIL.value, 2, 1) other_row = QWidget(self) other_lay = QFormLayout(other_row) other_lay.setContentsMargins(0, 0, 0, 0) other_lay.addRow("Other?", DeviceDetection.OTHER.value) alert_lay.addWidget(other_row, 2, 2) alert_form_box.frame_layout.addRow(alert_grid) form_box.frame_layout.addRow(alert_form_box) self._scroll_layout.addRow(form_box) def setDetectedLeakArea(self): form_box = FormGroupBox("Detected Leak from Device", self) form_box.frame_layout.addRow(DeviceDetection.SHOWS_LEAK.value) vol_unit_row = QWidget(self) vol_unit_lay = QHBoxLayout(vol_unit_row) vol_unit_lay.setContentsMargins(0, 0, 0, 0) vol_unit_lay.addWidget(QLabel("Volume:", self)) vol_unit_lay.addWidget(DeviceDetection.LEAK_VOL.value) vol_unit_lay.addWidget(QLabel("Unit:", self)) vol_unit_lay.addWidget(DeviceDetection.UNIT.value) vol_unit_lay.addWidget(HorizontalFiller(self)) form_box.frame_layout.addRow(vol_unit_row) form_box.frame_layout.addRow("Leak Location:", DeviceDetection.LEAK_LOC.value) self._scroll_layout.addRow(form_box) def setLeakDiscoveredArea(self): form_box = FormGroupBox("Leak Discovered when Device was Installed", self) form_box.frame_layout.addRow(DeviceDetection.LEAK_DISCOVERED.value) vol_unit_row = QWidget(self) vol_unit_lay = QHBoxLayout(vol_unit_row) vol_unit_lay.setContentsMargins(0, 0, 0, 0) vol_unit_lay.addWidget(QLabel("Volume:", self)) vol_unit_lay.addWidget(DeviceDetection.LEAK_DIS_VOL.value) vol_unit_lay.addWidget(QLabel("Unit:", self)) vol_unit_lay.addWidget(DeviceDetection.LEAK_DIS_UNIT.value) vol_unit_lay.addWidget(HorizontalFiller(self)) form_box.frame_layout.addRow(vol_unit_row) form_box.frame_layout.addRow("Discovered Leak Location:", DeviceDetection.LEAK_DIS_LOC.value) self._scroll_layout.addRow(form_box) def connectWidgets(self): super().connectWidgets() # LEAK IS DETECTED leak_det_widgets = [ DeviceDetection.LEAK_VOL, DeviceDetection.UNIT, DeviceDetection.LEAK_LOC ] DeviceDetection.SHOWS_LEAK.value.stateChanged.connect( self.enableDisableCheck(DeviceDetection.SHOWS_LEAK, leak_det_widgets)) # LEAK DISCOVERED leak_dis_widgets = [ DeviceDetection.LEAK_DIS_VOL, DeviceDetection.LEAK_DIS_UNIT, DeviceDetection.LEAK_DIS_LOC ] DeviceDetection.LEAK_DISCOVERED.value.stateChanged.connect( self.enableDisableCheck(DeviceDetection.LEAK_DISCOVERED, leak_dis_widgets)) # ALERT IS PRESENT. disable_widgets = [ DeviceDetection.AUDIO_AT_DEVICE, DeviceDetection.IN_APP_ALERT, DeviceDetection.AUTO_CALL, DeviceDetection.VISUAL_AT_DEVICE, DeviceDetection.TEXT_MSG, DeviceDetection.CALL_BY_RETAILER, DeviceDetection.PUSH_NOTIFY, DeviceDetection.EMAIL, DeviceDetection.OTHER ] DeviceDetection.ALERT_PRESENT.value.stateChanged.connect( self.enableDisableCheck(DeviceDetection.ALERT_PRESENT, disable_widgets)) # DEFAULT DISABLE WIDGETS for widget in leak_det_widgets: widget.value.setDisabled(True) for widget in leak_dis_widgets: widget.value.setDisabled(True) for widget in disable_widgets: widget.value.setDisabled(True)
class MyWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.thread = SerialMonitorThread() self.thread.dataReady.connect(self.get_data, Qt.QueuedConnection) self.thread.setTerminationEnabled(True) #Menu self.setPalette(get_verifone_color()) collapsible = CollapsibleWidget() self.init_logging(collapsible) self.init_download(collapsible) self.init_analyser(collapsible) collapsible.add_sections() # Scroll Area self.createLoggingDisplayLabel() self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.text) self.scrollArea.setWidgetResizable(True) hLayout = QHBoxLayout() #hLayout.addLayout(vLayout) hLayout.addWidget(collapsible) hLayout.addWidget(self.scrollArea) self.setLayout(hLayout) def init_logging(self, collapsible): self.logger = QPushButton("Start Logging", self) self.logger.setFont(QFont("Times", 14, QFont.Bold)) self.logger.clicked.connect(lambda: self.display_log_data()) self.logger.setStyleSheet("background-color: white") #self.filterLayout = QtWidgets.QHBoxLayout() self.logFilterLabel = QLabel('Filter', self) self.logFilterLabel.setFont(QFont("Times", 14, QFont.Bold)) self.logFilterLabel.setPalette(get_white_color_text()) self.logFilterLabel.setFixedWidth(60) self.logFilter = QLineEdit(self) self.logFilter.setPalette(get_white_color()) self.logFilter.setStyleSheet("background-color: white") self.logFilter.setFixedWidth(200) #self.filterLayout.addWidget(self.logFilterLabel, QtCore.Qt.AlignLeft) #self.filterLayout.addWidget(self.logFilter, QtCore.Qt.AlignLeft) self.serialList = QComboBox() ports = get_available_serial_ports() if (len(ports) == 1): self.serialList.addItem(ports[0]) self.thread.set_comport(self.serialList.currentText()) else: self.serialList.addItem("Select") for port in ports: self.serialList.addItem(port) self.serialList.currentIndexChanged.connect( lambda: self.set_serial_port()) self.serialList.setStyleSheet("background-color: white") self.clear = QPushButton("Clear Log File", self) self.clear.setStyleSheet("background-color: white") self.clear.setFont(QFont("Times", 14, QFont.Bold)) self.clear.clicked.connect(lambda: self.clear_data()) widget = QFrame(collapsible.get_tree()) widget.setPalette(get_verifone_color()) title = "Logging" self.loggerGrid = QGridLayout(widget) self.loggerGrid.addWidget(self.logger, 0, 0, 1, 2) self.loggerGrid.addWidget(self.logFilterLabel, 1, 0, 1, 1) self.loggerGrid.addWidget(self.logFilter, 1, 1, 1, 1) self.loggerGrid.addWidget(self.serialList, 2, 0, 1, 2) self.loggerGrid.addWidget(self.clear, 3, 0, 1, 2) collapsible.include_section(title, widget) def init_download(self, collapsible): self.download = QPushButton("Download Package", self) self.download.setFont(QFont("Times", 14, QFont.Bold)) self.download.clicked.connect(lambda: self.send_file()) self.download.setStyleSheet("background-color: white") self.loadDownloadFile = QPushButton("Load File", self) self.loadDownloadFile.setFont(QFont("Times", 14, QFont.Bold)) self.loadDownloadFile.clicked.connect(self.loadFromFile) self.loadDownloadFile.setStyleSheet("background-color: white") self.downloadFileName = QLineEdit("File name", self) self.downloadFileName.setStyleSheet("background-color: white") self.downloadFileName.setFixedWidth(300) self.downloadAddress = QLineEdit("IP Address", self) self.downloadAddress.setStyleSheet("background-color: white") self.downloadAddress.setFixedWidth(300) self.downloadStatus = QLabel("Download Status", self) self.downloadStatus.setStyleSheet( "background-color: rgba(3, 169, 229, 0); color : white") self.downloadStatus.setFixedWidth(300) widget = QFrame(collapsible.get_tree()) title = "Download" self.downloadGrid = QGridLayout(widget) self.downloadGrid.addWidget(self.download, 0, 0, 1, 2) self.downloadGrid.addWidget(self.loadDownloadFile, 1, 0, 1, 2) self.downloadGrid.addWidget(self.downloadFileName, 2, 0, 1, 2) self.downloadGrid.addWidget(self.downloadAddress, 3, 0, 1, 2) self.downloadGrid.addWidget(self.downloadStatus, 4, 0, 1, 2) collapsible.include_section(title, widget) def init_analyser(self, collapsible): self.performanceData = QPushButton("View Performance Data", self) self.performanceData.setFont(QFont("Times", 14, QFont.Bold)) self.performanceData.clicked.connect( lambda: self.display_performance_data()) self.performanceData.setStyleSheet("background-color: white") self.performanceChart = QPushButton("View Performance Chart", self) self.performanceChart.setFont(QFont("Times", 14, QFont.Bold)) self.performanceChart.clicked.connect( lambda: self.display_performance_chart()) self.performanceChart.setStyleSheet("background-color: white") widget = QFrame(collapsible.get_tree()) title = "Analyser" self.analyserGrid = QGridLayout(widget) self.analyserGrid.addWidget(self.performanceData, 0, 0, 1, 2) self.analyserGrid.addWidget(self.performanceChart, 1, 0, 1, 2) collapsible.include_section(title, widget) def loadFromFile(self): fileName, _ = QFileDialog.getOpenFileName( self, "Load Package", '', "Download Files (*.tgz);;All Files (*)") if not fileName: return try: in_file = open(str(fileName), 'rb') except IOError: QMessageBox.information( self, "Unable to open file", "There was an error opening \"%s\"" % fileName) return in_file.close() self.downloadFileName.setText(fileName) def createLoggingDisplayLabel(self): # Display Area self.text = QPlainTextEdit(self) self.text.setReadOnly(True) self.text.setFont(QFont("Times", 12, QFont.Bold)) self.text.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard) def clear_data(self): self.text.clear() os.remove(logfile_path, dir_fd=None) def display_log_data(self): #send_file() if ('COM' in self.serialList.currentText()): self.createLoggingDisplayLabel() self.scrollArea.setWidget(self.text) self.thread.stop() self.thread.start() data = get_data_from_file(logfile_path) if (len(data) > 0 and data != None): self.text.appendPlainText(data) self.logger.setDisabled(True) def get_data(self, data): if (len(data) > 0): logFile = open(logfile_path, "a") logFile.write(data) logFile.close() filterText = self.logFilter.text() if filterText in data.rstrip(): self.text.appendPlainText(data.rstrip()) vbar = self.scrollArea.verticalScrollBar() vbar.setValue(vbar.maximum()) def display_performance_data(self): self.thread.stop() data = get_data_from_file(logfile_path) jsonData = translate_data_to_json(data) self.performanceData = DisplayPerformanceData() self.performanceData.loadCsv( os.path.join(base_log_path, "performance_data.csv")) self.scrollArea.setWidget(self.performanceData) self.logger.setDisabled(False) def display_performance_chart(self): self.thread.stop() self.scrollArea.setWidget(get_performace_chart()) self.logger.setDisabled(False) def set_serial_port(self): self.thread.set_comport(self.serialList.currentText()) def send_file(self): base_path = os.path.join("c:/", "VFI", 'wks', 'global-payment-application', 'GPA', 'output', 'vos2', 'gpa', 'dl.gpa-1.0.0.0-000.tgz') fileName = self.downloadFileName.text() try: in_file = open(str(fileName), 'rb') except IOError: QMessageBox.information( self, "Unable to open file", "There was an error opening \"%s\"" % fileName) return in_file.close() load_vos_package_ip('192.168.0.104', fileName, self.downloadStatus)
def __init__(self, parent=None, title='', wSize=QSize(500, 500), scroll=True, search=False, modal=True): super().__init__(parent) self.setWindowTitle(parent.tr(title)) self.setStyleSheet( " * {background-color: rgb(220, 220, 220); color: black;}\ QLabel {selection-background-color: blue; selection-color: white}\ QLineEdit {background-color: white;}") self.setModal(modal) self.label = QLabel() self.label.setAlignment(Qt.AlignTop) #self.label.setStyleSheet("selection-background-color: blue; selection-color: white}") vl = QVBoxLayout() if search: ed = QLineEdit() ed.setMaximumWidth(300) ed.setPlaceholderText('Search') vl = QVBoxLayout() hl = QHBoxLayout() hl.addWidget(ed) button = QPushButton('Next') button.setAutoDefault(False) button.setMaximumWidth(60) hl.addWidget(button) vl.addLayout(hl) matches = [] current = 0 def f(searchedText): import re nonlocal current matches.clear() current = 0 matches.extend([ m.span() for m in re.finditer( searchedText, self.label.text(), re.IGNORECASE | re.MULTILINE | re.DOTALL) ]) if matches: item = matches[0] self.label.setSelection(item[0], item[1] - item[0]) metrics = self.label.fontMetrics() tabSize = 4 rect = metrics.boundingRect( 0, 0, 150000, 150000, self.label.alignment() | Qt.TextExpandTabs, self.label.text()[:item[1]], tabSize) scarea.ensureVisible(0, rect.height()) def g(): nonlocal current if not matches or not button.isDown(): return current = (current + 1) % len(matches) item = matches[current] self.label.setSelection(item[0], item[1] - item[0]) metrics = self.label.fontMetrics() tabSize = 4 rect = metrics.boundingRect( 0, 0, 150000, 150000, self.label.alignment() | Qt.TextExpandTabs, self.label.text()[:item[1]], tabSize) scarea.ensureVisible(0, rect.height()) button.pressed.connect(g) ed.textEdited.connect(f) if scroll: scarea = QScrollArea() scarea.setWidget(self.label) scarea.setWidgetResizable(True) vl.addWidget(scarea) else: vl.addWidget(self.label) self.setLayout(vl) self.setFixedSize(wSize)
def __init__(self, test_name: str, reference_image: QPixmap, generated_image: QPixmap): super(ApprovalDialog, self).__init__() self.setWindowTitle(test_name) main_layout = QVBoxLayout(self) ref_image = QLabel() ref_image.setPixmap(reference_image) gen_image = QLabel() gen_image.setPixmap(generated_image) scroll_area = QScrollArea() self.layout().addWidget(scroll_area) screen_width, screen_height = QGuiApplication.primaryScreen().size( ).toTuple() if reference_image.width() + gen_image.width() >= screen_width: self.image_layout = QVBoxLayout() else: self.image_layout = QHBoxLayout() self.image_layout.addStretch() self.image_layout.addWidget(ref_image) self.image_layout.addWidget(gen_image) self.image_layout.addStretch() scroll_area.setWidget(QWidget()) scroll_area.setWidgetResizable(True) scroll_area.widget().setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) scroll_area.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) scroll_area.widget().setLayout(self.image_layout) def _sizeHint(): orig_size = scroll_area.widget().sizeHint() orig_size.setHeight(orig_size.height() + 20) orig_size.setWidth(orig_size.width() + 20) if orig_size.width() > screen_width - 20: orig_size.setWidth(screen_width - 20) if orig_size.height() > screen_height - 20: orig_size.setHeight(screen_height - 20) return orig_size scroll_area.sizeHint = _sizeHint button_box = QDialogButtonBox() button_box.addButton( "Reject", QDialogButtonBox.RejectRole).clicked.connect(self.reject) button_box.addButton(QDialogButtonBox.Ignore).clicked.connect( self._on_ignore) button_box.addButton("Accept as new Reference", QDialogButtonBox.ApplyRole).clicked.connect( self._on_overwrite) main_layout.addWidget(scroll_area) main_layout.addWidget(button_box, alignment=Qt.AlignCenter)
class RepLeakTab(QWidget, Form): def __init__(self, parent=None, database=None): super(RepLeakTab, self).__init__(parent) Form.__init__(self, parent) self._database = database self._table_name = "REP_LEAK" self._table = RepLeak self._layout = QFormLayout(self) self._child_widgets_not_set = True self._scroll = QScrollArea(self) self._scroll.setWidgetResizable(True) self._layout.addRow(self._scroll) self._scroll_contents = QWidget(self) self._scroll_contents.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._scroll_contents.setObjectName("rplscroll") self._scroll_contents.setStyleSheet( 'QWidget[objectName^="rplscroll"] {background-color: #FFFFFF;}') self._scroll.setWidget(self._scroll_contents) self._scroll_layout = QFormLayout(self._scroll_contents) self.connectWidgets() RepLeak.MET_DEV_AGREE.value.setChecked(True) RepLeak.CUS_INFORM.value.setChecked(True) self.setRepLeakArea() self._scroll_layout.addRow(VerticalFiller(self)) def setRepLeakArea(self): form_box = FormGroupBox("Leak Discovered By SNWA Represenative", self) form_box.frame_layout.addRow(RepLeak.LEAK_SHOWING.value) vol_unit = QWidget(self) vol_unit_row = SmartHFormLayout(vol_unit) vol_unit_row.addRow(["Volume at Meter:", "Unit:"], [RepLeak.VOL.value, RepLeak.UNIT.value]) form_box.frame_layout.addRow(vol_unit) form_box.frame_layout.addRow("Leak Location:", RepLeak.LOCS.value) metdev_box = FormGroupBox("Meter and Device", self) metdev_box.frame_layout.addRow(RepLeak.MET_DEV_AGREE.value) metdev_box.frame_layout.addRow("Which has a Higher Volume?", RepLeak.MET_OR_DEV_HIGHER.value) metdev_box.frame_layout.addRow(RepLeak.CUS_INFORM.value) metdev_box.frame_layout.addRow("If No, Explain:", RepLeak.REA_CUS_NOT_INFORM.value) metdev_box.frame_layout.addRow("Expected Resolution (If Known):", RepLeak.RESOLUTION.value) form_box.frame_layout.addRow(metdev_box) form_box.frame_layout.addRow(VerticalFiller(self)) self._scroll_layout.addRow(form_box) def connectWidgets(self): super().connectWidgets() rep_leak_widgets = [ RepLeak.VOL, RepLeak.UNIT, RepLeak.LOCS, RepLeak.MET_DEV_AGREE, RepLeak.MET_OR_DEV_HIGHER, RepLeak.CUS_INFORM, RepLeak.REA_CUS_NOT_INFORM, RepLeak.RESOLUTION ] RepLeak.LEAK_SHOWING.value.stateChanged.connect( self.enableDisableCheck(RepLeak.LEAK_SHOWING, rep_leak_widgets)) RepLeak.MET_DEV_AGREE.value.stateChanged.connect( self.enableDisableCheck(RepLeak.MET_DEV_AGREE, [RepLeak.MET_OR_DEV_HIGHER], reverse_check=True)) RepLeak.CUS_INFORM.value.stateChanged.connect( self.enableDisableCheck(RepLeak.CUS_INFORM, [RepLeak.REA_CUS_NOT_INFORM], reverse_check=True)) for widget in rep_leak_widgets: widget.value.setDisabled(True) def enableDisableCheck(self, check_widget, widgets=[], reverse_check=False): def enableDisable(): if len(widgets) > 0: for widget in widgets: if reverse_check: widget.value.setDisabled( check_widget.value.isChecked()) else: widget.value.setEnabled(check_widget.value.isChecked()) if RepLeak.MET_DEV_AGREE.value.isChecked(): RepLeak.MET_OR_DEV_HIGHER.value.setDisabled(True) if RepLeak.CUS_INFORM.value.isChecked(): RepLeak.REA_CUS_NOT_INFORM.value.setDisabled(True) if check_widget == RepLeak.LEAK_SHOWING: self.MetDevAgreeCheck() self.CusInformCheck() return enableDisable def MetDevAgreeCheck(self): if not RepLeak.MET_DEV_AGREE.value.isEnabled(): RepLeak.MET_DEV_AGREE.value.setChecked(True) def CusInformCheck(self): if not RepLeak.CUS_INFORM.value.isEnabled(): RepLeak.CUS_INFORM.value.setChecked(True)
class ImageArea(QWidget): def __init__(self, parent=None): super(ImageArea, self).__init__(parent) self.image = None # Fonts self.text_font = QFont('monospace', 16) self.button_font = QFont('monospace', 18) self.selected_file_name_font = QFont('monospace', 10) self.file_selection_button = QPushButton('Select Image') self.file_selection_button.setFont(self.button_font) self.file_selection_button.clicked.connect(self.open_file) self.selected_file_name = QLineEdit() self.selected_file_name.setReadOnly(True) self.selected_file_name.setFont(self.selected_file_name_font) self.image_preview_container = QScrollArea() self.image_preview = QWidget() image_preview_layout = QVBoxLayout() self.image_preview.setLayout(image_preview_layout) self.image_preview_container.setWidget(self.image_preview) self.image_preview_container.setWidgetResizable(True) # Create layout and add widgets layout = QVBoxLayout() layout.addWidget(self.file_selection_button) layout.addWidget(self.selected_file_name) layout.addWidget(self.image_preview_container) # Set layout self.setLayout(layout) def get_file_path(self): file_path = QFileDialog.getOpenFileNames( filter="Images (*.png *.xpm *.jpg *.jpeg)") file_path = file_path[0][0] self.selected_file_name.setText(file_path) return file_path def get_file_contents(self, file_path): pixmap = QPixmap(file_path) label = QLabel('', self) label.setPixmap(pixmap) return label def open_file(self): # self.file_preview.clear() self.clear_image() self.selected_file_name.clear() file_path = self.get_file_path() file_contents = self.get_file_contents(file_path) self.image_preview.layout().addWidget(file_contents) self.image = file_path return file_contents def clear_image(self): for i in reversed(range(self.image_preview.layout().count())): self.image_preview.layout().itemAt(i).widget().setParent(None)
class Sidebar(QWidget): def __init__(self): super(Sidebar, self).__init__() DownloadHistorySignal.put.connect(self.render_total_downloads) DownloadHistorySignal.progress.connect(self.render_total_downloads) DownloadHistorySignal.deleted.connect(self.render_total_downloads) self.sidebar_width = 300 self.setStyleSheet('background: rgba(0, 0, 0, 0.05);') self.setFixedWidth(self.sidebar_width) self.layout = QVBoxLayout() self.layout.setMargin(0) self.scrollarea = QScrollArea() self.scrollarea.setWidgetResizable(True) widget = QWidget(self.scrollarea) self.scrollarea.setWidget(widget) self.page_layout = QVBoxLayout(widget, alignment=Qt.AlignTop) self.page_layout.addWidget(SearchBar()) self.register_menu_item('Home', icon='home', page=HomeFeed) self.register_menu_item('Downloads', icon='download', page=Downloads) self.register_menu_item('Settings', icon='cog', page=UserSettings) self.add_total_downloads_widget() self.render_total_downloads() self.layout.addWidget(self.scrollarea) self.setLayout(self.layout) def add_total_downloads_widget(self): total_downloads_label_size = 19 self.total_downloads_label = QLabel('1', self.downloads_menu_item) self.total_downloads_label.setAlignment(Qt.AlignCenter) self.total_downloads_label.hide() self.total_downloads_label.setFixedWidth(total_downloads_label_size) self.total_downloads_label.setFixedHeight(total_downloads_label_size) y = self.downloads_menu_item.sizeHint().height() / 2 - round( total_downloads_label_size / 2) x = self.sidebar_width - total_downloads_label_size * 3 self.total_downloads_label.move(x, y) self.total_downloads_label.setStyleSheet( css(''' text-align: center; background: {{backgroundColor}}; padding: 2px; border-radius: 9px; ''', backgroundColor=colors.GREY_COLOR)) @Slot(dict) def render_total_downloads(self): history = get_download_history() in_progress_items_length = len( [item for item in history if item['progress'] != 100]) self.total_downloads_label.setText(str(in_progress_items_length)) if in_progress_items_length > 0: self.total_downloads_label.show() else: self.total_downloads_label.hide() def register_menu_item(self, text, icon=None, page=None): btn = IconButton(icon=icon, text=text, on_click=lambda: PageSignal.changed.emit(page())) property_name = '{}_menu_item'.format( re.sub(r'\s+', '_', text).lower()) setattr(self, property_name, btn) self.page_layout.addWidget(btn)
class PrevLeakTab(QWidget, Form): def __init__(self, parent=None, database=None): super(PrevLeakTab, self).__init__(parent) Form.__init__(self, parent) self._database = database self._table_name = "PREV_LEAK" self._table = PrevLeak self._layout = QFormLayout(self) self._child_widgets_not_set = True self._scroll = QScrollArea(self) self._scroll.setWidgetResizable(True) self._layout.addRow(self._scroll) self._scroll_contents = QWidget(self) self._scroll_contents.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._scroll_contents.setObjectName("prevleakscroll") self._scroll_contents.setStyleSheet( 'QWidget[objectName^="prevleakscroll"] {background-color: #FFFFFF;}' ) self._scroll.setWidget(self._scroll_contents) self._scroll_layout = QFormLayout(self._scroll_contents) self.connectWidgets() self.setPrevLeakArea() self._scroll_layout.addRow(QWidget(self)) self._scroll_layout.addRow(VerticalFiller(self)) def getTabName(self): return "Previous Leak" def setPrevLeakArea(self): form_box = FormGroupBox("Previous Leak Information", self) form_box.frame_layout.addRow(PrevLeak.INFLUENCE.value) form_box.frame_layout.addRow("Location:", PrevLeak.LOC.value) form_box.frame_layout.addRow("Cost to Repair ($):", PrevLeak.COST_TO_REPAIR.value) vol_unit = QWidget() vol_unit_lay = QHBoxLayout(vol_unit) vol_unit_lay.setContentsMargins(0, 0, 0, 0) vol_unit_lay.addWidget(QLabel("Volume (if known):", self)) vol_unit_lay.addWidget(PrevLeak.VOL.value) vol_unit_lay.addWidget(QLabel("Unit:", self)) vol_unit_lay.addWidget(PrevLeak.UNIT.value) vol_unit_lay.addWidget(HorizontalFiller(self)) form_box.frame_layout.addRow(vol_unit) claim_box = FormGroupBox("Claim Information:", self) claim_box.frame_layout.addRow(PrevLeak.CLAIM_FILED.value) claim_box.frame_layout.addRow("Total Claim Amount ($):", PrevLeak.CLAIM_AMT.value) claim_box.frame_layout.addRow("Deductible ($):", PrevLeak.DEDUCT.value) form_box.frame_layout.addRow(claim_box) self._scroll_layout.addRow(form_box) def connectWidgets(self): super().connectWidgets() prev_leak_widgets = [ PrevLeak.LOC, PrevLeak.COST_TO_REPAIR, PrevLeak.VOL, PrevLeak.UNIT, PrevLeak.CLAIM_FILED, PrevLeak.CLAIM_AMT, PrevLeak.DEDUCT ] PrevLeak.INFLUENCE.value.stateChanged.connect( self.enableDisableCheck(PrevLeak.INFLUENCE, prev_leak_widgets)) PrevLeak.CLAIM_FILED.value.stateChanged.connect( self.enableDisableCheck(PrevLeak.CLAIM_FILED, [PrevLeak.CLAIM_AMT, PrevLeak.DEDUCT])) for widget in prev_leak_widgets: widget.value.setDisabled(True) def enableDisableCheck(self, check_widget, widgets=[], reverse_check=False): def enableDisable(): if len(widgets) > 0: for widget in widgets: if reverse_check: widget.value.setDisabled( check_widget.value.isChecked()) else: widget.value.setEnabled(check_widget.value.isChecked()) if not PrevLeak.CLAIM_FILED.value.isChecked(): PrevLeak.CLAIM_AMT.value.setDisabled(True) PrevLeak.DEDUCT.value.setDisabled(True) if check_widget == PrevLeak.INFLUENCE: self.claimFileCheck() return enableDisable def claimFileCheck(self): if not PrevLeak.CLAIM_FILED.value.isEnabled(): PrevLeak.CLAIM_FILED.value.setChecked(False)
class Widget(QWidget): def __init__(self): QWidget.__init__(self) self.setWindowTitle("Backend Discord-GUI") self.changeStyle('fusion') palette = QPalette() palette.setColor(QPalette.Window, QColor(53, 53, 53)) palette.setColor(QPalette.WindowText, Qt.white) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(60, 60, 60)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.Base, QColor(40, 40, 40)) palette.setColor(QPalette.ToolTipBase, QColor(60, 60, 60)) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.PlaceholderText, Qt.white) palette.setColor(QPalette.BrightText, Qt.white) palette.setColor(QPalette.Highlight, QColor(106, 13, 173)) palette.setColor(QPalette.HighlightedText, Qt.white) topButtonLayout = QGroupBox("Configurations") topStatsLayout = QGroupBox("Statistics") layoutLeft = QHBoxLayout() botConfigButton = QPushButton("Bot Config") botConfigButton.clicked.connect(lambda: CommentPopup()) serverSettingsButton = QPushButton("Server Settings") settingsButton = QPushButton("Settings") layoutLeft.addWidget(botConfigButton) layoutLeft.addWidget(serverSettingsButton) layoutLeft.addWidget(settingsButton) layoutRight = QVBoxLayout() botReadyLabel = QLabel("Bot_Ready: False") botStatusLabel = QLabel("Bot_Status: Off") # botDatabaseLabel = QLabel("Bot_Database: None") # botStandbyLabel = QLabel("Bot_Standby: False") layoutRight.addWidget(botReadyLabel) layoutRight.addWidget(botStatusLabel) # layoutRight.addWidget(botDatabaseLabel) # layoutRight.addWidget(botStandbyLabel) topButtonLayout.setLayout(layoutLeft) topStatsLayout.setLayout(layoutRight) self.createLeftSide() self.createRightSide() self.createProgressBar() topLayout = QGridLayout() topLayout.addWidget(topButtonLayout, 0, 0) topLayout.addWidget(topStatsLayout, 0, 1) topLayout.setColumnStretch(0, 1) mainLayout = QGridLayout() mainLayout.addLayout(topLayout, 0, 0, 1, 2) mainLayout.addWidget(self.leftSideGB, 1, 0) mainLayout.addWidget(self.topRightGroupBox, 1, 1) mainLayout.addWidget(self.progressBar, 3, 0, 1, 2) mainLayout.setRowStretch(1, 2) mainLayout.setColumnStretch(0, 1) mainLayout.setColumnStretch(1, 2) self.setLayout(mainLayout) QApplication.setPalette(palette) def changeStyle(self, styleName): QApplication.setStyle(QStyleFactory.create(styleName)) def advanceProgressBarLoading(self): curVal = self.progressBar.value() maxVal = self.progressBar.maximum() if curVal != maxVal: num = random.randint(1, 30) self.progressBar.setValue(curVal + num) else: self.timer.stop() change_status('Ready') self.progressBar.setValue(0) def createLeftSide(self): self.leftSideGB = QGroupBox() home_directory = "./app/" palette = QPalette() palette.setColor(QPalette.Window, QColor(30, 30, 30)) model = QDirModel() view = QTreeView() view.setStyleSheet("QTreeView { border: 0px; }") view.setModel(model) view.setRootIndex(model.index(home_directory)) view.setColumnHidden(1, True) view.setColumnHidden(2, True) view.setColumnHidden(3, True) view.show() view.setPalette(palette) runButton = QPushButton("►") stopButton = QPushButton("❚❚") bottomBar = QHBoxLayout() bottomBar.addWidget(runButton) bottomBar.addWidget(stopButton) layout = QVBoxLayout() layout.addWidget(view) layout.addLayout(bottomBar) layout.setStretch(0, 2) self.leftSideGB.setLayout(layout) def createRightSide(self): self.topRightGroupBox = QGroupBox() self.totalLength = 0 self.elems = 0 self.elems_list = [] self.overall_layout = QVBoxLayout() grad = QPalette() gradient = QConicalGradient(QPointF(1100, 150), -190) gradient.setColorAt(0.0, QColor(30, 30, 30)) gradient.setColorAt(0.5, QColor(50, 50, 50)) gradient.setColorAt(0.97, QColor(50, 13, 150)) gradient.setColorAt(1.0, QColor(106, 13, 173)) gradient.setSpread(QGradient.RepeatSpread) grad.setBrush(QPalette.Window, QBrush(gradient)) self.setPalette(grad) self.scrollarea = QScrollArea() self.scrollarea.setWidgetResizable(True) self.widget = QWidget() self.scrollarea.setWidget(self.widget) self.layout = QVBoxLayout(self.widget) self.add_elem = QPushButton("Add Element") if PLATFORM == "darwin": self.add_elem.setToolTip("Shortcut: ⌘E") else: self.add_elem.setToolTip("Shortcut: Ctrl+E") self.add_elem.setStyleSheet( "QToolTip { border: 0px; border-radius: 3px }") self.add_elem.clicked.connect(lambda: ElementPopup()) self.add_elem.setFixedWidth(300) shortcut = QShortcut(QKeySequence("Ctrl+E"), self.add_elem) shortcut.activated.connect(lambda: ElementPopup()) shortcut.setEnabled(True) self.layout.addWidget(self.add_elem) self.layout.setAlignment(self.add_elem, Qt.AlignCenter | Qt.AlignTop) self.overall_layout.addWidget(self.scrollarea) self.topRightGroupBox.setLayout(self.overall_layout) def add_element(self, title, type, isDupe=False, indexForDupe=0, data=""): # open form of widget lists if data != "": title = title + ": " + data elem = create_elem(title, type, data) self.elems_list.append(elem.getElem()) self.elems += 1 self.totalLength += 100 if isDupe: self.layout.insertWidget(indexForDupe + 1, self.elems_list[self.elems - 1]) else: self.layout.insertWidget(self.elems - 1, self.elems_list[self.elems - 1]) if self.totalLength > self.topRightGroupBox.height(): self.scrollarea.verticalScrollBar().setMaximum( self.scrollarea.verticalScrollBar().maximum() + 85) self.scrollarea.verticalScrollBar().setValue( self.scrollarea.verticalScrollBar().maximum()) self.topRightGroupBox.update() def createProgressBar(self): self.progressBar = QProgressBar() self.progressBar.setRange(0, 10000) self.progressBar.setValue(0) self.progressBar.setTextVisible(False) self.progressBar.setFixedHeight(5) # self.timer = QTimer(self) # self.timer.timeout.connect(self.advanceProgressBarLoading) # self.timer.start(10) @Slot() def quit_application(self): QApplication.quit()
class Ui_FE14MapEditor(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.toolbar = QToolBar() self.toggle_coordinate_type_action = QAction("Toggle Coordinate Type") self.refresh_action = QAction("Refresh") self.refresh_action.setShortcut(QKeySequence("Ctrl+R")) self.copy_spawn_action = QAction("Copy Spawn") self.copy_spawn_action.setShortcut(QKeySequence("Ctrl+C")) self.paste_spawn_action = QAction("Paste Spawn") self.paste_spawn_action.setShortcut(QKeySequence("Ctrl+V")) self.add_spawn_action = QAction("Add Spawn") self.delete_spawn_action = QAction("Delete Spawn") self.add_group_action = QAction("Add Group") self.delete_group_action = QAction("Delete Group") self.add_tile_action = QAction("Add Tile") self.toggle_mode_action = QAction("Toggle Mode") self.undo_action = QAction("Undo") self.undo_action.setShortcut(QKeySequence("Ctrl+Z")) self.redo_action = QAction("Redo") self.redo_action.setShortcut(QKeySequence("Ctrl+Shift+Z")) self.toolbar.addActions( [self.toggle_coordinate_type_action, self.refresh_action]) self.toolbar.addSeparator() self.toolbar.addActions([ self.copy_spawn_action, self.paste_spawn_action, self.add_spawn_action, self.delete_spawn_action, self.add_group_action, self.delete_group_action ]) self.toolbar.addSeparator() self.toolbar.addAction(self.add_tile_action) self.toolbar.addSeparator() self.toolbar.addAction(self.toggle_mode_action) self.toolbar.addSeparator() self.toolbar.addActions([self.undo_action, self.redo_action]) self.addToolBar(self.toolbar) self.model_view = QTreeView() self.model_view.setHeaderHidden(True) self.grid = FE14MapGrid() self.grid_scroll = QScrollArea() self.grid_scroll.setWidgetResizable(True) self.grid_scroll.setWidget(self.grid) self.tile_list = QListView() self.terrain_pane = FE14TerrainEditorPane() self.spawn_pane = FE14SpawnEditorPane() self.model_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.model_view_context_menu = QMenu() self.model_view_context_menu.addActions( [self.toggle_coordinate_type_action, self.refresh_action]) self.model_view_context_menu.addSeparator() self.model_view_context_menu.addActions([ self.copy_spawn_action, self.paste_spawn_action, self.add_spawn_action, self.delete_spawn_action, self.add_group_action, self.delete_group_action ]) self.model_view_context_menu.addSeparator() self.model_view_context_menu.addAction(self.add_tile_action) self.model_view_context_menu.addSeparator() self.model_view_context_menu.addAction(self.toggle_mode_action) self.model_view_context_menu.addSeparator() self.model_view_context_menu.addActions( [self.undo_action, self.redo_action]) self.status_bar = QStatusBar() self.coordinate_type_label = QLabel() self.status_bar.addPermanentWidget(self.coordinate_type_label) self.setStatusBar(self.status_bar) self.main_widget = QSplitter() self.main_widget.setOrientation(QtCore.Qt.Horizontal) self.main_widget.addWidget(self.model_view) self.main_widget.addWidget(self.grid_scroll) self.main_widget.addWidget(self.spawn_pane) self.main_widget.addWidget(self.terrain_pane) self.setCentralWidget(self.main_widget)
class TagsCheckboxWindow(QWidget): def __init__(self, path, owner): QWidget.__init__(self) self.path = path self.scroll_area = QScrollArea() self.num_columns = 3 self.owner = owner # self.checkboxes_widget = QWidget() for paper in Index.gPapers: if paper['path'] == self.path: self.paper = paper self.columns = [] for i in range(self.num_columns): layout = QVBoxLayout() layout.setSpacing(0) layout.setMargin(0) self.columns.append(layout) self.checkboxes = [] self.tags_copy = Index.gTags.copy() self.tags_copy.sort(key=lambda s: s) count = 0 for tag in self.tags_copy: checkbox = QCheckBox(tag) self.checkboxes.append(checkbox) self.columns[int( (self.num_columns * count) / len(self.tags_copy))].addWidget( checkbox) #add the checkbox to the appropriate column if 'tags' in self.paper: if tag in self.paper['tags']: checkbox.setChecked(True) checkbox.clicked.connect(self.checkbox_click_creator(checkbox)) count += 1 # self.checkboxes_widget.setLayout(self.layout) # self.scroll_area.setWidget(self.checkboxes_widget) self.layout = QHBoxLayout() for col in self.columns: self.layout.addLayout(col) self.scroll_area.setLayout(self.layout) self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll_area.setWidgetResizable(True) self.full_layout = QHBoxLayout() self.full_layout.addWidget(self.scroll_area) self.setLayout(self.full_layout) def checkbox_click_creator(self, box): @Slot() def checkbox_click(): if box.isChecked() == True: # print('checkbox for', self.path, 'is true') if 'tags' not in self.paper: self.paper['tags'] = [] if box.text() not in self.paper['tags']: self.paper['tags'].append(box.text()) Index.save_json(Index.gJSONfilename) # self.owner.PapersView = Index.gPapers.copy() # self.owner.update() self.owner.copy_sort_update() # for paper in Index.gPapers: # if paper['path'] == self.path: # if 'tags' not in paper: # paper['tags'] = [] # if box.text() not in paper['tags']: # paper['tags'].append(box.text()) # Index.save_json(Index.gJSONfilename) # break else: print('checkbox', box.text(), 'for', self.path, 'is false') if 'tags' not in self.paper: self.paper['tags'] = [] if box.text() in self.paper['tags']: self.paper['tags'].remove(box.text()) Index.save_json(Index.gJSONfilename) # self.owner.PapersView = Index.gPapers.copy() # self.owner.update() self.owner.copy_sort_update() # for paper in Index.gPapers: # if paper['path'] == self.path: # if 'tags' not in paper: # paper['tags'] = [] # if box.text() in paper['tags']: # paper['tags'].remove(box.text()) # Index.save_json(Index.gJSONfilename) # break return checkbox_click
def _askForFieldsDialog(self, options, fields_type="inputs"): #Display a dialog to ask the user to choose what inputs/outputs they want dialog = QDialog(self) dialog.setWindowTitle(f"Select the model {fields_type.upper()}") dialogButtons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0) dialogButtons.accepted.connect(dialog.accept) dialogButtons.rejected.connect(dialog.reject) mainLayout = QVBoxLayout(dialog) scroll = QScrollArea(dialog) scroll.setWidgetResizable(True) layoutWidget = QWidget() layout = QVBoxLayout(layoutWidget) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(layoutWidget) chosenFields = [] checkboxes = [] def handleCheckboxClicked(): dialogButtons.button(QDialogButtonBox.Ok).setDisabled(1) count = 0 for checkbox in checkboxes: if checkbox.isChecked(): count += 1 if fields_type.lower() == "output": setDisabled = True if count > 1 else False else: setDisabled = True if count == 0 else False dialogButtons.button(QDialogButtonBox.Ok).setDisabled(setDisabled) for input in options: checkbox = QCheckBox(text=input) checkbox.clicked.connect(handleCheckboxClicked) checkbox.setChecked(True) checkboxes.append(checkbox) layout.addWidget(checkbox) mainLayout.addWidget( QLabel( text= f"Please select the {fields_type.lower()} from the following:") ) mainLayout.addWidget(scroll) mainLayout.addWidget(dialogButtons) dialog.setLayout(mainLayout) handleCheckboxClicked() if dialog.exec_() == QDialog.Accepted: for checkbox in checkboxes: if checkbox.isChecked(): chosenFields.append(checkbox.text()) self.logger.log(f"The chosen {fields_type.lower()} are: " + ', '.join(chosenFields), type="INFO") return chosenFields else: return []
class TabDisplays(QTabWidget): def __init__(self, parent=None): super(TabDisplays, self).__init__(parent) # Initialize logging logging_conf_file = os.path.join(os.path.dirname(__file__), 'cfg/aecgviewer_aecg_logging.conf') logging.config.fileConfig(logging_conf_file) self.logger = logging.getLogger(__name__) self.studyindex_info = aecg.tools.indexer.StudyInfo() self.validator = QWidget() self.studyinfo = QWidget() self.statistics = QWidget() self.waveforms = QWidget() self.waveforms.setAccessibleName("Waveforms") self.scatterplot = QWidget() self.histogram = QWidget() self.trends = QWidget() self.xmlviewer = QWidget() self.xml_display = QTextEdit(self.xmlviewer) self.options = QWidget() self.aecg_display_area = QScrollArea() self.cbECGLayout = QComboBox() self.aecg_display = EcgDisplayWidget(self.aecg_display_area) self.aecg_display_area.setWidget(self.aecg_display) self.addTab(self.validator, "Study information") self.addTab(self.waveforms, "Waveforms") self.addTab(self.xmlviewer, "XML") self.addTab(self.options, "Options") self.setup_validator() self.setup_waveforms() self.setup_xmlviewer() self.setup_options() size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) size.setHeightForWidth(False) self.setSizePolicy(size) # Initialized a threpool with 2 threads 1 for the GUI, 1 for long # tasks, so GUI remains responsive self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(2) self.validator_worker = None self.indexing_timer = QElapsedTimer() def setup_validator(self): self.directory_indexer = None # aecg.indexing.DirectoryIndexer() self.validator_layout_container = QWidget() self.validator_layout = QFormLayout() self.validator_form_layout = QFormLayout( self.validator_layout_container) self.validator_grid_layout = QGridLayout() self.study_info_file = QLineEdit() self.study_info_file.setToolTip("Study index file") self.study_info_description = QLineEdit() self.study_info_description.setToolTip("Description") self.app_type = QLineEdit() self.app_type.setToolTip("Application type (e.g., NDA, IND, BLA, IDE)") self.app_num = QLineEdit() self.app_num.setToolTip("Six-digit application number") self.app_num.setValidator(QIntValidator(self.app_num)) self.study_id = QLineEdit() self.study_id.setToolTip("Study identifier") self.study_sponsor = QLineEdit() self.study_sponsor.setToolTip("Sponsor of the study") self.study_annotation_aecg_cb = QComboBox() self.study_annotation_aecg_cb.addItems( ["Rhythm", "Derived beat", "Holter-rhythm", "Holter-derived"]) self.study_annotation_aecg_cb.setToolTip( "Waveforms used to perform the ECG measurements (i.e., " "annotations)\n" "\tRhythm: annotations in a rhythm strip or discrete ECG " "extraction (e.g., 10-s strips)\n" "\tDerived beat: annotations in a representative beat derived " "from a rhythm strip\n" "\tHolter-rhythm: annotations in a the analysis window of a " "continuous recording\n" "\tHolter-derived: annotations in a representative beat derived " "from analysis window of a continuous recording\n") self.study_annotation_lead_cb = QComboBox() self.ui_leads = ["GLOBAL"] + aecg.STD_LEADS[0:12] +\ [aecg.KNOWN_NON_STD_LEADS[1]] + aecg.STD_LEADS[12:15] + ["Other"] self.study_annotation_lead_cb.addItems(self.ui_leads) self.study_annotation_lead_cb.setToolTip( "Primary analysis lead annotated per protocol. There could be " "annotations in other leads also, but only the primary lead should" " be selected here.\n" "Select global if all leads were used at the " "same time (e.g., superimposed on screen).\n" "Select other if the primary lead used is not in the list.") self.study_numsubjects = QLineEdit() self.study_numsubjects.setToolTip( "Number of subjects with ECGs in the study") self.study_numsubjects.setValidator( QIntValidator(self.study_numsubjects)) self.study_aecgpersubject = QLineEdit() self.study_aecgpersubject.setToolTip( "Number of scheduled ECGs (or analysis windows) per subject as " "specified in the study protocol.\n" "Enter average number of ECGs " "per subject if the protocol does not specify a fixed number of " "ECGs per subject.") self.study_aecgpersubject.setValidator( QIntValidator(self.study_aecgpersubject)) self.study_numaecg = QLineEdit() self.study_numaecg.setToolTip( "Total number of aECG XML files in the study") self.study_numaecg.setValidator(QIntValidator(self.study_numaecg)) self.study_annotation_numbeats = QLineEdit() self.study_annotation_numbeats.setToolTip( "Minimum number of beats annotated in each ECG or analysis window" ".\nEnter 1 if annotations were done in the derived beat.") self.study_annotation_numbeats.setValidator( QIntValidator(self.study_annotation_numbeats)) self.aecg_numsubjects = QLineEdit() self.aecg_numsubjects.setToolTip( "Number of subjects found across the provided aECG XML files") self.aecg_numsubjects.setReadOnly(True) self.aecg_aecgpersubject = QLineEdit() self.aecg_aecgpersubject.setToolTip( "Average number of ECGs per subject found across the provided " "aECG XML files") self.aecg_aecgpersubject.setReadOnly(True) self.aecg_numaecg = QLineEdit() self.aecg_numaecg.setToolTip( "Number of aECG XML files found in the study aECG directory") self.aecg_numaecg.setReadOnly(True) self.subjects_less_aecgs = QLineEdit() self.subjects_less_aecgs.setToolTip( "Percentage of subjects with less aECGs than specified per " "protocol") self.subjects_less_aecgs.setReadOnly(True) self.subjects_more_aecgs = QLineEdit() self.subjects_more_aecgs.setToolTip( "Percentage of subjects with more aECGs than specified per " "protocol") self.subjects_more_aecgs.setReadOnly(True) self.aecgs_no_annotations = QLineEdit() self.aecgs_no_annotations.setToolTip( "Percentage of aECGs with no annotations") self.aecgs_no_annotations.setReadOnly(True) self.aecgs_less_qt_in_primary_lead = QLineEdit() self.aecgs_less_qt_in_primary_lead.setToolTip( "Percentage of aECGs with less QT intervals in the primary lead " "than specified per protocol") self.aecgs_less_qt_in_primary_lead.setReadOnly(True) self.aecgs_less_qts = QLineEdit() self.aecgs_less_qts.setToolTip( "Percentage of aECGs with less QT intervals than specified per " "protocol") self.aecgs_less_qts.setReadOnly(True) self.aecgs_annotations_multiple_leads = QLineEdit() self.aecgs_annotations_multiple_leads.setToolTip( "Percentage of aECGs with QT annotations in multiple leads") self.aecgs_annotations_multiple_leads.setReadOnly(True) self.aecgs_annotations_no_primary_lead = QLineEdit() self.aecgs_annotations_no_primary_lead.setToolTip( "Percentage of aECGs with QT annotations not in the primary lead") self.aecgs_annotations_no_primary_lead.setReadOnly(True) self.aecgs_with_errors = QLineEdit() self.aecgs_with_errors.setToolTip("Number of aECG files with errors") self.aecgs_with_errors.setReadOnly(True) self.aecgs_potentially_digitized = QLineEdit() self.aecgs_potentially_digitized.setToolTip( "Number of aECG files potentially digitized (i.e., with more than " "5% of samples missing)") self.aecgs_potentially_digitized.setReadOnly(True) self.study_dir = QLineEdit() self.study_dir.setToolTip("Directory containing the aECG files") self.study_dir_button = QPushButton("...") self.study_dir_button.clicked.connect(self.select_study_dir) self.study_dir_button.setToolTip("Open select directory dialog") self.validator_form_layout.addRow("Application Type", self.app_type) self.validator_form_layout.addRow("Application Number", self.app_num) self.validator_form_layout.addRow("Study name/ID", self.study_id) self.validator_form_layout.addRow("Sponsor", self.study_sponsor) self.validator_form_layout.addRow("Study description", self.study_info_description) self.validator_form_layout.addRow("Annotations in", self.study_annotation_aecg_cb) self.validator_form_layout.addRow("Annotations primary lead", self.study_annotation_lead_cb) self.validator_grid_layout.addWidget(QLabel(""), 0, 0) self.validator_grid_layout.addWidget( QLabel("Per study protocol or report"), 0, 1) self.validator_grid_layout.addWidget(QLabel("Found in aECG files"), 0, 2) self.validator_grid_layout.addWidget(QLabel("Number of subjects"), 1, 0) self.validator_grid_layout.addWidget(self.study_numsubjects, 1, 1) self.validator_grid_layout.addWidget(self.aecg_numsubjects, 1, 2) self.validator_grid_layout.addWidget( QLabel("Number of aECG per subject"), 2, 0) self.validator_grid_layout.addWidget(self.study_aecgpersubject, 2, 1) self.validator_grid_layout.addWidget(self.aecg_aecgpersubject, 2, 2) self.validator_grid_layout.addWidget(QLabel("Total number of aECG"), 3, 0) self.validator_grid_layout.addWidget(self.study_numaecg, 3, 1) self.validator_grid_layout.addWidget(self.aecg_numaecg, 3, 2) self.validator_grid_layout.addWidget( QLabel("Number of beats per aECG"), 4, 0) self.validator_grid_layout.addWidget(self.study_annotation_numbeats, 4, 1) self.validator_grid_layout.addWidget( QLabel("Subjects with fewer ECGs"), 5, 1) self.validator_grid_layout.addWidget(self.subjects_less_aecgs, 5, 2) self.validator_grid_layout.addWidget(QLabel("Subjects with more ECGs"), 6, 1) self.validator_grid_layout.addWidget(self.subjects_more_aecgs, 6, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without annotations"), 7, 1) self.validator_grid_layout.addWidget(self.aecgs_no_annotations, 7, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without expected number of QTs in primary lead"), 8, 1) self.validator_grid_layout.addWidget( self.aecgs_less_qt_in_primary_lead, 8, 2) self.validator_grid_layout.addWidget( QLabel("aECGs without expected number of QTs"), 9, 1) self.validator_grid_layout.addWidget(self.aecgs_less_qts, 9, 2) self.validator_grid_layout.addWidget( QLabel("aECGs annotated in multiple leads"), 10, 1) self.validator_grid_layout.addWidget( self.aecgs_annotations_multiple_leads, 10, 2) self.validator_grid_layout.addWidget( QLabel("aECGs with annotations not in primary lead"), 11, 1) self.validator_grid_layout.addWidget( self.aecgs_annotations_no_primary_lead, 11, 2) self.validator_grid_layout.addWidget(QLabel("aECGs with errors"), 12, 1) self.validator_grid_layout.addWidget(self.aecgs_with_errors, 12, 2) self.validator_grid_layout.addWidget( QLabel("Potentially digitized aECGs"), 13, 1) self.validator_grid_layout.addWidget(self.aecgs_potentially_digitized, 13, 2) self.validator_form_layout.addRow(self.validator_grid_layout) tmp = QHBoxLayout() tmp.addWidget(self.study_dir) tmp.addWidget(self.study_dir_button) self.validator_form_layout.addRow("Study aECGs directory", tmp) self.validator_form_layout.addRow("Study index file", self.study_info_file) self.validator_layout.addWidget(self.validator_layout_container) self.validator_effective_dirs = QLabel("") self.validator_effective_dirs.setWordWrap(True) self.validator_layout.addWidget(self.validator_effective_dirs) self.val_button = QPushButton("Generate/update study index") self.val_button.clicked.connect(self.importstudy_dialog) self.validator_layout.addWidget(self.val_button) self.cancel_val_button = QPushButton("Cancel study index generation") self.cancel_val_button.clicked.connect(self.cancel_validator) self.cancel_val_button.setEnabled(False) self.validator_layout.addWidget(self.cancel_val_button) self.validator_pl = QLabel("") self.validator_layout.addWidget(self.validator_pl) self.validator_pb = QProgressBar() self.validator_layout.addWidget(self.validator_pb) self.validator.setLayout(self.validator_layout) self.stop_indexing = False self.lastindexing_starttime = None self.update_validator_effective_dirs() def effective_aecgs_dir(self, navwidget, silent=False): aecgs_effective_dir = self.study_dir.text() if navwidget.project_loaded != '': # Path specified in the GUI potential_aecgs_dirs = [self.study_dir.text()] # StudyDir path from current working directory potential_aecgs_dirs += [self.studyindex_info.StudyDir] # StudyDir path from directory where the index is located potential_aecgs_dirs += [ os.path.join(os.path.dirname(navwidget.project_loaded), self.studyindex_info.StudyDir) ] # StudyDir replaced with the directory where the index is located potential_aecgs_dirs += [os.path.dirname(navwidget.project_loaded)] dir_found = False # Get xml and zip filenames from first element in the index aecg_xml_file = navwidget.data_index["AECGXML"][0] zipfile = "" if aecg_xml_file != "": zipfile = navwidget.data_index["ZIPFILE"][0] for p in potential_aecgs_dirs: testfn = os.path.join(p, aecg_xml_file) if zipfile != "": testfn = os.path.join(p, zipfile) if os.path.isfile(testfn): dir_found = True aecgs_effective_dir = p break if not silent: if not dir_found: QMessageBox.warning( self, f"Study aECGs directory not found", f"The following paths were checked:" f"{[','.join(p) for p in potential_aecgs_dirs]} and " f"none of them is valid") elif p != self.study_dir.text(): QMessageBox.warning( self, f"Study aECGs directory not found", f"The path specified in the study aECGs directory is " f"not valid and {p} is being used instead. Check and " f"update the path in Study aECGs directory textbox if " f"the suggested path is not the adequate path") return aecgs_effective_dir def update_validator_effective_dirs(self): msg = f"Working directory: {os.getcwd()}" if self.parent() is not None: if isinstance(self.parent(), QSplitter): navwidget = self.parent().parent() else: # Tabs widget has not been allocated the QSplitter yet navwidget = self.parent() project_loaded = navwidget.project_loaded if project_loaded != '': msg = f"{msg}\nLoaded project index: "\ f"{navwidget.project_loaded}" effective_aecgs_path = self.effective_aecgs_dir(navwidget) msg = f"{msg}\nEffective study aECGs directory: "\ f"{effective_aecgs_path}" else: msg = f"{msg}\nLoaded project index: None" else: msg = f"{msg}\nLoaded project index: None" self.validator_effective_dirs.setText(msg) def load_study_info(self, fileName): self.study_info_file.setText(fileName) try: study_info = pd.read_excel(fileName, sheet_name="Info") self.studyindex_info = aecg.tools.indexer.StudyInfo() self.studyindex_info.__dict__.update( study_info.set_index("Property").transpose().reset_index( drop=True).to_dict('index')[0]) sponsor = "" description = "" if self.studyindex_info.Sponsor is not None and\ isinstance(self.studyindex_info.Sponsor, str): sponsor = self.studyindex_info.Sponsor if self.studyindex_info.Description is not None and\ isinstance(self.studyindex_info.Description, str): description = self.studyindex_info.Description self.study_sponsor.setText(sponsor) self.study_info_description.setText(description) self.app_type.setText(self.studyindex_info.AppType) self.app_num.setText(f"{int(self.studyindex_info.AppNum):06d}") self.study_id.setText(self.studyindex_info.StudyID) self.study_numsubjects.setText(str(self.studyindex_info.NumSubj)) self.study_aecgpersubject.setText( str(self.studyindex_info.NECGSubj)) self.study_numaecg.setText(str(self.studyindex_info.TotalECGs)) anns_in = self.studyindex_info.AnMethod.upper() idx = 0 if anns_in == "RHYTHM": idx = 0 elif anns_in == "DERIVED": idx = 1 elif anns_in == "HOLTER_RHYTHM": idx = 2 elif anns_in == "HOLTER_MEDIAN_BEAT": idx = 3 else: idx = int(anns_in) - 1 self.study_annotation_aecg_cb.setCurrentIndex(idx) the_lead = self.studyindex_info.AnLead idx = self.study_annotation_lead_cb.findText(str(the_lead)) if idx == -1: idx = self.study_annotation_lead_cb.findText("MDC_ECG_LEAD_" + str(the_lead)) if idx == -1: idx = int(the_lead) self.study_annotation_lead_cb.setCurrentIndex(idx) self.study_annotation_numbeats.setText( str(self.studyindex_info.AnNbeats)) if self.studyindex_info.StudyDir == "": self.studyindex_info.StudyDir = os.path.dirname(fileName) self.study_dir.setText(self.studyindex_info.StudyDir) self.update_validator_effective_dirs() self.setCurrentWidget(self.validator) except Exception as ex: QMessageBox.critical( self, "Import study error", "Error reading the study information file: '" + fileName + "'") def setup_waveforms(self): wflayout = QVBoxLayout() # ECG plot layout selection box self.cbECGLayout.addItems( ['12-lead stacked', '3x4 + lead II rhythm', 'Superimposed']) self.cbECGLayout.currentIndexChanged.connect( self.ecgplotlayout_changed) # Zoom buttons blayout = QHBoxLayout() pb_zoomin = QPushButton() pb_zoomin.setText("Zoom +") pb_zoomin.clicked.connect(self.zoom_in) pb_zoomreset = QPushButton() pb_zoomreset.setText("Zoom 1:1") pb_zoomreset.clicked.connect(self.zoom_reset) pb_zoomout = QPushButton() pb_zoomout.setText("Zoom -") pb_zoomout.clicked.connect(self.zoom_out) blayout.addWidget(self.cbECGLayout) blayout.addWidget(pb_zoomout) blayout.addWidget(pb_zoomreset) blayout.addWidget(pb_zoomin) wflayout.addLayout(blayout) # Add QScrollArea to main layout of waveforms tab self.aecg_display_area.setWidgetResizable(False) wflayout.addWidget(self.aecg_display_area) self.waveforms.setLayout(wflayout) size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size.setHeightForWidth(False) self.aecg_display_area.setSizePolicy(size) self.waveforms.setSizePolicy(size) def setup_xmlviewer(self): wf_layout = QHBoxLayout() wf_layout.addWidget(self.xml_display) self.xmlviewer.setLayout(wf_layout) def setup_options(self): self.options_layout = QFormLayout() self.aecg_schema_filename = QLineEdit(aecg.get_aecg_schema_location()) self.options_layout.addRow("aECG XML schema path", self.aecg_schema_filename) self.save_index_every_n_aecgs = QSpinBox() self.save_index_every_n_aecgs.setMinimum(0) self.save_index_every_n_aecgs.setMaximum(50000) self.save_index_every_n_aecgs.setValue(0) self.save_index_every_n_aecgs.setSingleStep(100) self.save_index_every_n_aecgs.setSuffix(" aECGs") self.save_index_every_n_aecgs.setToolTip( "Set o 0 to save the study index file only after its generation " "is completed.\nOtherwise, the file is saved everytime the " " specified number of ECGs have been appended to the index.") self.options_layout.addRow("Save index every ", self.save_index_every_n_aecgs) self.save_all_intervals_cb = QCheckBox("") self.save_all_intervals_cb.setChecked(False) self.options_layout.addRow("Save individual beat intervals", self.save_all_intervals_cb) self.parallel_processing_cb = QCheckBox("") self.parallel_processing_cb.setChecked(True) self.options_layout.addRow("Parallel processing of files", self.parallel_processing_cb) self.options.setLayout(self.options_layout) def zoom_in(self): self.aecg_display.apply_zoom(self.aecg_display.zoom_factor + 0.1) def zoom_out(self): self.aecg_display.apply_zoom(self.aecg_display.zoom_factor - 0.1) def zoom_reset(self): self.aecg_display.apply_zoom(1.0) def ecgplotlayout_changed(self, i): self.aecg_display.update_aecg_plot( ecg_layout=aecg.utils.ECG_plot_layout(i + 1)) def update_search_progress(self, i, n): self.validator_pl.setText( f"Searching aECGs in directory ({n} XML files found)") def update_progress(self, i, n): j = i m = n if i <= 1: j = 1 if self.validator_pb.value() > 0: j = self.validator_pb.value() + 1 m = self.validator_pb.maximum() running_time = self.indexing_timer.elapsed() * 1e-3 # in seconds time_per_item = running_time / j # reamining = seconds per item so far * total pending items to process remaining_time = time_per_item * (m - j) eta = datetime.datetime.now() +\ datetime.timedelta(seconds=round(remaining_time, 0)) self.validator_pl.setText( f"Validating aECG {j}/{m} | " f"Execution time: " f"{str(datetime.timedelta(0,seconds=round(running_time)))} | " f"{round(1/time_per_item,2)} aECGs per second | " f"ETA: {eta.isoformat(timespec='seconds')}") self.validator_pb.setValue(j) if self.save_index_every_n_aecgs.value() > 0 and\ len(self.directory_indexer.studyindex) % \ self.save_index_every_n_aecgs.value() == 0: self.save_validator_results( pd.concat(self.directory_indexer.studyindex, ignore_index=True)) def save_validator_results(self, res): if res.shape[0] > 0: self.studyindex_info = aecg.tools.indexer.StudyInfo() self.studyindex_info.StudyDir = self.study_dir.text() self.studyindex_info.IndexFile = self.study_info_file.text() self.studyindex_info.Sponsor = self.study_sponsor.text() self.studyindex_info.Description =\ self.study_info_description.text() self.studyindex_info.Date = self.lastindexing_starttime.isoformat() self.studyindex_info.End_date = datetime.datetime.now().isoformat() self.studyindex_info.Version = aecg.__version__ self.studyindex_info.AppType = self.app_type.text() self.studyindex_info.AppNum = f"{int(self.app_num.text()):06d}" self.studyindex_info.StudyID = self.study_id.text() self.studyindex_info.NumSubj = int(self.study_numsubjects.text()) self.studyindex_info.NECGSubj = int( self.study_aecgpersubject.text()) self.studyindex_info.TotalECGs = int(self.study_numaecg.text()) anmethod = aecg.tools.indexer.AnnotationMethod( self.study_annotation_aecg_cb.currentIndex()) self.studyindex_info.AnMethod = anmethod.name self.studyindex_info.AnLead =\ self.study_annotation_lead_cb.currentText() self.studyindex_info.AnNbeats = int( self.study_annotation_numbeats.text()) # Calculate stats study_stats = aecg.tools.indexer.StudyStats( self.studyindex_info, res) # Save to file aecg.tools.indexer.save_study_index(self.studyindex_info, res, study_stats) validator_data_ready = Signal() def save_validator_results_and_load_index(self, res): self.save_validator_results(res) self.validator_data_ready.emit() def indexer_validator_results(self, res): self.studyindex_df = pd.concat([self.studyindex_df, res], ignore_index=True) def subindex_thread_complete(self): return def index_directory_thread_complete(self): tmp = self.validator_pl.text().replace("ETA:", "Completed: ").replace( "Validating", "Validated") self.validator_pl.setText(tmp) self.val_button.setEnabled(True) self.cancel_val_button.setEnabled(False) self.validator_layout_container.setEnabled(True) def index_directory(self, progress_callback): self.lastindexing_starttime = datetime.datetime.now() self.indexing_timer.start() studyindex_df = [] n_cores = os.cpu_count() aecg_schema = None if self.aecg_schema_filename.text() != "": aecg_schema = self.aecg_schema_filename.text() if self.parallel_processing_cb.isChecked(): studyindex_df = self.directory_indexer.index_directory( self.save_all_intervals_cb.isChecked(), aecg_schema, n_cores, progress_callback) else: studyindex_df = self.directory_indexer.index_directory( self.save_all_intervals_cb.isChecked(), aecg_schema, 1, progress_callback) return studyindex_df def importstudy_dialog(self): dirName = os.path.normpath(self.study_dir.text()) if dirName != "": if os.path.exists(dirName): self.directory_indexer = aecg.indexing.DirectoryIndexer() self.directory_indexer.set_aecg_dir( dirName, self.update_search_progress) self.validator_pb.setMaximum(self.directory_indexer.num_files) self.validator_pb.reset() self.stop_indexing = False self.validator_layout_container.setEnabled(False) self.val_button.setEnabled(False) self.cancel_val_button.setEnabled(True) self.validator_worker = Worker(self.index_directory) self.validator_worker.signals.result.connect( self.save_validator_results_and_load_index) self.validator_worker.signals.finished.connect( self.index_directory_thread_complete) self.validator_worker.signals.progress.connect( self.update_progress) # Execute self.threadpool.start(self.validator_worker) else: QMessageBox.critical( self, "Directory not found", f"Specified study directory not found:\n{dirName}") else: QMessageBox.critical(self, "Import study error", "Study directory cannot be empty") def cancel_validator(self): self.cancel_val_button.setEnabled(False) self.stop_indexing = True self.directory_indexer.cancel_indexing = True self.threadpool.waitForDone(3000) self.val_button.setEnabled(True) def select_study_dir(self): cd = self.study_dir.text() if cd == "": cd = "." dir = QFileDialog.getExistingDirectory( self, "Open Directory", cd, QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) if dir != "": self.study_dir.setText(dir)
class AudioSplitter(QWidget): def __init__(self): super(AudioSplitter, self).__init__() self.inputFile = '' self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.buildUI() def buildUI(self): self.closeButton = QPushButton('close') self.closeButton.setShortcut('Ctrl+W') self.closeButton.clicked.connect(self.onClose) self.closeButton.setFixedSize(0, 0) self.mainWrapperLayout = QVBoxLayout() self.mainWrapperLayout.addWidget(self.closeButton) self.divider1 = QFrame() self.divider1.setFrameShape(QFrame.HLine) self.divider1.setFrameShadow(QFrame.Sunken) self.divider2 = QFrame() self.divider2.setFrameShape(QFrame.HLine) self.divider2.setFrameShadow(QFrame.Sunken) self.headerLayout = self.buildHeader() self.optionsLayout = self.buildOptionsLayout() self.statusTextScroll = QScrollArea() self.statusTextScroll.setWidgetResizable(True) self.statusWidget = QWidget() self.statusTextScroll.setWidget(self.statusWidget) self.statusText = QVBoxLayout(self.statusWidget) self.statusTextScroll.setContentsMargins(0, 0, 0, 50) self.statusWidget.setLayout(self.statusText) self.mainWrapperLayout.addLayout(self.headerLayout) self.mainWrapperLayout.addWidget(self.divider1) self.mainWrapperLayout.addLayout(self.optionsLayout) self.mainWrapperLayout.addWidget(self.divider2) self.mainWrapperLayout.addWidget(self.statusTextScroll) self.startButton = QPushButton('Go') self.startButton.clicked.connect(self.onStartClick) self.mainWrapperLayout.addWidget(self.startButton) self.setLayout(self.mainWrapperLayout) self.setMinimumWidth(450) def buildHeader(self): appTitle = QLabel(self) appTitle.setText('Audio Splitter') appTitle.setFont(boldFont) chooseFileButton = QPushButton("Choose File") chooseFileButton.clicked.connect(self.loadNewFile) chooseFileButton.setShortcut('Ctrl+O') chooseFileButton.setMaximumWidth(90) self.fileLabel = QLabel() titleBar = QHBoxLayout() titleBar.addWidget(appTitle) titleBar.addWidget(chooseFileButton) headerLayout = QVBoxLayout() headerLayout.addLayout(titleBar) headerLayout.addWidget(self.fileLabel) return headerLayout def buildOptionsLayout(self): stemsRow = QHBoxLayout() stemsLabel = QLabel('Split into ') stemBox = QComboBox() stemBox.addItem('2 tracks') stemBox.addItem('4 tracks') stemBox.addItem('5 tracks') stemBox.setMaximumWidth(100) self.stemOptions = StemOptions() stemBox.activated[str].connect(self.stemOptions.setStems) stemsRow.addWidget(stemsLabel) stemsRow.addWidget(stemBox) stemsRow.addStretch() optionsLayout = QVBoxLayout() optionsLayout.addLayout(stemsRow) optionsLayout.addLayout(self.stemOptions) return optionsLayout def getCsvFileName(self): if "PYCHARM_HOSTED" in os.environ: success = \ QFileDialog.getOpenFileName(None, 'Open Audio Track', '', 'Audio Files (*.mp3 *.wav *.flac *.ogg)', options=QFileDialog.DontUseNativeDialog)[0] else: success = QFileDialog.getOpenFileName(None, 'Open Audio Track', '', 'Audio Files (*.mp3 *.wav *.flac *.ogg)')[0] if success: return success def loadNewFile(self): fileName = self.getCsvFileName() if fileName: path = Path(fileName) self.inputFile = path self.fileLabel.setText(f'Input: {self.inputFile.name}') def onStartClick(self): runInstance = RunSpleeter() self.updateStatus('Initializing') runInstance.startRun(self.inputFile, self.stemOptions.curStems, self.stemOptions.curOptions, self.updateStatus, self.saveOutput) def updateStatus(self, status, isError=False): newLine = QLabel(('ERROR' if isError else 'STATUS') + f': {status}') newLine.setWordWrap(True) self.statusText.addWidget(newLine) self.statusWidget.setLayout(self.statusText) QCoreApplication.processEvents() QCoreApplication.processEvents() self.statusTextScroll.verticalScrollBar().setValue(self.statusTextScroll.verticalScrollBar().maximum() + 1) def saveOutput(self): dlg = QFileDialog() if "PYCHARM_HOSTED" in os.environ: saveLoc = dlg.getSaveFileName(None, 'Save File', '', 'Zip files (*.zip)', options=QFileDialog.DontUseNativeDialog) else: saveLoc = dlg.getSaveFileName(None, 'Save File', '', 'Zip files (*.zip)') if saveLoc and saveLoc[0]: return saveLoc[0] def onClose(self): sys.exit()
def _init_widgets(self): layout = QGridLayout() row = 0 # validation_failures = set() addr = hex(self._addr) address_label = QLabel(self) address_label.setText(f"Hook at address {addr}:") layout.addWidget(address_label, row, 0) row += 1 options_container = QGroupBox(self) options = QVBoxLayout() for name, template in sorted(self.templates.items()): child = QRadioButton() child.setText(name) child.template = template child.toggled.connect(self.selected) options.addWidget(child) scroll_area = QScrollArea(self) scroll_area.setWidgetResizable(True) widget = QWidget() scroll_area.setWidget(widget) layout_scroll = QVBoxLayout(widget) header_label = QLabel(self) header_label.setText("Presets:") layout_scroll.addWidget(header_label) layout_scroll.addWidget(options_container) options_container.setLayout(options) layout.addWidget(scroll_area, row, 0) row += 1 function_box = CodeEdit(self) function_box.use_spaces_instead_of_tabs = True function_box.tab_length = 4 function_box.modes.append(CaretLineHighlighterMode()) function_box.modes.append( PygmentsSyntaxHighlighter(function_box.document())) function_box.modes.append(AutoIndentMode()) function_box.setWordWrapMode(QTextOption.WordWrap) self._function_box = function_box layout.addWidget(function_box, row, 0) row += 1 self.main_layout.addLayout(layout) buttons = QDialogButtonBox(parent=self) buttons.setStandardButtons(QDialogButtonBox.StandardButton.Cancel | QDialogButtonBox.StandardButton.Ok) buttons.button(QDialogButtonBox.Ok).setText('Append to Console') def do_ok(): code = function_box.toPlainText() self.instance.append_code_to_console(code) self.close() buttons.accepted.connect(do_ok) buttons.rejected.connect(self.close) self.main_layout.addWidget(buttons)
class MainWindow(QMainWindow): def __init__(self, path_to_rom=""): super(MainWindow, self).__init__() self.setWindowIcon(icon("foundry.ico")) file_menu = QMenu("File") open_rom_action = file_menu.addAction("&Open ROM") open_rom_action.triggered.connect(self.on_open_rom) self.open_m3l_action = file_menu.addAction("&Open M3L") self.open_m3l_action.triggered.connect(self.on_open_m3l) file_menu.addSeparator() self.save_rom_action = file_menu.addAction("&Save ROM") self.save_rom_action.triggered.connect(self.on_save_rom) self.save_rom_as_action = file_menu.addAction("&Save ROM as ...") self.save_rom_as_action.triggered.connect(self.on_save_rom_as) """ file_menu.AppendSeparator() """ self.save_m3l_action = file_menu.addAction("&Save M3L") self.save_m3l_action.triggered.connect(self.on_save_m3l) """ file_menu.Append(ID_SAVE_LEVEL_TO, "&Save Level to", "") file_menu.AppendSeparator() file_menu.Append(ID_APPLY_IPS_PATCH, "&Apply IPS Patch", "") file_menu.AppendSeparator() file_menu.Append(ID_ROM_PRESET, "&ROM Preset", "") """ file_menu.addSeparator() settings_action = file_menu.addAction("&Settings") settings_action.triggered.connect(show_settings) file_menu.addSeparator() exit_action = file_menu.addAction("&Exit") exit_action.triggered.connect(lambda _: self.close()) self.menuBar().addMenu(file_menu) """ edit_menu = wx.Menu() edit_menu.Append(ID_EDIT_LEVEL, "&Edit Level", "") edit_menu.Append(ID_EDIT_OBJ_DEFS, "&Edit Object Definitions", "") edit_menu.Append(ID_EDIT_PALETTE, "&Edit Palette", "") edit_menu.Append(ID_EDIT_GRAPHICS, "&Edit Graphics", "") edit_menu.Append(ID_EDIT_MISC, "&Edit Miscellaneous", "") edit_menu.AppendSeparator() edit_menu.Append(ID_FREE_FORM_MODE, "&Free form Mode", "") edit_menu.Append(ID_LIMIT_SIZE, "&Limit Size", "") """ self.level_menu = QMenu("Level") self.select_level_action = self.level_menu.addAction("&Select Level") self.select_level_action.triggered.connect(self.open_level_selector) self.reload_action = self.level_menu.addAction("&Reload Level") self.reload_action.triggered.connect(self.reload_level) self.level_menu.addSeparator() self.edit_header_action = self.level_menu.addAction("&Edit Header") self.edit_header_action.triggered.connect(self.on_header_editor) self.edit_autoscroll = self.level_menu.addAction("Edit Autoscrolling") self.edit_autoscroll.triggered.connect(self.on_edit_autoscroll) self.menuBar().addMenu(self.level_menu) self.object_menu = QMenu("Objects") view_blocks_action = self.object_menu.addAction("&View Blocks") view_blocks_action.triggered.connect(self.on_block_viewer) view_objects_action = self.object_menu.addAction("&View Objects") view_objects_action.triggered.connect(self.on_object_viewer) self.object_menu.addSeparator() view_palettes_action = self.object_menu.addAction("View Object Palettes") view_palettes_action.triggered.connect(self.on_palette_viewer) self.menuBar().addMenu(self.object_menu) self.view_menu = QMenu("View") self.view_menu.triggered.connect(self.on_menu) action = self.view_menu.addAction("Mario") action.setProperty(ID_PROP, ID_MARIO) action.setCheckable(True) action.setChecked(SETTINGS["draw_mario"]) action = self.view_menu.addAction("&Jumps on objects") action.setProperty(ID_PROP, ID_JUMP_OBJECTS) action.setCheckable(True) action.setChecked(SETTINGS["draw_jump_on_objects"]) action = self.view_menu.addAction("Items in blocks") action.setProperty(ID_PROP, ID_ITEM_BLOCKS) action.setCheckable(True) action.setChecked(SETTINGS["draw_items_in_blocks"]) action = self.view_menu.addAction("Invisible items") action.setProperty(ID_PROP, ID_INVISIBLE_ITEMS) action.setCheckable(True) action.setChecked(SETTINGS["draw_invisible_items"]) action = self.view_menu.addAction("Autoscroll Path") action.setProperty(ID_PROP, ID_AUTOSCROLL) action.setCheckable(True) action.setChecked(SETTINGS["draw_autoscroll"]) self.view_menu.addSeparator() action = self.view_menu.addAction("Jump Zones") action.setProperty(ID_PROP, ID_JUMPS) action.setCheckable(True) action.setChecked(SETTINGS["draw_jumps"]) action = self.view_menu.addAction("&Grid lines") action.setProperty(ID_PROP, ID_GRID_LINES) action.setCheckable(True) action.setChecked(SETTINGS["draw_grid"]) action = self.view_menu.addAction("Resize Type") action.setProperty(ID_PROP, ID_RESIZE_TYPE) action.setCheckable(True) action.setChecked(SETTINGS["draw_expansion"]) self.view_menu.addSeparator() action = self.view_menu.addAction("&Block Transparency") action.setProperty(ID_PROP, ID_TRANSPARENCY) action.setCheckable(True) action.setChecked(SETTINGS["block_transparency"]) self.view_menu.addSeparator() self.view_menu.addAction("&Save Screenshot of Level").triggered.connect(self.on_screenshot) """ self.view_menu.Append(ID_BACKGROUND_FLOOR, "&Background & Floor", "") self.view_menu.Append(ID_TOOLBAR, "&Toolbar", "") self.view_menu.AppendSeparator() self.view_menu.Append(ID_ZOOM, "&Zoom", "") self.view_menu.AppendSeparator() self.view_menu.Append(ID_USE_ROM_GRAPHICS, "&Use ROM Graphics", "") self.view_menu.Append(ID_PALETTE, "&Palette", "") self.view_menu.AppendSeparator() self.view_menu.Append(ID_MORE, "&More", "") """ self.menuBar().addMenu(self.view_menu) help_menu = QMenu("Help") """ help_menu.Append(ID_ENEMY_COMPATIBILITY, "&Enemy Compatibility", "") help_menu.Append(ID_TROUBLESHOOTING, "&Troubleshooting", "") help_menu.AppendSeparator() help_menu.Append(ID_PROGRAM_WEBSITE, "&Program Website", "") help_menu.Append(ID_MAKE_A_DONATION, "&Make a Donation", "") help_menu.AppendSeparator() """ update_action = help_menu.addAction("Check for updates") update_action.triggered.connect(self.on_check_for_update) help_menu.addSeparator() video_action = help_menu.addAction("Feature Video on YouTube") video_action.triggered.connect(lambda: open_url(feature_video_link)) github_action = help_menu.addAction("Github Repository") github_action.triggered.connect(lambda: open_url(github_link)) discord_action = help_menu.addAction("SMB3 Rom Hacking Discord") discord_action.triggered.connect(lambda: open_url(discord_link)) help_menu.addSeparator() enemy_compat_action = help_menu.addAction("Enemy Compatibility") enemy_compat_action.triggered.connect(lambda: open_url(enemy_compat_link)) about_action = help_menu.addAction("&About") about_action.triggered.connect(self.on_about) self.menuBar().addMenu(help_menu) self.block_viewer = None self.object_viewer = None self.level_ref = LevelRef() self.level_ref.data_changed.connect(self._on_level_data_changed) self.context_menu = ContextMenu(self.level_ref) self.context_menu.triggered.connect(self.on_menu) self.level_view = LevelView(self, self.level_ref, self.context_menu) self.scroll_panel = QScrollArea() self.scroll_panel.setWidgetResizable(True) self.scroll_panel.setWidget(self.level_view) self.setCentralWidget(self.scroll_panel) self.spinner_panel = SpinnerPanel(self, self.level_ref) self.spinner_panel.zoom_in_triggered.connect(self.level_view.zoom_in) self.spinner_panel.zoom_out_triggered.connect(self.level_view.zoom_out) self.spinner_panel.object_change.connect(self.on_spin) self.object_list = ObjectList(self, self.level_ref, self.context_menu) self.object_dropdown = ObjectDropdown(self) self.object_dropdown.object_selected.connect(self._on_placeable_object_selected) self.level_size_bar = LevelSizeBar(self, self.level_ref) self.enemy_size_bar = EnemySizeBar(self, self.level_ref) self.jump_list = JumpList(self, self.level_ref) self.jump_list.add_jump.connect(self.on_jump_added) self.jump_list.edit_jump.connect(self.on_jump_edit) self.jump_list.remove_jump.connect(self.on_jump_removed) jump_buttons = QWidget() jump_buttons.setLayout(QHBoxLayout()) jump_buttons.layout().setContentsMargins(0, 0, 0, 0) add_jump_button = QPushButton("Add Jump") add_jump_button.clicked.connect(self.on_jump_added) set_jump_destination_button = QPushButton("Set Jump Destination") set_jump_destination_button.clicked.connect(self._show_jump_dest) jump_buttons.layout().addWidget(add_jump_button) jump_buttons.layout().addWidget(set_jump_destination_button) splitter = QSplitter(self) splitter.setOrientation(Qt.Vertical) splitter.addWidget(self.object_list) splitter.setStretchFactor(0, 1) splitter.addWidget(self.jump_list) splitter.addWidget(jump_buttons) splitter.setChildrenCollapsible(False) level_toolbar = QToolBar("Level Info Toolbar", self) level_toolbar.setContextMenuPolicy(Qt.PreventContextMenu) level_toolbar.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) level_toolbar.setOrientation(Qt.Horizontal) level_toolbar.setFloatable(False) level_toolbar.addWidget(self.spinner_panel) level_toolbar.addWidget(self.object_dropdown) level_toolbar.addWidget(self.level_size_bar) level_toolbar.addWidget(self.enemy_size_bar) level_toolbar.addWidget(splitter) level_toolbar.setAllowedAreas(Qt.LeftToolBarArea | Qt.RightToolBarArea) self.addToolBar(Qt.RightToolBarArea, level_toolbar) self.object_toolbar = ObjectToolBar(self) self.object_toolbar.object_selected.connect(self._on_placeable_object_selected) object_toolbar = QToolBar("Object Toolbar", self) object_toolbar.setContextMenuPolicy(Qt.PreventContextMenu) object_toolbar.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) object_toolbar.setFloatable(False) object_toolbar.addWidget(self.object_toolbar) object_toolbar.setAllowedAreas(Qt.LeftToolBarArea | Qt.RightToolBarArea) self.addToolBar(Qt.LeftToolBarArea, object_toolbar) self.menu_toolbar = QToolBar("Menu Toolbar", self) self.menu_toolbar.setOrientation(Qt.Horizontal) self.menu_toolbar.setIconSize(QSize(20, 20)) self.menu_toolbar.addAction(icon("settings.svg"), "Editor Settings").triggered.connect(show_settings) self.menu_toolbar.addSeparator() self.menu_toolbar.addAction(icon("folder.svg"), "Open ROM").triggered.connect(self.on_open_rom) self.menu_toolbar.addAction(icon("save.svg"), "Save Level").triggered.connect(self.on_save_rom) self.menu_toolbar.addSeparator() self.undo_action = self.menu_toolbar.addAction(icon("rotate-ccw.svg"), "Undo Action") self.undo_action.triggered.connect(self.level_ref.undo) self.undo_action.setEnabled(False) self.redo_action = self.menu_toolbar.addAction(icon("rotate-cw.svg"), "Redo Action") self.redo_action.triggered.connect(self.level_ref.redo) self.redo_action.setEnabled(False) self.menu_toolbar.addSeparator() play_action = self.menu_toolbar.addAction(icon("play-circle.svg"), "Play Level") play_action.triggered.connect(self.on_play) play_action.setWhatsThis("Opens an emulator with the current Level set to 1-1.\nSee Settings.") self.menu_toolbar.addSeparator() self.menu_toolbar.addAction(icon("zoom-out.svg"), "Zoom Out").triggered.connect(self.level_view.zoom_out) self.menu_toolbar.addAction(icon("zoom-in.svg"), "Zoom In").triggered.connect(self.level_view.zoom_in) self.menu_toolbar.addSeparator() header_action = self.menu_toolbar.addAction(icon("tool.svg"), "Edit Level Header") header_action.triggered.connect(self.on_header_editor) header_action.setWhatsThis( "<b>Header Editor</b><br/>" "Many configurations regarding the level are done in its header, like the length of " "the timer, or where and how Mario enters the level.<br/>" ) self.jump_destination_action = self.menu_toolbar.addAction( icon("arrow-right-circle.svg"), "Go to Jump Destination" ) self.jump_destination_action.triggered.connect(self._go_to_jump_destination) self.jump_destination_action.setWhatsThis( "Opens the level, that can be reached from this one, e.g. by entering a pipe." ) self.menu_toolbar.addSeparator() whats_this_action = QWhatsThis.createAction() whats_this_action.setWhatsThis("Click on parts of the editor, to receive help information.") whats_this_action.setIcon(icon("help-circle.svg")) whats_this_action.setText("Starts 'What's this?' mode") self.menu_toolbar.addAction(whats_this_action) self.menu_toolbar.addSeparator() self.warning_list = WarningList(self, self.level_ref) warning_action = self.menu_toolbar.addAction(icon("alert-triangle.svg"), "Warning Panel") warning_action.setWhatsThis("Shows a list of warnings.") warning_action.triggered.connect(self.warning_list.show) warning_action.setDisabled(True) self.warning_list.warnings_updated.connect(warning_action.setEnabled) self.addToolBar(Qt.TopToolBarArea, self.menu_toolbar) self.status_bar = ObjectStatusBar(self, self.level_ref) self.setStatusBar(self.status_bar) self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self, self.remove_selected_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_X), self, self._cut_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_C), self, self._copy_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_V), self, self._paste_objects) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Z), self, self.level_ref.undo) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Y), self, self.level_ref.redo) QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_Z), self, self.level_ref.redo) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Plus), self, self.level_view.zoom_in) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Minus), self, self.level_view.zoom_out) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_A), self, self.level_view.select_all) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_L), self, self.object_dropdown.setFocus) self.on_open_rom(path_to_rom) self.showMaximized() def _on_level_data_changed(self): self.undo_action.setEnabled(self.level_ref.undo_stack.undo_available) self.redo_action.setEnabled(self.level_ref.undo_stack.redo_available) self.jump_destination_action.setEnabled(self.level_ref.level.has_next_area) self._save_auto_save() def _save_auto_save(self): self._save_current_changes_to_file(auto_save_rom_path, set_new_path=False) undo_index, data = self.level_ref.level.undo_stack.export_data() (level_offset, _), (enemy_offset, _) = self.level_ref.level.to_bytes() object_set_number = self.level_ref.level.object_set_number base64_data = [] for (object_offset, object_data), (enemy_offset_, enemy_data) in data: base64_data.append( ( object_offset, base64.b64encode(object_data).decode("ascii"), enemy_offset_, base64.b64encode(enemy_data).decode("ascii") ) ) with open(auto_save_level_data_path, "w") as level_data_file: level_data_file.write( json.dumps( [object_set_number, level_offset, enemy_offset, (undo_index, base64_data)] ) ) def _load_auto_save(self): # rom already loaded with open(auto_save_level_data_path, "r") as level_data_file: json_data = level_data_file.read() object_set_number, level_offset, enemy_offset, (undo_index, base64_data) = json.loads(json_data) self.update_level("recovered level", level_offset, enemy_offset, object_set_number) byte_data = [] for undo_data in base64_data: level_offset, object_data, enemy_offset, enemy_data = undo_data object_data = bytearray(base64.b64decode(object_data)) enemy_data = bytearray(base64.b64decode(enemy_data)) byte_data.append(((level_offset, object_data), (enemy_offset, enemy_data))) self.level_ref.level.undo_stack.import_data(undo_index, byte_data) self.level_ref.level.changed = bool(base64_data) self._on_level_data_changed() def _go_to_jump_destination(self): if not self.safe_to_change(): return level_address = self.level_ref.level.next_area_objects enemy_address = self.level_ref.level.next_area_enemies + 1 object_set = self.level_ref.level.next_area_object_set world, level = world_and_level_for_level_address(level_address) self.update_level(f"Level {world}-{level}", level_address, enemy_address, object_set) def on_play(self): """ Copies the ROM, including the current level, to a temporary directory, saves the current level as level 1-1 and opens the rom in an emulator. """ temp_dir = pathlib.Path(tempfile.gettempdir()) / "smb3foundry" temp_dir.mkdir(parents=True, exist_ok=True) path_to_temp_rom = temp_dir / "instaplay.rom" ROM().save_to(path_to_temp_rom) if not self._put_current_level_to_level_1_1(path_to_temp_rom): return if not self._set_default_powerup(path_to_temp_rom): return arguments = SETTINGS["instaplay_arguments"].replace("%f", str(path_to_temp_rom)) arguments = shlex.split(arguments, posix=False) emu_path = pathlib.Path(SETTINGS["instaplay_emulator"]) if emu_path.is_absolute(): if emu_path.exists(): emulator = str(emu_path) else: QMessageBox.critical( self, "Emulator not found", f"Check it under File > Settings.\nFile {emu_path} not found." ) return else: emulator = SETTINGS["instaplay_emulator"] try: subprocess.run([emulator, *arguments]) except Exception as e: QMessageBox.critical(self, "Emulator command failed.", f"Check it under File > Settings.\n{str(e)}") @staticmethod def _open_rom(path_to_rom): with open(path_to_rom, "rb") as smb3_rom: data = smb3_rom.read() rom = SMB3Rom(bytearray(data)) return rom def _show_jump_dest(self): header_editor = HeaderEditor(self, self.level_ref) header_editor.tab_widget.setCurrentIndex(3) header_editor.exec_() def _put_current_level_to_level_1_1(self, path_to_rom) -> bool: rom = self._open_rom(path_to_rom) # load world-1 data world_1 = SMB3World.from_world_number(rom, 1) # find position of "level 1" tile in world map for position in world_1.gen_positions(): if position.tile() == TILE_LEVEL_1: break else: QMessageBox.critical( self, "Couldn't place level", "Could not find a level 1 tile in World 1 to put your level at." ) return False if not self.level_ref.level.attached_to_rom: QMessageBox.critical( self, "Couldn't place level", "The Level is not part of the rom yet (M3L?). Try saving it into the ROM first.", ) return False # write level and enemy data of current level (layout_address, layout_bytes), (enemy_address, enemy_bytes) = self.level_ref.level.to_bytes() rom.write(layout_address, layout_bytes) rom.write(enemy_address, enemy_bytes) # replace level information with that of current level object_set_number = self.level_ref.object_set_number world_1.replace_level_at_position((layout_address, enemy_address - 1, object_set_number), position) # save rom rom.save_to(path_to_rom) return True def _set_default_powerup(self, path_to_rom) -> bool: rom = self._open_rom(path_to_rom) *_, powerup, hasPWing = POWERUPS[SETTINGS["default_powerup"]] rom.write(Title_PrepForWorldMap + 0x1, bytes([powerup])) nop = 0xEA rts = 0x60 lda = 0xA9 staAbsolute = 0x8D # If a P-wing powerup is selected, another variable needs to be set with the P-wing value # This piece of code overwrites a part of Title_DebugMenu if hasPWing: Map_Power_DispHigh = 0x03 Map_Power_DispLow = 0xF3 # We need to start one byte before Title_DebugMenu to remove the RTS of Title_PrepForWorldMap # The assembly code below reads as follows: # LDA 0x08 # STA $03F3 # RTS rom.write( Title_DebugMenu - 0x1, bytes( [ lda, 0x8, staAbsolute, Map_Power_DispLow, Map_Power_DispHigh, # The RTS to get out of the now extended Title_PrepForWorldMap rts, ] ), ) # Remove code that resets the powerup value by replacing it with no-operations # Otherwise this code would copy the value of the normal powerup here # (So if the powerup would be Raccoon Mario, Map_Power_Disp would also be # set as Raccoon Mario instead of P-wing Map_Power_DispResetLocation = 0x3C5A2 rom.write(Map_Power_DispResetLocation, bytes([nop, nop, nop])) rom.save_to(path_to_rom) return True def on_screenshot(self, _) -> bool: if self.level_view is None: return False recommended_file = f"{os.path.expanduser('~')}/{ROM.name} - {self.level_view.level_ref.name}.png" pathname, _ = QFileDialog.getSaveFileName( self, caption="Save Screenshot", dir=recommended_file, filter=IMG_FILE_FILTER ) if not pathname: return False # Proceed loading the file chosen by the user self.level_view.make_screenshot().save(pathname) return True def update_title(self): if self.level_view.level_ref is not None and ROM is not None: title = f"{self.level_view.level_ref.name} - {ROM.name}" else: title = "SMB3Foundry" self.setWindowTitle(title) def on_open_rom(self, path_to_rom="") -> bool: if not self.safe_to_change(): return False if not path_to_rom: # otherwise ask the user what new file to open path_to_rom, _ = QFileDialog.getOpenFileName(self, caption="Open ROM", filter=ROM_FILE_FILTER) if not path_to_rom: self._enable_disable_gui_elements() return False # Proceed loading the file chosen by the user try: ROM.load_from_file(path_to_rom) if path_to_rom == auto_save_rom_path: self._load_auto_save() else: return self.open_level_selector(None) except IOError as exp: QMessageBox.warning(self, type(exp).__name__, f"Cannot open file '{path_to_rom}'.") return False finally: self._enable_disable_gui_elements() def on_open_m3l(self, _) -> bool: if not self.safe_to_change(): return False # otherwise ask the user what new file to open pathname, _ = QFileDialog.getOpenFileName(self, caption="Open M3L file", filter=M3L_FILE_FILTER) if not pathname: return False # Proceed loading the file chosen by the user try: with open(pathname, "rb") as m3l_file: self.level_view.from_m3l(bytearray(m3l_file.read())) except IOError as exp: QMessageBox.warning(self, type(exp).__name__, f"Cannot open file '{pathname}'.") return False self.level_view.level_ref.name = os.path.basename(pathname) self.update_gui_for_level() return True def safe_to_change(self) -> bool: if not self.level_ref: return True if self.level_ref.level.changed: answer = QMessageBox.question( self, "Please confirm", "Current content has not been saved! Proceed?", QMessageBox.No | QMessageBox.Yes, QMessageBox.No, ) return answer == QMessageBox.Yes else: return True def on_save_rom(self, _): self.save_rom(False) def on_save_rom_as(self, _): self.save_rom(True) def save_rom(self, is_save_as): safe_to_save, reason, additional_info = self.level_view.level_safe_to_save() if not safe_to_save: answer = QMessageBox.warning( self, reason, f"{additional_info}\n\nDo you want to proceed?", QMessageBox.No | QMessageBox.Yes, QMessageBox.No, ) if answer == QMessageBox.No: return if not self.level_ref.attached_to_rom: QMessageBox.information( self, "Importing M3L into ROM", "Please select the positions in the ROM you want the level objects and enemies/items to be stored.", QMessageBox.Ok, ) level_selector = LevelSelector(self) answer = level_selector.exec_() if answer == QMessageBox.Accepted: self.level_view.level_ref.attach_to_rom( level_selector.object_data_offset, level_selector.enemy_data_offset ) if is_save_as: # if we save to another rom, don't consider the level # attached (to the current rom) self.level_view.level_ref.attached_to_rom = False else: return if is_save_as: pathname, _ = QFileDialog.getSaveFileName(self, caption="Save ROM as", filter=ROM_FILE_FILTER) if not pathname: return # the user changed their mind else: pathname = ROM.path if str(pathname) == str(auto_save_rom_path): QMessageBox.critical( self, "Cannot save to auto save ROM", "You can't save to the auto save ROM, as it will be deleted, when exiting the editor. Please choose " "another location, or your changes will be lost." ) self._save_current_changes_to_file(pathname, set_new_path=True) self.update_title() if not is_save_as: self.level_ref.changed = False def _save_current_changes_to_file(self, pathname: str, set_new_path): for offset, data in self.level_ref.to_bytes(): ROM().bulk_write(data, offset) try: ROM().save_to_file(pathname, set_new_path) except IOError as exp: QMessageBox.warning(self, f"{type(exp).__name__}", f"Cannot save ROM data to file '{pathname}'.") def on_save_m3l(self, _): suggested_file = self.level_view.level_ref.name if not suggested_file.endswith(".m3l"): suggested_file += ".m3l" pathname, _ = QFileDialog.getSaveFileName(self, caption="Save M3L as", filter=M3L_FILE_FILTER) if not pathname: return level = self.level_view.level_ref try: with open(pathname, "wb") as m3l_file: m3l_file.write(level.to_m3l()) except IOError as exp: QMessageBox.warning(self, type(exp).__name__, f"Couldn't save level to '{pathname}'.") def on_check_for_update(self): self.setCursor(Qt.WaitCursor) current_version = get_current_version_name() try: latest_version = get_latest_version_name() except ValueError as ve: QMessageBox.critical(self, "Error while checking for updates", f"Error: {ve}") self.setCursor(Qt.ArrowCursor) return if current_version != latest_version: latest_release_url = f"{releases_link}/tag/{latest_version}" go_to_github_button = QPushButton(icon("external-link.svg"), "Go to latest release") go_to_github_button.clicked.connect(lambda: open_url(latest_release_url)) info_box = QMessageBox( QMessageBox.Information, "New release available", f"New Version {latest_version} is available." ) info_box.addButton(QMessageBox.Cancel) info_box.addButton(go_to_github_button, QMessageBox.AcceptRole) info_box.exec_() else: QMessageBox.information(self, "No newer release", f"Version {current_version} is up to date.") self.setCursor(Qt.ArrowCursor) def on_menu(self, action: QAction): item_id = action.property(ID_PROP) if item_id in CHECKABLE_MENU_ITEMS: self.on_menu_item_checked(action) self.level_view.update() # if setting a checkbox, keep the menu open menu_of_action: QMenu = self.sender() menu_of_action.exec_() elif item_id in self.context_menu.get_all_menu_item_ids(): x, y = self.context_menu.get_position() if item_id == CMAction.REMOVE: self.remove_selected_objects() elif item_id == CMAction.ADD_OBJECT: selected_object = self.object_dropdown.currentIndex() if selected_object != -1: self.place_object_from_dropdown((x, y)) else: self.create_object_at(x, y) elif item_id == CMAction.CUT: self._cut_objects() elif item_id == CMAction.COPY: self._copy_objects() elif item_id == CMAction.PASTE: self._paste_objects(x, y) elif item_id == CMAction.FOREGROUND: self.bring_objects_to_foreground() elif item_id == CMAction.BACKGROUND: self.bring_objects_to_background() self.level_view.update() def reload_level(self): if not self.safe_to_change(): return level_name = self.level_view.level_ref.name object_data = self.level_view.level_ref.header_offset enemy_data = self.level_view.level_ref.enemy_offset object_set = self.level_view.level_ref.object_set_number self.update_level(level_name, object_data, enemy_data, object_set) def _on_placeable_object_selected(self, level_object: Union[LevelObject, EnemyObject]): if self.sender() is self.object_toolbar: self.object_dropdown.select_object(level_object) else: self.object_toolbar.select_object(level_object) @undoable def bring_objects_to_foreground(self): self.level_ref.level.bring_to_foreground(self.level_ref.selected_objects) @undoable def bring_objects_to_background(self): self.level_ref.level.bring_to_background(self.level_ref.selected_objects) @undoable def create_object_at(self, x, y): self.level_view.create_object_at(x, y) @undoable def create_enemy_at(self, x, y): self.level_view.create_enemy_at(x, y) def _cut_objects(self): self._copy_objects() self.remove_selected_objects() def _copy_objects(self): selected_objects = self.level_view.get_selected_objects().copy() if selected_objects: self.context_menu.set_copied_objects(selected_objects) @undoable def _paste_objects(self, x=None, y=None): self.level_view.paste_objects_at(self.context_menu.get_copied_objects(), x, y) @undoable def remove_selected_objects(self): self.level_view.remove_selected_objects() self.level_view.update() self.spinner_panel.disable_all() def on_menu_item_checked(self, action: QAction): item_id = action.property(ID_PROP) checked = action.isChecked() if item_id == ID_GRID_LINES: self.level_view.draw_grid = checked elif item_id == ID_TRANSPARENCY: self.level_view.transparency = checked elif item_id == ID_JUMPS: self.level_view.draw_jumps = checked elif item_id == ID_MARIO: self.level_view.draw_mario = checked elif item_id == ID_RESIZE_TYPE: self.level_view.draw_expansions = checked elif item_id == ID_JUMP_OBJECTS: self.level_view.draw_jumps_on_objects = checked elif item_id == ID_ITEM_BLOCKS: self.level_view.draw_items_in_blocks = checked elif item_id == ID_INVISIBLE_ITEMS: self.level_view.draw_invisible_items = checked elif item_id == ID_AUTOSCROLL: self.level_view.draw_autoscroll = checked SETTINGS["draw_mario"] = self.level_view.draw_mario SETTINGS["draw_jumps"] = self.level_view.draw_jumps SETTINGS["draw_grid"] = self.level_view.draw_grid SETTINGS["draw_expansion"] = self.level_view.draw_expansions SETTINGS["draw_jump_on_objects"] = self.level_view.draw_jumps_on_objects SETTINGS["draw_items_in_blocks"] = self.level_view.draw_items_in_blocks SETTINGS["draw_invisible_items"] = self.level_view.draw_invisible_items SETTINGS["draw_autoscroll"] = self.level_view.draw_autoscroll SETTINGS["block_transparency"] = self.level_view.transparency save_settings() @undoable def on_spin(self, _): selected_objects = self.level_ref.selected_objects if len(selected_objects) != 1: logging.error(selected_objects, RuntimeWarning) return selected_object = selected_objects[0] obj_type = self.spinner_panel.get_type() if isinstance(selected_object, LevelObject): domain = self.spinner_panel.get_domain() if selected_object.is_4byte: length = self.spinner_panel.get_length() else: length = None self.level_view.replace_object(selected_object, domain, obj_type, length) else: self.level_view.replace_enemy(selected_object, obj_type) self.level_ref.data_changed.emit() def fill_object_list(self): self.object_list.Clear() self.object_list.SetItems(self.level_view.get_object_names()) def open_level_selector(self, _): if not self.safe_to_change(): return level_selector = LevelSelector(self) level_was_selected = level_selector.exec_() == QDialog.Accepted if level_was_selected: self.update_level( level_selector.level_name, level_selector.object_data_offset, level_selector.enemy_data_offset, level_selector.object_set, ) return level_was_selected def on_block_viewer(self, _): if self.block_viewer is None: self.block_viewer = BlockViewer(parent=self) if self.level_ref.level is not None: self.block_viewer.object_set = self.level_ref.object_set.number self.block_viewer.palette_group = self.level_ref.object_palette_index self.block_viewer.show() def on_object_viewer(self, _): if self.object_viewer is None: self.object_viewer = ObjectViewer(parent=self) if self.level_ref.level is not None: object_set = self.level_ref.object_set.number graphics_set = self.level_ref.graphic_set self.object_viewer.set_object_and_graphic_set(object_set, graphics_set) if len(self.level_view.get_selected_objects()) == 1: selected_object = self.level_view.get_selected_objects()[0] if isinstance(selected_object, LevelObject): self.object_viewer.set_object( selected_object.domain, selected_object.obj_index, selected_object.length ) self.object_viewer.show() def on_palette_viewer(self, _): PaletteViewer(self, self.level_ref).exec_() def on_edit_autoscroll(self, _): AutoScrollEditor(self, self.level_ref).exec_() def on_header_editor(self, _): HeaderEditor(self, self.level_ref).exec_() def update_level(self, level_name: str, object_data_offset: int, enemy_data_offset: int, object_set: int): try: self.level_ref.load_level(level_name, object_data_offset, enemy_data_offset, object_set) except IndexError: QMessageBox.critical(self, "Please confirm", "Failed loading level. The level offsets don't match.") return self.update_gui_for_level() def update_gui_for_level(self): self._enable_disable_gui_elements() self.update_title() self.jump_list.update() is_a_world_map = isinstance(self.level_ref.level, WorldMap) self.save_m3l_action.setEnabled(not is_a_world_map) self.edit_header_action.setEnabled(not is_a_world_map) if is_a_world_map: self.object_dropdown.Clear() self.object_dropdown.setEnabled(False) self.jump_list.setEnabled(False) self.jump_list.Clear() else: self.object_dropdown.setEnabled(True) self.object_dropdown.set_object_set(self.level_ref.object_set_number, self.level_ref.graphic_set) self.jump_list.setEnabled(True) self.object_toolbar.set_object_set(self.level_ref.object_set_number, self.level_ref.graphic_set) self.level_view.update() def _enable_disable_gui_elements(self): rom_elements = [ # entries in file menu self.open_m3l_action, self.save_rom_action, self.save_rom_as_action, # entry in level menu self.select_level_action, ] level_elements = [ # entry in file menu self.save_m3l_action, # top toolbar self.menu_toolbar, # other gui elements self.level_view, self.spinner_panel, self.object_toolbar, self.level_size_bar, self.enemy_size_bar, self.object_list, self.jump_list, self.object_toolbar, ] level_elements.extend(self.level_menu.actions()) level_elements.remove(self.select_level_action) level_elements.extend(self.object_menu.actions()) level_elements.extend(self.view_menu.actions()) for gui_element in rom_elements: gui_element.setEnabled(ROM.is_loaded()) for gui_element in level_elements: gui_element.setEnabled(ROM.is_loaded() and self.level_ref.level is not None) def on_jump_edit(self): index = self.jump_list.currentIndex().row() updated_jump = JumpEditor.edit_jump(self, self.level_view.level_ref.jumps[index]) self.on_jump_edited(updated_jump) @undoable def on_jump_added(self): self.level_view.add_jump() @undoable def on_jump_removed(self): self.level_view.remove_jump(self.jump_list.currentIndex().row()) @undoable def on_jump_edited(self, jump): index = self.jump_list.currentIndex().row() assert index >= 0 if isinstance(self.level_ref.level, Level): self.level_view.level_ref.jumps[index] = jump self.jump_list.item(index).setText(str(jump)) def on_jump_list_change(self, event): self.jump_list.set_jumps(event) def mouseReleaseEvent(self, event: QMouseEvent): if event.button() == Qt.MiddleButton: pos = self.level_view.mapFromGlobal(self.mapToGlobal(event.pos())).toTuple() self.place_object_from_dropdown(pos) @undoable def place_object_from_dropdown(self, pos: Tuple[int, int]) -> None: # the dropdown is synchronized with the toolbar, so it doesn't matter where to take it from level_object = self.object_dropdown.currentData(Qt.UserRole) self.object_toolbar.add_recent_object(level_object) if isinstance(level_object, LevelObject): self.level_view.create_object_at(*pos, level_object.domain, level_object.obj_index) elif isinstance(level_object, EnemyObject): self.level_view.add_enemy(level_object.obj_index, *pos, -1) def on_about(self, _): about = AboutDialog(self) about.show() def closeEvent(self, event: QCloseEvent): if not self.safe_to_change(): event.ignore() return auto_save_rom_path.unlink(missing_ok=True) auto_save_level_data_path.unlink(missing_ok=True) super(MainWindow, self).closeEvent(event)
class MainWindow(QWidget): # noinspection PyUnresolvedReferences def __init__(self): super().__init__() self.setWindowTitle("Cobaya input generator for Cosmology") self.setStyleSheet("* {font-size:%s;}" % font_size) # Menu bar for defaults self.menubar = QMenuBar() defaults_menu = self.menubar.addMenu( '&Show defaults and bibliography for a component...') menu_actions = {} for kind in kinds: submenu = defaults_menu.addMenu(subfolders[kind]) components = get_available_internal_class_names(kind) menu_actions[kind] = {} for component in components: menu_actions[kind][component] = QAction(component, self) menu_actions[kind][component].setData((kind, component)) menu_actions[kind][component].triggered.connect(self.show_defaults) submenu.addAction(menu_actions[kind][component]) # Main layout self.menu_layout = QVBoxLayout() self.menu_layout.addWidget(self.menubar) self.setLayout(self.menu_layout) self.layout = QHBoxLayout() self.menu_layout.addLayout(self.layout) self.layout_left = QVBoxLayout() self.layout.addLayout(self.layout_left) self.layout_output = QVBoxLayout() self.layout.addLayout(self.layout_output) # LEFT: Options self.options = QWidget() self.layout_options = QVBoxLayout() self.options.setLayout(self.layout_options) self.options_scroll = QScrollArea() self.options_scroll.setWidget(self.options) self.options_scroll.setWidgetResizable(True) self.layout_left.addWidget(self.options_scroll) self.combos = dict() for group, fields in _combo_dict_text: group_box = QGroupBox(group) self.layout_options.addWidget(group_box) group_layout = QVBoxLayout(group_box) for a, desc in fields: self.combos[a] = QComboBox() # Combo box label only if not single element in group if len(fields) > 1: label = QLabel(desc) group_layout.addWidget(label) group_layout.addWidget(self.combos[a]) self.combos[a].addItems( [text(k, v) for k, v in getattr(input_database, a).items()]) # PLANCK NAMES CHECKBOX TEMPORARILY DISABLED # if a == "theory": # # Add Planck-naming checkbox # self.planck_names = QCheckBox( # "Keep common parameter names " # "(useful for fast CLASS/CAMB switching)") # group_layout.addWidget(self.planck_names) # Connect to refreshers -- needs to be after adding all elements for field, combo in self.combos.items(): if field == "preset": combo.currentIndexChanged.connect(self.refresh_preset) else: combo.currentIndexChanged.connect(self.refresh) # self.planck_names.stateChanged.connect(self.refresh_keep_preset) # RIGHT: Output + buttons self.display_tabs = QTabWidget() self.display = {} for k in ["yaml", "python", "bibliography"]: self.display[k] = QTextEdit() self.display[k].setLineWrapMode(QTextEdit.NoWrap) self.display[k].setFontFamily("mono") self.display[k].setCursorWidth(0) self.display[k].setReadOnly(True) self.display_tabs.addTab(self.display[k], k) self.display["covmat"] = QWidget() covmat_tab_layout = QVBoxLayout() self.display["covmat"].setLayout(covmat_tab_layout) self.covmat_text = QLabel() self.covmat_text.setWordWrap(True) self.covmat_table = QTableWidget(0, 0) self.covmat_table.setEditTriggers(QAbstractItemView.NoEditTriggers) # ReadOnly! covmat_tab_layout.addWidget(self.covmat_text) covmat_tab_layout.addWidget(self.covmat_table) self.display_tabs.addTab(self.display["covmat"], "covariance matrix") self.layout_output.addWidget(self.display_tabs) # Buttons self.buttons = QHBoxLayout() self.save_button = QPushButton('Save as...', self) self.copy_button = QPushButton('Copy to clipboard', self) self.buttons.addWidget(self.save_button) self.buttons.addWidget(self.copy_button) self.save_button.released.connect(self.save_file) self.copy_button.released.connect(self.copy_clipb) self.layout_output.addLayout(self.buttons) self.save_dialog = QFileDialog() self.save_dialog.setFileMode(QFileDialog.AnyFile) self.save_dialog.setAcceptMode(QFileDialog.AcceptSave) self.read_settings() self.show() def read_settings(self): settings = get_settings() # noinspection PyArgumentList screen = QApplication.desktop().screenGeometry() h = min(screen.height() * 5 / 6., 900) size = QSize(min(screen.width() * 5 / 6., 1200), h) pos = settings.value("pos", None) savesize = settings.value("size", size) if savesize.width() > screen.width(): savesize.setWidth(size.width()) if savesize.height() > screen.height(): savesize.setHeight(size.height()) self.resize(savesize) if ((pos is None or pos.x() + savesize.width() > screen.width() or pos.y() + savesize.height() > screen.height())): self.move(screen.center() - self.rect().center()) else: self.move(pos) def write_settings(self): settings = get_settings() settings.setValue("pos", self.pos()) settings.setValue("size", self.size()) def closeEvent(self, event): self.write_settings() event.accept() def create_input(self): return create_input( get_comments=True, # planck_names=self.planck_names.isChecked(), **{field: list(getattr(input_database, field))[combo.currentIndex()] for field, combo in self.combos.items() if field != "preset"}) @Slot() def refresh_keep_preset(self): self.refresh_display(self.create_input()) @Slot() def refresh(self): self.combos["preset"].blockSignals(True) self.combos["preset"].setCurrentIndex(0) self.combos["preset"].blockSignals(False) self.refresh_display(self.create_input()) @Slot() def refresh_preset(self): preset = list(getattr(input_database, "preset"))[ self.combos["preset"].currentIndex()] if preset is input_database.none: return info = create_input( get_comments=True, # planck_names=self.planck_names.isChecked(), preset=preset) self.refresh_display(info) # Update combo boxes to reflect the preset values, without triggering update for k, v in input_database.preset[preset].items(): if k in ["desc"]: continue self.combos[k].blockSignals(True) self.combos[k].setCurrentIndex( self.combos[k].findText( text(v, getattr(input_database, k).get(v)))) self.combos[k].blockSignals(False) def refresh_display(self, info): QApplication.setOverrideCursor(Qt.WaitCursor) try: comments = info.pop("comment", None) comments_text = "\n# " + "\n# ".join(comments) except (TypeError, # No comments AttributeError): # Failed to generate info (returned str instead) comments_text = "" self.display["python"].setText("info = " + pformat(info) + comments_text) self.display["yaml"].setText(yaml_dump(sort_cosmetic(info)) + comments_text) self.display["bibliography"].setText(prettyprint_bib(*get_bib_info(info))) # Display covmat packages_path = resolve_packages_path() if not packages_path: self.covmat_text.setText( "\nIn order to find a covariance matrix, you need to define an external " "packages installation path, e.g. via the env variable %r.\n" % packages_path_env) elif any(not os.path.isdir(d.format(**{"packages_path": packages_path})) for d in covmat_folders): self.covmat_text.setText( "\nThe external cosmological packages appear not to be installed where " "expected: %r\n" % packages_path) else: covmat_data = get_best_covmat(info, packages_path=packages_path) self.current_params_in_covmat = covmat_data["params"] self.current_covmat = covmat_data["covmat"] _, corrmat = cov_to_std_and_corr(self.current_covmat) self.covmat_text.setText( "\nCovariance file: %r\n\n" "NB: you do *not* need to save or copy this covariance matrix: " "it will be selected automatically.\n" % covmat_data["name"]) self.covmat_table.setRowCount(len(self.current_params_in_covmat)) self.covmat_table.setColumnCount(len(self.current_params_in_covmat)) self.covmat_table.setHorizontalHeaderLabels( list(self.current_params_in_covmat)) self.covmat_table.setVerticalHeaderLabels( list(self.current_params_in_covmat)) for i, pi in enumerate(self.current_params_in_covmat): for j, pj in enumerate(self.current_params_in_covmat): self.covmat_table.setItem( i, j, QTableWidgetItem("%g" % self.current_covmat[i, j])) if i != j: color = [256 * c for c in cmap_corr(corrmat[i, j] / 2 + 0.5)[:3]] else: color = [255.99] * 3 self.covmat_table.item(i, j).setBackground(QColor(*color)) self.covmat_table.item(i, j).setForeground(QColor(0, 0, 0)) QApplication.restoreOverrideCursor() def save_covmat_txt(self, file_handle=None): """ Saved covmat to given file_handle. If none given, returns the text to be saved. """ return_txt = False if not file_handle: file_handle = io.BytesIO() return_txt = True np.savetxt(file_handle, self.current_covmat, header=" ".join(self.current_params_in_covmat)) if return_txt: return file_handle.getvalue().decode() @Slot() def save_file(self): ftype = next(k for k, w in self.display.items() if w is self.display_tabs.currentWidget()) ffilter = {"yaml": "Yaml files (*.yaml *.yml)", "python": "(*.py)", "bibliography": "(*.txt)", "covmat": "(*.covmat)"}[ftype] fsuffix = {"yaml": ".yaml", "python": ".py", "bibliography": ".txt", "covmat": ".covmat"}[ftype] fname, path = self.save_dialog.getSaveFileName( self.save_dialog, "Save input file", fsuffix, ffilter, os.getcwd()) if not fname.endswith(fsuffix): fname += fsuffix with open(fname, "w+", encoding="utf-8") as f: if self.display_tabs.currentWidget() == self.display["covmat"]: self.save_covmat_txt(f) else: f.write(self.display_tabs.currentWidget().toPlainText()) @Slot() def copy_clipb(self): if self.display_tabs.currentWidget() == self.display["covmat"]: self.clipboard.setText(self.save_covmat_txt()) else: self.clipboard.setText(self.display_tabs.currentWidget().toPlainText()) def show_defaults(self): kind, component = self.sender().data() self.current_defaults_diag = DefaultsDialog(kind, component, parent=self)
class JointStatePublisherGui(QWidget): sliderUpdateTrigger = Signal() def __init__(self, title, jsp, num_rows=0): super(JointStatePublisherGui, self).__init__() self.jsp = jsp self.joint_map = {} self.vlayout = QVBoxLayout(self) self.scrollable = QWidget() self.gridlayout = QGridLayout() self.scroll = QScrollArea() self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setWidgetResizable(True) font = QFont("Helvetica", 9, QFont.Bold) ### Generate sliders ### sliders = [] for name in self.jsp.joint_list: if name not in self.jsp.free_joints: continue joint = self.jsp.free_joints[name] if joint['min'] == joint['max']: continue joint_layout = QVBoxLayout() row_layout = QHBoxLayout() label = QLabel(name) label.setFont(font) row_layout.addWidget(label) display = QLineEdit("0.00") display.setAlignment(Qt.AlignRight) display.setFont(font) display.setReadOnly(True) row_layout.addWidget(display) joint_layout.addLayout(row_layout) slider = QSlider(Qt.Horizontal) slider.setFont(font) slider.setRange(0, RANGE) slider.setValue(RANGE / 2) joint_layout.addWidget(slider) self.joint_map[name] = { 'slidervalue': 0, 'display': display, 'slider': slider, 'joint': joint } # Connect to the signal provided by QSignal slider.valueChanged.connect(self.onValueChanged) sliders.append(joint_layout) # Determine number of rows to be used in grid self.num_rows = num_rows # if desired num of rows wasn't set, default behaviour is a vertical layout if self.num_rows == 0: self.num_rows = len(sliders) # equals VBoxLayout # Generate positions in grid and place sliders there self.positions = self.generate_grid_positions(len(sliders), self.num_rows) for item, pos in zip(sliders, self.positions): self.gridlayout.addLayout(item, *pos) # Set zero positions read from parameters self.center() # Synchronize slider and displayed value self.sliderUpdate(None) # Set up a signal for updating the sliders based on external joint info self.sliderUpdateTrigger.connect(self.updateSliders) self.scrollable.setLayout(self.gridlayout) self.scroll.setWidget(self.scrollable) self.vlayout.addWidget(self.scroll) # Buttons for randomizing and centering sliders and # Spinbox for on-the-fly selecting number of rows self.randbutton = QPushButton('Randomize', self) self.randbutton.clicked.connect(self.randomize_event) self.vlayout.addWidget(self.randbutton) self.ctrbutton = QPushButton('Center', self) self.ctrbutton.clicked.connect(self.center_event) self.vlayout.addWidget(self.ctrbutton) self.maxrowsupdown = QSpinBox() self.maxrowsupdown.setMinimum(1) self.maxrowsupdown.setMaximum(len(sliders)) self.maxrowsupdown.setValue(self.num_rows) self.maxrowsupdown.lineEdit().setReadOnly( True) # don't edit it by hand to avoid weird resizing of window self.maxrowsupdown.valueChanged.connect(self.reorggrid_event) self.vlayout.addWidget(self.maxrowsupdown) self.setLayout(self.vlayout) @pyqtSlot(int) def onValueChanged(self, event): # A slider value was changed, but we need to change the joint_info metadata. for name, joint_info in self.joint_map.items(): joint_info['slidervalue'] = joint_info['slider'].value() joint = joint_info['joint'] joint['position'] = self.sliderToValue(joint_info['slidervalue'], joint) joint_info['display'].setText("%.2f" % joint['position']) @pyqtSlot() def updateSliders(self): self.update_sliders() def update_sliders(self): for name, joint_info in self.joint_map.items(): joint = joint_info['joint'] joint_info['slidervalue'] = self.valueToSlider( joint['position'], joint) joint_info['slider'].setValue(joint_info['slidervalue']) def center_event(self, event): self.center() def center(self): rospy.loginfo("Centering") for name, joint_info in self.joint_map.items(): joint = joint_info['joint'] joint_info['slider'].setValue( self.valueToSlider(joint['zero'], joint)) def reorggrid_event(self, event): self.reorganize_grid(event) def reorganize_grid(self, number_of_rows): self.num_rows = number_of_rows # Remove items from layout (won't destroy them!) items = [] for pos in self.positions: item = self.gridlayout.itemAtPosition(*pos) items.append(item) self.gridlayout.removeItem(item) # Generate new positions for sliders and place them in their new spots self.positions = self.generate_grid_positions(len(items), self.num_rows) for item, pos in zip(items, self.positions): self.gridlayout.addLayout(item, *pos) def generate_grid_positions(self, num_items, num_rows): if num_rows == 0: return [] positions = [ (y, x) for x in range(int((math.ceil(float(num_items) / num_rows)))) for y in range(num_rows) ] positions = positions[:num_items] return positions def randomize_event(self, event): self.randomize() def randomize(self): rospy.loginfo("Randomizing") for name, joint_info in self.joint_map.items(): joint = joint_info['joint'] joint_info['slider'].setValue( self.valueToSlider(random.uniform(joint['min'], joint['max']), joint)) def sliderUpdate(self, event): for name, joint_info in self.joint_map.items(): joint_info['slidervalue'] = joint_info['slider'].value() self.update_sliders() def valueToSlider(self, value, joint): return (value - joint['min']) * float(RANGE) / (joint['max'] - joint['min']) def sliderToValue(self, slider, joint): pctvalue = slider / float(RANGE) return joint['min'] + (joint['max'] - joint['min']) * pctvalue
def _startButtonClicked(self): self.logger.log("Attempting to start the processing", type="INFO") if not self.b_serialConnected: self.logger.log( "Serial is not connected, Please connect serial first", type="ERROR") return if self.inputLineEdit.text()[-4:].lower() != ".csv": self.logger.log("Please select a valid input csv file", type="ERROR") return if self.outputFolderLineEdit.text() == "": self.logger.log("Please select an output directory", type="ERROR") return self.executer = Executer(serialObj=self.port, loggerObj=self.logger) if self.modelLineEdit.text() != "": modelPath = self.modelLineEdit.text() else: modelPath = None if self._modelRPiPath: self.logger.log( "Please select a valid model that is already available in the folder saved_models on the RPi", type="ERROR") return #Read the Input File try: inputDataFrame = pd.read_csv(self.inputLineEdit.text()) except: self.logger.log("CSV File Reading Failed, select a valid csv file", type="ERROR") possibleInputs = list(inputDataFrame.columns) #Display a dialog to ask the user to choose what inputs they want dialog = QDialog(self) dialog.setWindowTitle("Select the Input Fields") dialogButtons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0) dialogButtons.accepted.connect(dialog.accept) dialogButtons.rejected.connect(dialog.reject) mainLayout = QVBoxLayout(dialog) scroll = QScrollArea(dialog) scroll.setWidgetResizable(True) layoutWidget = QWidget() layout = QVBoxLayout(layoutWidget) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(layoutWidget) chosenInputs = [] checkboxes = [] def handleCheckboxClicked(): dialogButtons.button(QDialogButtonBox.Ok).setDisabled(1) for checkbox in checkboxes: if checkbox.isChecked(): dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0) for input in possibleInputs: checkbox = QCheckBox(text=input) checkbox.clicked.connect(handleCheckboxClicked) checkbox.setChecked(True) checkboxes.append(checkbox) layout.addWidget(checkbox) mainLayout.addWidget( QLabel(text="Please select the input fields from the following:")) mainLayout.addWidget(scroll) mainLayout.addWidget(dialogButtons) dialog.setLayout(mainLayout) # dialog.setFixedHeight(400) if dialog.exec_() == QDialog.Accepted: for checkbox in checkboxes: if checkbox.isChecked(): chosenInputs.append(checkbox.text()) self.logger.log("The chosen input fields are: " + ', '.join(chosenInputs), type="INFO") else: return self.startStopButton.setText("Stop Processing") self.b_processRunning = True executionResult = self.executer.execute(self.labNameComboBox.currentText().split(":")[0], inputDataFrame, \ self.outputFolderLineEdit.text(), inputFields=chosenInputs, progressBar=self.progressBar, \ model=modelPath if not self._modelRPiPath else "RPI:"+modelPath) if executionResult == ExecutionResult.COMPLETED: self._stopButtonClicked(finishedProcessing=True) elif executionResult == ExecutionResult.INTERRUPTED or executionResult == ExecutionResult.FAILED: self._stopButtonClicked() if self.executer.reset() == ExecutionResult.FAILED: self.logger.log( "Resetting the serial state of RPi Failed, please power cycle the RPi", type="ERROR") else: self.logger.log("The serial state of RPi has been reset", type="INFO")
class TallyDock(PlotterDock): def __init__(self, model, font_metric, parent=None): super().__init__(model, font_metric, parent) self.setAllowedAreas(QtCore.Qt.RightDockWidgetArea) # Dock maps for tally information self.tally_map = {} self.filter_map = {} self.score_map = {} self.nuclide_map = {} # Tally selector self.tallySelectorLayout = QFormLayout() self.tallySelector = QComboBox(self) self.tallySelector.currentTextChanged[str].connect( self.main_window.editSelectedTally) self.tallySelectorLayout.addRow(self.tallySelector) self.tallySelectorLayout.setLabelAlignment(QtCore.Qt.AlignLeft) self.tallySelectorLayout.setFieldGrowthPolicy( QFormLayout.AllNonFixedFieldsGrow) # Add selector to its own box self.tallyGroupBox = QGroupBox('Selected Tally') self.tallyGroupBox.setLayout(self.tallySelectorLayout) # Create submit button self.applyButton = QPushButton("ApplyChanges") self.applyButton.setMinimumHeight(self.font_metric.height() * 1.6) self.applyButton.clicked.connect(self.main_window.applyChanges) # Color options section self.tallyColorForm = ColorForm(self.model, self.main_window, 'tally') self.scoresGroupBox = Expander(title="Scores:") self.scoresListWidget = QListWidget() self.nuclidesListWidget = QListWidget() # Main layout self.dockLayout = QVBoxLayout() self.dockLayout.addWidget(QLabel("Tallies")) self.dockLayout.addWidget(HorizontalLine()) self.dockLayout.addWidget(self.tallyGroupBox) self.dockLayout.addStretch() self.dockLayout.addWidget(HorizontalLine()) self.dockLayout.addWidget(self.tallyColorForm) self.dockLayout.addWidget(HorizontalLine()) self.dockLayout.addWidget(self.applyButton) # Create widget for dock and apply main layout self.scroll = QScrollArea() self.scroll.setWidgetResizable(True) self.widget = QWidget() self.widget.setLayout(self.dockLayout) self.scroll.setWidget(self.widget) self.setWidget(self.scroll) def _createFilterTree(self, spatial_filters): av = self.model.activeView tally = self.model.statepoint.tallies[av.selectedTally] filters = tally.filters # create a tree for the filters self.treeLayout = QVBoxLayout() self.filterTree = QTreeWidget() self.treeLayout.addWidget(self.filterTree) self.treeExpander = Expander("Filters:", layout=self.treeLayout) self.treeExpander.expand() # start with filters expanded header = QTreeWidgetItem(["Filters"]) self.filterTree.setHeaderItem(header) self.filterTree.setItemHidden(header, True) self.filterTree.setColumnCount(1) self.filterTree.itemChanged.connect(self.updateFilters) self.filter_map = {} self.bin_map = {} for tally_filter in filters: filter_label = str(type(tally_filter)).split(".")[-1][:-2] filter_item = QTreeWidgetItem(self.filterTree, (filter_label, )) self.filter_map[tally_filter] = filter_item # make checkable if not spatial_filters: filter_item.setFlags(QtCore.Qt.ItemIsUserCheckable) filter_item.setToolTip( 0, "Only tallies with spatial filters are viewable.") else: filter_item.setFlags(filter_item.flags() | QtCore.Qt.ItemIsTristate | QtCore.Qt.ItemIsUserCheckable) filter_item.setCheckState(0, QtCore.Qt.Unchecked) # all mesh bins are selected by default and not shown in the dock if isinstance(tally_filter, openmc.MeshFilter): filter_item.setCheckState(0, QtCore.Qt.Checked) filter_item.setFlags(QtCore.Qt.ItemIsUserCheckable) filter_item.setToolTip( 0, "All Mesh bins are selected automatically") continue def _bin_sort_val(bin): if isinstance(bin, Iterable) and all( [isinstance(val, float) for val in bin]): return np.sum(bin) else: return bin for bin in sorted(tally_filter.bins, key=_bin_sort_val): item = QTreeWidgetItem(filter_item, [ str(bin), ]) if not spatial_filters: item.setFlags(QtCore.Qt.ItemIsUserCheckable) item.setToolTip( 0, "Only tallies with spatial filters are viewable.") else: item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) item.setCheckState(0, QtCore.Qt.Unchecked) bin = bin if not isinstance(bin, Iterable) else tuple(bin) self.bin_map[tally_filter, bin] = item # start with all filters selected if spatial filters are present if spatial_filters: filter_item.setCheckState(0, QtCore.Qt.Checked) def selectFromModel(self): cv = self.model.currentView self.selectedTally(cv.selectedTally) def selectTally(self, tally_label=None): # using active view to populate tally options live av = self.model.activeView # reset form layout for i in reversed(range(self.tallySelectorLayout.count())): self.tallySelectorLayout.itemAt(i).widget().setParent(None) # always re-add the tally selector to the layout self.tallySelectorLayout.addRow(self.tallySelector) self.tallySelectorLayout.addRow(HorizontalLine()) if tally_label is None or tally_label == "None" or tally_label == "": av.selectedTally = None self.score_map = None self.nuclide_map = None self.filter_map = None av.tallyValue = "Mean" else: # get the tally tally = self.model.statepoint.tallies[av.selectedTally] # populate filters filter_types = {type(f) for f in tally.filters} spatial_filters = bool(filter_types.intersection(_SPATIAL_FILTERS)) if not spatial_filters: self.filter_description = QLabel("(No Spatial Filters)") self.tallySelectorLayout.addRow(self.filter_description) self._createFilterTree(spatial_filters) self.tallySelectorLayout.addRow(self.treeExpander) self.tallySelectorLayout.addRow(HorizontalLine()) # value selection self.tallySelectorLayout.addRow(QLabel("Value:")) self.valueBox = QComboBox(self) self.values = tuple(_TALLY_VALUES.keys()) for value in self.values: self.valueBox.addItem(value) self.tallySelectorLayout.addRow(self.valueBox) self.valueBox.currentTextChanged[str].connect( self.main_window.editTallyValue) self.updateTallyValue() if not spatial_filters: self.valueBox.setEnabled(False) self.valueBox.setToolTip( "Only tallies with spatial filters are viewable.") # scores self.score_map = {} self.scoresListWidget.itemClicked.connect( self.main_window.updateScores) self.score_map.clear() self.scoresListWidget.clear() sorted_scores = sorted(tally.scores) # always put total first if present if 'total' in sorted_scores: idx = sorted_scores.index('total') sorted_scores.insert(0, sorted_scores.pop(idx)) for score in sorted_scores: ql = QListWidgetItem() ql.setText(score.capitalize()) ql.setCheckState(QtCore.Qt.Unchecked) if not spatial_filters: ql.setFlags(QtCore.Qt.ItemIsUserCheckable) else: ql.setFlags(ql.flags() | QtCore.Qt.ItemIsUserCheckable) ql.setFlags(ql.flags() & ~QtCore.Qt.ItemIsSelectable) self.score_map[score] = ql self.scoresListWidget.addItem(ql) # select the first score item by default for item in self.score_map.values(): item.setCheckState(QtCore.Qt.Checked) break self.updateScores() self.scoresGroupBoxLayout = QVBoxLayout() self.scoresGroupBoxLayout.addWidget(self.scoresListWidget) self.scoresGroupBox = Expander("Scores:", layout=self.scoresGroupBoxLayout) self.tallySelectorLayout.addRow(self.scoresGroupBox) # nuclides self.nuclide_map = {} self.nuclidesListWidget.itemClicked.connect( self.main_window.updateNuclides) self.nuclide_map.clear() self.nuclidesListWidget.clear() sorted_nuclides = sorted(tally.nuclides) # always put total at the top if 'total' in sorted_nuclides: idx = sorted_nuclides.index('total') sorted_nuclides.insert(0, sorted_nuclides.pop(idx)) for nuclide in sorted_nuclides: ql = QListWidgetItem() ql.setText(nuclide.capitalize()) ql.setCheckState(QtCore.Qt.Unchecked) if not spatial_filters: ql.setFlags(QtCore.Qt.ItemIsUserCheckable) else: ql.setFlags(ql.flags() | QtCore.Qt.ItemIsUserCheckable) ql.setFlags(ql.flags() & ~QtCore.Qt.ItemIsSelectable) self.nuclide_map[nuclide] = ql self.nuclidesListWidget.addItem(ql) # select the first nuclide item by default for item in self.nuclide_map.values(): item.setCheckState(QtCore.Qt.Checked) break self.updateNuclides() self.nuclidesGroupBoxLayout = QVBoxLayout() self.nuclidesGroupBoxLayout.addWidget(self.nuclidesListWidget) self.nuclidesGroupBox = Expander( "Nuclides:", layout=self.nuclidesGroupBoxLayout) self.tallySelectorLayout.addRow(self.nuclidesGroupBox) def updateMinMax(self): self.tallyColorForm.updateMinMax() def updateTallyValue(self): cv = self.model.currentView idx = self.valueBox.findText(cv.tallyValue) self.valueBox.setCurrentIndex(idx) def updateSelectedTally(self): cv = self.model.currentView idx = 0 if cv.selectedTally: idx = self.tallySelector.findData(cv.selectedTally) self.tallySelector.setCurrentIndex(idx) def updateFilters(self): applied_filters = defaultdict(tuple) for f, f_item in self.filter_map.items(): if type(f) == openmc.MeshFilter: continue filter_checked = f_item.checkState(0) if filter_checked != QtCore.Qt.Unchecked: selected_bins = [] for idx, b in enumerate(f.bins): b = b if not isinstance(b, Iterable) else tuple(b) bin_checked = self.bin_map[(f, b)].checkState(0) if bin_checked == QtCore.Qt.Checked: selected_bins.append(idx) applied_filters[f] = tuple(selected_bins) self.model.appliedFilters = applied_filters def updateScores(self): applied_scores = [] for score, score_box in self.score_map.items(): if score_box.checkState() == QtCore.Qt.CheckState.Checked: applied_scores.append(score) self.model.appliedScores = tuple(applied_scores) if not applied_scores: # if no scores are selected, enable all scores again for score, score_box in self.score_map.items(): sunits = _SCORE_UNITS.get(score, _REACTION_UNITS) empty_item = QListWidgetItem() score_box.setFlags(empty_item.flags() | QtCore.Qt.ItemIsUserCheckable) score_box.setFlags(empty_item.flags() & ~QtCore.Qt.ItemIsSelectable) elif 'total' in applied_scores: self.model.appliedScores = ('total', ) # if total is selected, disable all other scores for score, score_box in self.score_map.items(): if score != 'total': score_box.setFlags(QtCore.Qt.ItemIsUserCheckable) score_box.setToolTip( "De-select 'total' to enable other scores") else: # get units of applied scores selected_units = _SCORE_UNITS.get(applied_scores[0], _REACTION_UNITS) # disable scores with incompatible units for score, score_box in self.score_map.items(): sunits = _SCORE_UNITS.get(score, _REACTION_UNITS) if sunits != selected_units: score_box.setFlags(QtCore.Qt.ItemIsUserCheckable) score_box.setToolTip( "Score is incompatible with currently selected scores") else: score_box.setFlags(score_box.flags() | QtCore.Qt.ItemIsUserCheckable) score_box.setFlags(score_box.flags() & ~QtCore.Qt.ItemIsSelectable) def updateNuclides(self): applied_nuclides = [] for nuclide, nuclide_box in self.nuclide_map.items(): if nuclide_box.checkState() == QtCore.Qt.CheckState.Checked: applied_nuclides.append(nuclide) self.model.appliedNuclides = tuple(applied_nuclides) if 'total' in applied_nuclides: self.model.appliedNuclides = [ 'total', ] for nuclide, nuclide_box in self.nuclide_map.items(): if nuclide != 'total': nuclide_box.setFlags(QtCore.Qt.ItemIsUserCheckable) nuclide_box.setToolTip( "De-select 'total' to enable other nuclides") elif not applied_nuclides: # if no nuclides are selected, enable all nuclides again for nuclide, nuclide_box in self.nuclide_map.items(): empty_item = QListWidgetItem() nuclide_box.setFlags(empty_item.flags() | QtCore.Qt.ItemIsUserCheckable) nuclide_box.setFlags(empty_item.flags() & ~QtCore.Qt.ItemIsSelectable) def update(self): # update the color form self.tallyColorForm.update() if self.model.statepoint: self.tallySelector.clear() self.tallySelector.setEnabled(True) self.tallySelector.addItem("None") for idx, tally in enumerate( self.model.statepoint.tallies.values()): if tally.name == "": self.tallySelector.addItem('Tally {}'.format(tally.id), userData=tally.id) else: self.tallySelector.addItem('Tally {} "{}"'.format( tally.id, tally.name), userData=tally.id) self.tally_map[idx] = tally self.updateSelectedTally() self.updateMinMax() else: self.tallySelector.clear() self.tallySelector.setDisabled(True)