def _make_dock(name, widgets=[], tab_with=None): dock = QDockWidget(name) dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) dock_widget = QWidget() layout = QVBoxLayout() for widget in widgets: layout.addWidget(widget) dock_widget.setLayout(layout) dock.setWidget(dock_widget) key = f"hide {name.lower()} dock" if key in prefs and prefs[key]: dock.hide() self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) if tab_with is not None: self.tabifyDockWidget(tab_with, dock) return layout
def createDockWindows(self): dock = QDockWidget("Customers", self) dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.customerList = QListWidget(dock) self.customerList.addItems( ("John Doe, Harmony Enterprises, 12 Lakeside, Ambleton", "Jane Doe, Memorabilia, 23 Watersedge, Beaton", "Tammy Shea, Tiblanka, 38 Sea Views, Carlton", "Tim Sheen, Caraba Gifts, 48 Ocean Way, Deal", "Sol Harvey, Chicos Coffee, 53 New Springs, Eccleston", "Sally Hobart, Tiroli Tea, 67 Long River, Fedula")) dock.setWidget(self.customerList) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) dock = QDockWidget("Paragraphs", self) self.paragraphsList = QListWidget(dock) self.paragraphsList.addItems( ("Thank you for your payment which we have received today.", "Your order has been dispatched and should be with you within " "28 days.", "We have dispatched those items that were in stock. The rest of " "your order will be dispatched once all the remaining items " "have arrived at our warehouse. No additional shipping " "charges will be made.", "You made a small overpayment (less than $5) which we will keep " "on account for you, or return at your request.", "You made a small underpayment (less than $1), but we have sent " "your order anyway. We'll add this underpayment to your next " "bill.", "Unfortunately you did not send enough money. Please remit an " "additional $. Your order will be dispatched as soon as the " "complete amount has been received.", "You made an overpayment (more than $5). Do you wish to buy more " "items, or should we return the excess to you?")) dock.setWidget(self.paragraphsList) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) self.customerList.currentTextChanged.connect(self.insertCustomer) self.paragraphsList.currentTextChanged.connect(self.addParagraph)
def createDockWindows(self): # Info dock = QDockWidget('Details', self) dock.setObjectName("DETAILS") dock.setAllowedAreas(Qt.AllDockWidgetAreas) self.detailsWidget = InfoPanelWidget(self) self.project_widget.treeCurrentItemChanged.connect( self.detailsWidget.setItem) self.project_widget.treeCurrentItemChanged.connect( self.currentItemChanged) dock.setWidget(self.detailsWidget) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction())
def create_dock_widget(self): """ Sets up a dock widget for Markdown hot previewing, hidden by default. :return: None """ dock = QDockWidget("Markdown Viewer", self) dock.setWidget(self.md_text_edit) dock.visibilityChanged.connect( lambda visible: visible and self.update_markdown_viewer( )) # precludes slowdowns self.addDockWidget(Qt.BottomDockWidgetArea, dock) dock_act = dock.toggleViewAction() dock_act.setShortcuts([ QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_M), QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_C) ]) self.view_menu.addSeparator() # Does nothing on Mac self.view_menu.addAction(dock_act) dock.close()
def __init__(self, app): super(MainWindow, self).__init__() self._app = app self._selectedIndex = None # model nodeFactory = NodeFactory() rootNode = nodeFactory.create(NodeType.General, 'Root') # for i in range(10000): # for testing childNode0 = nodeFactory.create(NodeType.General, 'RightPirateLeg', rootNode) childNode1 = nodeFactory.create(NodeType.General, 'RightPirateLeg_END', childNode0) childNode2 = nodeFactory.create(NodeType.General, 'LeftFemur', rootNode) childNode3 = nodeFactory.create(NodeType.Sphere, 'LeftTibia', childNode2) childNode4 = nodeFactory.create(NodeType.Sphere, 'LeftFoot', childNode3) transform = childNode4.component(ComponentType.Transform) qTransform = transform.component() translation = qTransform.translation() translation.setX(5) qTransform.setTranslation(translation) # childNode5 = nodeFactory.create(NodeType.Box, 'LeftFoot_END', childNode4) self._model = SceneGraphModel(rootNode) self._sceneView = SceneView(rootNode.entity()) self._container = self.createWindowContainer(self._sceneView) # scene graph view self._treeView = QTreeView() self._treeView.setModel(self._model) self._treeView.setHeaderHidden(True) self._treeView.setAlternatingRowColors(True) dockWidget = QDockWidget() dockWidget.setWidget(self._treeView) dockWidget.setWindowTitle('Scene Graph') dockWidget.setObjectName('sceneGraph') sceneGraphToggleAction = dockWidget.toggleViewAction() self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget) # property editor propertyEditor = PropertyEditor(self._model, nodeFactory, FieldFactory()) dockWidget = QDockWidget() dockWidget.setWidget(propertyEditor) dockWidget.setWindowTitle('Property Editor') dockWidget.setObjectName('propertyEditor') propertyEditorToggleAction = dockWidget.toggleViewAction() self.addDockWidget(Qt.RightDockWidgetArea, dockWidget) # menu menuBar = self.menuBar() menu = menuBar.addMenu('&File') exitAction = menu.addAction('E&xit') exitAction.triggered.connect(self.close) menu.addAction(exitAction) menu = menuBar.addMenu('&Windows') menu.addAction(sceneGraphToggleAction) menu.addAction(propertyEditorToggleAction) menuBar.addMenu(menu) # central widget #button = QPushButton() self.setCentralWidget(self._container) # selection change event selectionModel = self._treeView.selectionModel() selectionModel.currentChanged.connect(propertyEditor.changeSelection)
def __init__(self, parent=None): super(MainWindow, self).__init__(parent) QApplication.setApplicationName('Sherloq') QApplication.setOrganizationName('Guido Bartoli') QApplication.setOrganizationDomain('www.guidobartoli.com') QApplication.setApplicationVersion(ToolTree().version) QApplication.setWindowIcon(QIcon('icons/sherloq_white.png')) self.setWindowTitle('{} {}'.format(QApplication.applicationName(), QApplication.applicationVersion())) self.mdi_area = QMdiArea() self.setCentralWidget(self.mdi_area) self.filename = None self.image = None modify_font(self.statusBar(), bold=True) tree_dock = QDockWidget(self.tr('TOOLS'), self) tree_dock.setObjectName('tree_dock') tree_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.LeftDockWidgetArea, tree_dock) self.tree_widget = ToolTree() self.tree_widget.setObjectName('tree_widget') self.tree_widget.itemDoubleClicked.connect(self.open_tool) tree_dock.setWidget(self.tree_widget) tools_action = tree_dock.toggleViewAction() tools_action.setText(self.tr('Show tools')) tools_action.setToolTip(self.tr('Toggle toolset visibility')) tools_action.setShortcut(QKeySequence(Qt.Key_Tab)) tools_action.setObjectName('tools_action') tools_action.setIcon(QIcon('icons/tools.svg')) help_action = QAction(self.tr('Show help'), self) help_action.setToolTip(self.tr('Toggle online help')) help_action.setShortcut(QKeySequence.HelpContents) help_action.setObjectName('help_action') help_action.setIcon(QIcon('icons/help.svg')) help_action.setCheckable(True) help_action.setEnabled(False) load_action = QAction(self.tr('&Load image...'), self) load_action.setToolTip(self.tr('Load an image to analyze')) load_action.setShortcut(QKeySequence.Open) load_action.triggered.connect(self.load_file) load_action.setObjectName('load_action') load_action.setIcon(QIcon('icons/load.svg')) quit_action = QAction(self.tr('&Quit'), self) quit_action.setToolTip(self.tr('Exit from Sherloq')) quit_action.setShortcut(QKeySequence.Quit) quit_action.triggered.connect(self.close) quit_action.setObjectName('quit_action') quit_action.setIcon(QIcon('icons/quit.svg')) tabbed_action = QAction(self.tr('&Tabbed'), self) tabbed_action.setToolTip(self.tr('Toggle tabbed view for window area')) tabbed_action.setShortcut(QKeySequence(Qt.Key_F10)) tabbed_action.setCheckable(True) tabbed_action.triggered.connect(self.toggle_view) tabbed_action.setObjectName('tabbed_action') tabbed_action.setIcon(QIcon('icons/tabbed.svg')) prev_action = QAction(self.tr('&Previous'), self) prev_action.setToolTip(self.tr('Select the previous tool window')) prev_action.setShortcut(QKeySequence.PreviousChild) prev_action.triggered.connect(self.mdi_area.activatePreviousSubWindow) prev_action.setObjectName('prev_action') prev_action.setIcon(QIcon('icons/previous.svg')) next_action = QAction(self.tr('&Next'), self) next_action.setToolTip(self.tr('Select the next tool window')) next_action.setShortcut(QKeySequence.NextChild) next_action.triggered.connect(self.mdi_area.activateNextSubWindow) next_action.setObjectName('next_action') next_action.setIcon(QIcon('icons/next.svg')) tile_action = QAction(self.tr('&Tile'), self) tile_action.setToolTip( self.tr('Arrange windows into non-overlapping views')) tile_action.setShortcut(QKeySequence(Qt.Key_F11)) tile_action.triggered.connect(self.mdi_area.tileSubWindows) tile_action.setObjectName('tile_action') tile_action.setIcon(QIcon('icons/tile.svg')) cascade_action = QAction(self.tr('&Cascade'), self) cascade_action.setToolTip( self.tr('Arrange windows into overlapping views')) cascade_action.setShortcut(QKeySequence(Qt.Key_F12)) cascade_action.triggered.connect(self.mdi_area.cascadeSubWindows) cascade_action.setObjectName('cascade_action') cascade_action.setIcon(QIcon('icons/cascade.svg')) close_action = QAction(self.tr('Close &All'), self) close_action.setToolTip(self.tr('Close all open tool windows')) close_action.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_W)) close_action.triggered.connect(self.mdi_area.closeAllSubWindows) close_action.setObjectName('close_action') close_action.setIcon(QIcon('icons/close.svg')) self.full_action = QAction(self.tr('Full screen'), self) self.full_action.setToolTip(self.tr('Switch to full screen mode')) self.full_action.setShortcut(QKeySequence.FullScreen) self.full_action.triggered.connect(self.change_view) self.full_action.setObjectName('full_action') self.full_action.setIcon(QIcon('icons/full.svg')) self.normal_action = QAction(self.tr('Normal view'), self) self.normal_action.setToolTip(self.tr('Back to normal view mode')) self.normal_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F12)) self.normal_action.triggered.connect(self.change_view) self.normal_action.setObjectName('normal_action') self.normal_action.setIcon(QIcon('icons/normal.svg')) about_action = QAction(self.tr('&About...'), self) about_action.setToolTip(self.tr('Information about this program')) about_action.triggered.connect(self.show_about) about_action.setObjectName('about_action') about_action.setIcon(QIcon('icons/sherloq_alpha.png')) about_qt_action = QAction(self.tr('About &Qt'), self) about_qt_action.setToolTip( self.tr('Information about the Qt Framework')) about_qt_action.triggered.connect(QApplication.aboutQt) about_qt_action.setIcon(QIcon('icons/Qt.svg')) file_menu = self.menuBar().addMenu(self.tr('&File')) file_menu.addAction(load_action) file_menu.addSeparator() self.recent_actions = [None] * self.max_recent for i in range(len(self.recent_actions)): self.recent_actions[i] = QAction(self) self.recent_actions[i].setVisible(False) self.recent_actions[i].triggered.connect(self.open_recent) file_menu.addAction(self.recent_actions[i]) file_menu.addSeparator() file_menu.addAction(quit_action) view_menu = self.menuBar().addMenu(self.tr('&View')) view_menu.addAction(tools_action) view_menu.addAction(help_action) view_menu.addSeparator() view_menu.addAction(self.full_action) view_menu.addAction(self.normal_action) window_menu = self.menuBar().addMenu(self.tr('&Window')) window_menu.addAction(prev_action) window_menu.addAction(next_action) window_menu.addSeparator() window_menu.addAction(tile_action) window_menu.addAction(cascade_action) window_menu.addAction(tabbed_action) window_menu.addSeparator() window_menu.addAction(close_action) help_menu = self.menuBar().addMenu(self.tr('&Help')) help_menu.addAction(help_action) help_menu.addSeparator() help_menu.addAction(about_action) help_menu.addAction(about_qt_action) main_toolbar = self.addToolBar(self.tr('&Toolbar')) main_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) main_toolbar.addAction(load_action) main_toolbar.addSeparator() main_toolbar.addAction(tools_action) main_toolbar.addAction(help_action) main_toolbar.addSeparator() main_toolbar.addAction(prev_action) main_toolbar.addAction(next_action) main_toolbar.addSeparator() main_toolbar.addAction(tile_action) main_toolbar.addAction(cascade_action) main_toolbar.addAction(tabbed_action) main_toolbar.addAction(close_action) # main_toolbar.addSeparator() # main_toolbar.addAction(self.normal_action) # main_toolbar.addAction(self.full_action) main_toolbar.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea) main_toolbar.setObjectName('main_toolbar') settings = QSettings() settings.beginGroup('main_window') self.restoreGeometry(settings.value('geometry')) self.restoreState(settings.value('state')) self.recent_files = settings.value('recent_files') if self.recent_files is None: self.recent_files = [] elif not isinstance(self.recent_files, list): self.recent_files = [self.recent_files] self.update_recent() settings.endGroup() prev_action.setEnabled(False) next_action.setEnabled(False) tile_action.setEnabled(False) cascade_action.setEnabled(False) close_action.setEnabled(False) tabbed_action.setEnabled(False) self.tree_widget.setEnabled(False) self.showNormal() self.normal_action.setEnabled(False) self.show_message(self.tr('Ready'))
class MainWindow(QMainWindow): """Provides the parent window that includes the BookmarkWidget, BrowserTabWidget, and a DownloadWidget, to offer the complete web browsing experience.""" def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle('PySide2 tabbed browser Example') self._tab_widget = BrowserTabWidget(create_main_window_with_browser) self._tab_widget.enabled_changed.connect(self._enabled_changed) self._tab_widget.download_requested.connect(self._download_requested) self.setCentralWidget(self._tab_widget) self.connect(self._tab_widget, QtCore.SIGNAL("url_changed(QUrl)"), self.url_changed) self._bookmark_dock = QDockWidget() self._bookmark_dock.setWindowTitle('Bookmarks') self._bookmark_widget = BookmarkWidget() self._bookmark_widget.open_bookmark.connect(self.load_url) self._bookmark_widget.open_bookmark_in_new_tab.connect( self.load_url_in_new_tab) self._bookmark_dock.setWidget(self._bookmark_widget) self.addDockWidget(Qt.LeftDockWidgetArea, self._bookmark_dock) self._find_tool_bar = None self._actions = {} self._create_menu() self._tool_bar = QToolBar() self.addToolBar(self._tool_bar) for action in self._actions.values(): if not action.icon().isNull(): self._tool_bar.addAction(action) self._addres_line_edit = QLineEdit() self._addres_line_edit.setClearButtonEnabled(True) self._addres_line_edit.returnPressed.connect(self.load) self._tool_bar.addWidget(self._addres_line_edit) self._zoom_label = QLabel() self.statusBar().addPermanentWidget(self._zoom_label) self._update_zoom_label() self._bookmarksToolBar = QToolBar() self.addToolBar(Qt.TopToolBarArea, self._bookmarksToolBar) self.insertToolBarBreak(self._bookmarksToolBar) self._bookmark_widget.changed.connect(self._update_bookmarks) self._update_bookmarks() def _update_bookmarks(self): self._bookmark_widget.populate_tool_bar(self._bookmarksToolBar) self._bookmark_widget.populate_other(self._bookmark_menu, 3) def _create_menu(self): file_menu = self.menuBar().addMenu("&File") exit_action = QAction(QIcon.fromTheme("application-exit"), "E&xit", self, shortcut="Ctrl+Q", triggered=qApp.quit) file_menu.addAction(exit_action) navigation_menu = self.menuBar().addMenu("&Navigation") style_icons = ':/qt-project.org/styles/commonstyle/images/' back_action = QAction(QIcon.fromTheme( "go-previous", QIcon(style_icons + 'left-32.png')), "Back", self, shortcut=QKeySequence(QKeySequence.Back), triggered=self._tab_widget.back) self._actions[QWebEnginePage.Back] = back_action back_action.setEnabled(False) navigation_menu.addAction(back_action) forward_action = QAction(QIcon.fromTheme( "go-next", QIcon(style_icons + 'right-32.png')), "Forward", self, shortcut=QKeySequence(QKeySequence.Forward), triggered=self._tab_widget.forward) forward_action.setEnabled(False) self._actions[QWebEnginePage.Forward] = forward_action navigation_menu.addAction(forward_action) reload_action = QAction(QIcon(style_icons + 'refresh-32.png'), "Reload", self, shortcut=QKeySequence(QKeySequence.Refresh), triggered=self._tab_widget.reload) self._actions[QWebEnginePage.Reload] = reload_action reload_action.setEnabled(False) navigation_menu.addAction(reload_action) navigation_menu.addSeparator() new_tab_action = QAction("New Tab", self, shortcut='Ctrl+T', triggered=self.add_browser_tab) navigation_menu.addAction(new_tab_action) close_tab_action = QAction("Close Current Tab", self, shortcut="Ctrl+W", triggered=self._close_current_tab) navigation_menu.addAction(close_tab_action) edit_menu = self.menuBar().addMenu("&Edit") find_action = QAction("Find", self, shortcut=QKeySequence(QKeySequence.Find), triggered=self._show_find) edit_menu.addAction(find_action) edit_menu.addSeparator() undo_action = QAction("Undo", self, shortcut=QKeySequence(QKeySequence.Undo), triggered=self._tab_widget.undo) self._actions[QWebEnginePage.Undo] = undo_action undo_action.setEnabled(False) edit_menu.addAction(undo_action) redo_action = QAction("Redo", self, shortcut=QKeySequence(QKeySequence.Redo), triggered=self._tab_widget.redo) self._actions[QWebEnginePage.Redo] = redo_action redo_action.setEnabled(False) edit_menu.addAction(redo_action) edit_menu.addSeparator() cut_action = QAction("Cut", self, shortcut=QKeySequence(QKeySequence.Cut), triggered=self._tab_widget.cut) self._actions[QWebEnginePage.Cut] = cut_action cut_action.setEnabled(False) edit_menu.addAction(cut_action) copy_action = QAction("Copy", self, shortcut=QKeySequence(QKeySequence.Copy), triggered=self._tab_widget.copy) self._actions[QWebEnginePage.Copy] = copy_action copy_action.setEnabled(False) edit_menu.addAction(copy_action) paste_action = QAction("Paste", self, shortcut=QKeySequence(QKeySequence.Paste), triggered=self._tab_widget.paste) self._actions[QWebEnginePage.Paste] = paste_action paste_action.setEnabled(False) edit_menu.addAction(paste_action) edit_menu.addSeparator() select_all_action = QAction("Select All", self, shortcut=QKeySequence( QKeySequence.SelectAll), triggered=self._tab_widget.select_all) self._actions[QWebEnginePage.SelectAll] = select_all_action select_all_action.setEnabled(False) edit_menu.addAction(select_all_action) self._bookmark_menu = self.menuBar().addMenu("&Bookmarks") add_bookmark_action = QAction("&Add Bookmark", self, triggered=self._add_bookmark) self._bookmark_menu.addAction(add_bookmark_action) add_tool_bar_bookmark_action = QAction( "&Add Bookmark to Tool Bar", self, triggered=self._add_tool_bar_bookmark) self._bookmark_menu.addAction(add_tool_bar_bookmark_action) self._bookmark_menu.addSeparator() tools_menu = self.menuBar().addMenu("&Tools") download_action = QAction( "Open Downloads", self, triggered=DownloadWidget.open_download_directory) tools_menu.addAction(download_action) window_menu = self.menuBar().addMenu("&Window") window_menu.addAction(self._bookmark_dock.toggleViewAction()) window_menu.addSeparator() zoom_in_action = QAction(QIcon.fromTheme("zoom-in"), "Zoom In", self, shortcut=QKeySequence(QKeySequence.ZoomIn), triggered=self._zoom_in) window_menu.addAction(zoom_in_action) zoom_out_action = QAction(QIcon.fromTheme("zoom-out"), "Zoom Out", self, shortcut=QKeySequence(QKeySequence.ZoomOut), triggered=self._zoom_out) window_menu.addAction(zoom_out_action) reset_zoom_action = QAction(QIcon.fromTheme("zoom-original"), "Reset Zoom", self, shortcut="Ctrl+0", triggered=self._reset_zoom) window_menu.addAction(reset_zoom_action) about_menu = self.menuBar().addMenu("&About") about_action = QAction("About Qt", self, shortcut=QKeySequence( QKeySequence.HelpContents), triggered=qApp.aboutQt) about_menu.addAction(about_action) def add_browser_tab(self): return self._tab_widget.add_browser_tab() def _close_current_tab(self): if self._tab_widget.count() > 1: self._tab_widget.close_current_tab() else: self.close() def close_event(self, event): main_windows.remove(self) event.accept() def load(self): url_string = self._addres_line_edit.text().strip() if url_string: self.load_url_string(url_string) def load_url_string(self, url_s): url = QUrl.fromUserInput(url_s) if (url.isValid()): self.load_url(url) def load_url(self, url): self._tab_widget.load(url) def load_url_in_new_tab(self, url): self.add_browser_tab().load(url) def url_changed(self, url): self._addres_line_edit.setText(url.toString()) def _enabled_changed(self, web_action, enabled): action = self._actions[web_action] if action: action.setEnabled(enabled) def _add_bookmark(self): index = self._tab_widget.currentIndex() if index >= 0: url = self._tab_widget.url() title = self._tab_widget.tabText(index) icon = self._tab_widget.tabIcon(index) self._bookmark_widget.add_bookmark(url, title, icon) def _add_tool_bar_bookmark(self): index = self._tab_widget.currentIndex() if index >= 0: url = self._tab_widget.url() title = self._tab_widget.tabText(index) icon = self._tab_widget.tabIcon(index) self._bookmark_widget.add_tool_bar_bookmark(url, title, icon) def _zoom_in(self): new_zoom = self._tab_widget.zoom_factor() * 1.5 if (new_zoom <= WebEngineView.maximum_zoom_factor()): self._tab_widget.set_zoom_factor(new_zoom) self._update_zoom_label() def _zoom_out(self): new_zoom = self._tab_widget.zoom_factor() / 1.5 if (new_zoom >= WebEngineView.minimum_zoom_factor()): self._tab_widget.set_zoom_factor(new_zoom) self._update_zoom_label() def _reset_zoom(self): self._tab_widget.set_zoom_factor(1) self._update_zoom_label() def _update_zoom_label(self): percent = int(self._tab_widget.zoom_factor() * 100) self._zoom_label.setText("{}%".format(percent)) def _download_requested(self, item): # Remove old downloads before opening a new one for old_download in self.statusBar().children(): if type(old_download).__name__ == 'download_widget' and \ old_download.state() != QWebEngineDownloadItem.DownloadInProgress: self.statusBar().removeWidget(old_download) del old_download item.accept() download_widget = download_widget(item) download_widget.removeRequested.connect( self._remove_download_requested, Qt.QueuedConnection) self.statusBar().addWidget(download_widget) def _remove_download_requested(self): download_widget = self.sender() self.statusBar().removeWidget(download_widget) del download_widget def _show_find(self): if self._find_tool_bar is None: self._find_tool_bar = FindToolBar() self._find_tool_bar.find.connect(self._tab_widget.find) self.addToolBar(Qt.BottomToolBarArea, self._find_tool_bar) else: self._find_tool_bar.show() self._find_tool_bar.focus_find() def write_bookmarks(self): self._bookmark_widget.write_bookmarks()
class PhotoEditor(QMainWindow): #Ok def __init__(self): super().__init__() self.iniciaUI() def iniciaUI(self): """ Inicializa a janela e mostra seu conteuda na tela """ self.setFixedSize(650, 650) #self.setGeometry(100,100, 400, 230) self.setWindowTitle("Photo Editor") self.centerMainWindow() self.createToolsDockWidget() self.createMenu() self.createToolBar() self.photoEditorWidgets() self.show() def centerMainWindow(self): #Ok """ Use a classe QDesktopWidget para acessar informações sobre sua tela e use-a para centralizar a janela do aplicativo. """ desktop = QDesktopWidget().screenGeometry() screen_width = desktop.width() screen_height = desktop.height() self.move((screen_width - self.width()) / 2, (screen_height - self.height()) / 2) def createMenu(self): #Ok """ Criar menu para editor de fotos """ self.abre_act = QAction(QIcon('Imagens/open_file.png'), "Abrir", self) self.abre_act.setShortcut('Ctrl+O') self.abre_act.setStatusTip('Abrir uma nova imagem') self.abre_act.triggered.connect(self.openImage) self.salv_act = QAction(QIcon('Imagens/save_file.png'), 'Salvar', self) self.salv_act.setShortcut('Ctrl+S') self.salv_act.setStatusTip('Salvar imagem') self.salv_act.triggered.connect(self.saveToFile) self.prnt_act = QAction(QIcon('Imagens/print.png'), "Imprimir", self) self.prnt_act.setShortcut('Ctrl+P') self.prnt_act.setStatusTip('Imprimir imagem') self.prnt_act.triggered.connect(self.printImage) self.prnt_act.setEnabled(False) self.sair_act = QAction(QIcon('Imagens/exit.png'), 'Sair', self) self.sair_act.setShortcut('Ctrl+Q') self.sair_act.setStatusTip('Sair do programa') self.sair_act.triggered.connect(self.close) self.rt90_act = QAction("Girar 90°", self) self.rt90_act.setStatusTip('Girar imagem 90 ° no sentido horário') self.rt90_act.triggered.connect(self.rotateImage90) self.rt180_act = QAction("Girar 180°", self) self.rt180_act.setStatusTip('Girar imagem 180° no sentido horário') self.rt180_act.triggered.connect(self.rotateImage180) self.flph_act = QAction("Espelhar na Horizontal", self) self.flph_act.setStatusTip('Espelhar imagem no eixo horizontal') self.flph_act.triggered.connect(self.flipImageHorizontal) self.flpv_act = QAction("Espelhar na Vertical", self) self.flpv_act.setStatusTip('Espelhar imagem no eixo vertical') self.flpv_act.triggered.connect(self.flipImageVertical) self.redm_act = QAction("Redimensionar metade", self) self.redm_act.setStatusTip( 'Redimensionar imagem para metade do tamanho original') self.redm_act.triggered.connect(self.resizeImageHalf) self.limp_act = QAction(QIcon('Imagens/clear.png'), "Limpar Imagem", self) self.limp_act.setShortcut("Ctrl+D") self.limp_act.setStatusTip('Limpar a imagem atual') self.limp_act.triggered.connect(self.clearImage) menu_bar = self.menuBar() menu_bar.setNativeMenuBar(False) arqv_menu = menu_bar.addMenu('Arquivo') arqv_menu.addAction(self.abre_act) arqv_menu.addAction(self.salv_act) arqv_menu.addSeparator() arqv_menu.addAction(self.prnt_act) arqv_menu.addSeparator() arqv_menu.addAction(self.sair_act) edit_menu = menu_bar.addMenu('Editar') edit_menu.addAction(self.rt90_act) edit_menu.addAction(self.rt180_act) edit_menu.addSeparator() edit_menu.addAction(self.flph_act) edit_menu.addAction(self.flpv_act) edit_menu.addSeparator() edit_menu.addAction(self.redm_act) edit_menu.addSeparator() edit_menu.addAction(self.limp_act) #??????????????????? view_menu = menu_bar.addMenu('Exibir') view_menu.addAction(self.toggle_dock_tools_act) self.setStatusBar(QStatusBar(self)) def openImage(self): #Ok """ Abrir um arquivo de imagem e exiba seu conteúdo no label. Exibir mensagem de erro se a imagem não puder ser aberta. """ arq_img, _ = QFileDialog.getOpenFileName( self, "Abrir Imagem", "", "Arquivos JPG (*.jpeg *.jpg );;Arquivos PNG (*.png)\ ;;Arquivos Bitmap (*.bmp);;Arquivos GIF (*.gif)") if arq_img: self.img = QPixmap(arq_img) self.img_lbl.setPixmap( self.img.scaled(self.img_lbl.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) else: QMessageBox.information(self, "Erro", "Não é possível abrir imagem.", QMessageBox.Ok) self.prnt_act.setEnabled(True) def saveToFile(self): #Ok """ Salvar a imagem. Exibir mensagem de erro se a imagem não puder ser salva. """ arq_img, _ = QFileDialog.getSaveFileName( self, "Salvar Imagem", "", "Arquivos JPG (*.jpeg *.jpg );;Arquivos PNG (*.png)\ ;;Arquivos Bitmap (*.bmp);;Arquivos GIF (*.gif)") if arq_img and self.img.isNull() == False: self.img.save(image_file) else: QMessageBox.information(self, "Não é possível salvar imagem.", QMessageBox.Ok) def printImage(self): #Ok """Imprimir Imagem """ printer = QPrinter() printer.setOutputFormat(QPrinter.NativeFormat) prnt_dlg = QPrintDialog(printer) if (prnt_dlg.exec_() == QPrintDialog.Accepted): painter = QPainter() painter.begin(printer) rect = QRect(painter.viewport()) size = QSize(self.img_lbl.pixmap().size()) size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.img_lbl.pixmap().rect()) painter.drawPixmap(0, 0, self.img_lbl.pixmap()) painter.end() def rotateImage90(self): #Ok """ Girar imagem 90° no sentido horário """ if self.img.isNull() == False: transform90 = QTransform().rotate(90) pixmap = QPixmap(self.img) rotated = pixmap.transformed(transform90, mode=Qt.SmoothTransformation) self.img_lbl.setPixmap( rotated.scaled(self.img_lbl.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.img = QPixmap(rotated) self.img_lbl.repaint() # repaint the child widget else: # No image to rotate pass def rotateImage180(self): #Ok """ Girar imagem 180° no sentido horário """ if self.img.isNull() == False: transform180 = QTransform().rotate(180) pixmap = QPixmap(self.img) rotated = pixmap.transformed(transform180, mode=Qt.SmoothTransformation) self.img_lbl.setPixmap( rotated.scaled(self.img_lbl.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.img = QPixmap(rotated) self.img_label.repaint() # repaint the child widget else: # No image to rotate pass def flipImageHorizontal(self): #Ok """ Espelhar a imagem no eixo horizontal """ if self.img.isNull() == False: flip_h = QTransform().scale(-1, 1) pixmap = QPixmap(self.img) flipped = pixmap.transformed(flip_h) self.img_lbl.setPixmap( flipped.scaled(self.img_lbl.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.img = QPixmap(flipped) self.img_lbl.repaint() else: pass def flipImageVertical(self): #Ok """ Espelhar a imagem no eixo vertical """ if self.img.isNull() == False: flip_v = QTransform().scale(1, -1) pixmap = QPixmap(self.img) flipped = pixmap.transformed(flip_v) self.img_lbl.setPixmap( flipped.scaled(self.img_lbl.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.img = QPixmap(flipped) self.img_lbl.repaint() else: pass def resizeImageHalf(self): #Ok """ Redimensione a imagem para a metade do tamanho atual. """ if self.img.isNull() == False: resize = QTransform().scale(0.5, 0.5) pixmap = QPixmap(self.img) resized = pixmap.transformed(resize) self.img_lbl.setPixmap( resized.scaled(self.img_lbl.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.img = QPixmap(resized) self.img_lbl.repaint() else: pass def clearImage(self): #Ok """ Limpar a imagem atual no widget QLabel """ self.img_lbl.clear() self.img = QPixmap() def createToolBar(self): #Ok """ Criar barra de ferramentas para editor de fotos """ tool_bar = QToolBar("Barra de Ferramentas do Editor de Fotos") tool_bar.setIconSize(QSize(24, 24)) self.addToolBar(tool_bar) tool_bar.addAction(self.abre_act) tool_bar.addAction(self.salv_act) tool_bar.addAction(self.prnt_act) tool_bar.addAction(self.limp_act) tool_bar.addSeparator() tool_bar.addAction(self.sair_act) def createToolsDockWidget(self): #Ok """ Use o menu Exibir -> Editar Ferramentas de Imagem e clique no widget dock para ligar ou desligar. O dock de ferramentas pode ser colocado à esquerda ou à direita da janela principal. """ self.dock_tools_view = QDockWidget() self.dock_tools_view.setWindowTitle("Ferramentas Edição de Imagem") self.dock_tools_view.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.img_tool = QWidget() self.rt90_btn = QPushButton("Girar 90°") self.rt90_btn.setMinimumSize(QSize(130, 40)) self.rt90_btn.setStatusTip('Girar imagem 90° no sentido horário') self.rt90_btn.clicked.connect(self.rotateImage90) self.rt180_btn = QPushButton("Girar 180°") self.rt180_btn.setMinimumSize(QSize(130, 40)) self.rt180_btn.setStatusTip('Girar imagem 180° no sentido horário') self.rt180_btn.clicked.connect(self.rotateImage180) self.flph_btn = QPushButton("Espelhar na Horizontal") self.flph_btn.setMinimumSize(QSize(130, 40)) self.flph_btn.setStatusTip('Espelhar imagem no eixo horizontal') self.flph_btn.clicked.connect(self.flipImageHorizontal) self.flpv_btn = QPushButton("Espelhar na Vertical") self.flpv_btn.setMinimumSize(QSize(130, 40)) self.flpv_btn.setStatusTip('Espelhar imagem no eixo vertical') self.flpv_btn.clicked.connect(self.flipImageVertical) self.redm_btn = QPushButton("Redimensionar metade") self.redm_btn.setMinimumSize(QSize(130, 40)) self.redm_btn.setStatusTip( 'Redimensionar imagem para metade do tamanho original') self.redm_btn.clicked.connect(self.resizeImageHalf) vbox = QVBoxLayout() vbox.addWidget(self.rt90_btn) vbox.addWidget(self.rt180_btn) vbox.addStretch(1) vbox.addWidget(self.flph_btn) vbox.addWidget(self.flpv_btn) vbox.addStretch(1) vbox.addWidget(self.redm_btn) vbox.addStretch(6) self.img_tool.setLayout(vbox) self.dock_tools_view.setWidget(self.img_tool) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_tools_view) self.toggle_dock_tools_act = self.dock_tools_view.toggleViewAction() def photoEditorWidgets(self): #Ok """ Configurar instâncias de widgets para editor de fotos """ self.img = QPixmap() self.img_lbl = QLabel() self.img_lbl.setAlignment(Qt.AlignCenter) self.img_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) self.setCentralWidget(self.img_lbl)
def createDockWindows(self): # Scale self.dynamic_scale_dock = QDockWidget("Dynamic Scale", self) self.dynamic_scale_dock.setObjectName("SCALE") self.dynamic_scale_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.scaleWidget = ScaleWidget(self, scales_model=self.scales_model, cmap_model=self.cmaps) self.dynamic_scale_dock.setWidget(self.scaleWidget) self.addDockWidget(Qt.RightDockWidgetArea, self.dynamic_scale_dock) self.viewMenu.addAction(self.dynamic_scale_dock.toggleViewAction()) self.dynamic_scale_dock.setFloating(True) self.dynamic_scale_dock.hide() #radial profiles dock = QDockWidget("Radial Profile Fit", self) dock.setObjectName("RADIAL_PROFILE_IRAF") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.radial_profile_iraf_widget = IRAFRadialProfileWidget( self.fits_image.data) dock.setWidget(self.radial_profile_iraf_widget) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) self.dockRadialFit = dock dock = QDockWidget("Radial Profile Curve", self) dock.setObjectName("RADIAL_PROFILE") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.radial_profile_widget = RadialProfileWidget(self.fits_image.data) dock.setWidget(self.radial_profile_widget) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) dock.hide() #info panel dock = QDockWidget("Info", self) dock.setObjectName("INFO_PANEL") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.info_widget = InfoWidget(self) dock.setWidget(self.info_widget) self.addDockWidget(Qt.TopDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) # FITS headers dock = QDockWidget("FITS header", self) dock.setObjectName("FTIS_DATA") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.addDockWidget(Qt.TopDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) self.headerWidget = HeaderTableWidget(self) self.headerWidget.setColumnCount(2) self.headerWidget.setHorizontalHeaderItem(0, QTableWidgetItem("KEY")) self.headerWidget.setHorizontalHeaderItem(1, QTableWidgetItem("VALUE")) self.headerWidget.horizontalHeader().setStretchLastSection(1) self.headerWidget.setEditTriggers( QtWidgets.QTableWidget.NoEditTriggers) self.headerWidget.clearFocus() dock.setWidget(self.headerWidget) # full dock = QDockWidget("Full view", self) dock.setObjectName("FULL_VIEW") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.full_view_widget = FullViewWidget(self.fits_image) self.full_view_widget.fits_image.set_scale_model(self.scales_model) self.full_view_widget.fits_image.set_cmap_model(self.cmaps) dock.setWidget(self.full_view_widget) self.addDockWidget(Qt.TopDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) # zoom dock = QDockWidget("Zoom view", self) dock.setObjectName("ZOOM_VIEW") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.zoom_view_widget = ZoomViewWidget(self.fits_image) self.zoom_view_widget.fits_image.set_scale_model(self.scales_model) self.zoom_view_widget.fits_image.set_cmap_model(self.cmaps) dock.setWidget(self.zoom_view_widget) self.addDockWidget(Qt.TopDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) # fileSelector dock = QDockWidget("Directory view", self) dock.setObjectName("DIRECTORY_VIEW") dock.setAllowedAreas(Qt.LeftDockWidgetArea) self.file_widget = FileSystemWidget(self) dock.setWidget(self.file_widget) self.addDockWidget(Qt.LeftDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) self.viewMenu.addSeparator()
class MainWindow(QMainWindow): def __init__(self, tedaCommandLine): super().__init__() self.tedaCommandLine = tedaCommandLine self.cmaps = ColorMaps() self.combobox = QComboBox() self.filename = None self.isMousePressed = False self.isCmdPressed = False self.cursor_coords = CoordinatesModel() self.scales_model = ScalesModel() fig = Figure(figsize=(14, 10)) fig.tight_layout() self.fits_image = FitsPlotter(figure=fig) fig.subplots_adjust(left=0, bottom=0.001, right=1, top=1, wspace=None, hspace=None) self.fits_image = FitsPlotterFitsFile(figure=fig, cmap_model=self.cmaps, scale_model=self.scales_model) self.central_widget = FigureCanvas(fig) self.setCentralWidget(self.central_widget) self.current_x_coord = 0 self.current_y_coord = 0 self.fullWidgetXcord = 0 self.fullWidgetYcord = 0 self.centralWidgetcordX = 0 self.centralWidgetcordY = 0 self.painterComponent = PainterComponent(self.fits_image) # self.painterComponent.startMovingEvents(self.central_widget) self.painterComponent.setCanvas(self.central_widget) self.scanObject = ScanToolbar(self) self.createActions() self.createMenus() self.createToolBars() self.createStatusBar() self.createDockWindows() if not self.tedaCommandLine.ignoreSettings: self.scaleWidget.readSlidersValues() # self.defineButtonsActions() self.setWindowTitle("TeDa") self.painterComponent.observe( lambda change: self.onAutoCenterChange(change), ['auto_center']) self.readWindowSettings() self.readAppState() self.updateHeaderData() self.dragging = draggingComponent.Dragging( widget=self, scale_widget=self.scaleWidget) self.activeLinearAdjustmentByMouseMovement() # Observing here may be to late for values loaded from settings e.g. via readAppState self.painterComponent.observe( lambda change: self.onCenterCircleChange(change), ['ccenter_x', 'ccenter_y']) self.painterComponent.observe( lambda change: self.onCenterCircleRadiusChange(change), ['cradius']) self.fits_image.observe(lambda change: self.onMouseMoveOnImage(change), ['mouse_xdata', 'mouse_ydata']) # self.cmaps.observe(lambda change: self.on_colormap_change(change)) self.full_view_widget.painterComponent.observe( lambda change: self.onRectangleInWidgetMove(change), ['viewX', 'viewY']) self.painterComponent.observe( lambda change: self.movingCentralWidget(change), ['movingViewX', 'movingViewY']) self.fits_image.observe(lambda change: self.onMouseZoomOnImage(change), ['viewBounaries_versionno']) # open last fits try: self.openLastFits() except FileNotFoundError: print('Błąd w odczycie lub brak ostatio wczytanego pliku') def closeEvent(self, event: PySide2.QtGui.QCloseEvent): self.writeAppState() self.writeWindowSettings() if not self.tedaCommandLine.ignoreSettings: self.scaleWidget.writeSlidersValues() super().closeEvent(event) def keyPressEvent(self, e): if e.key() == Qt.Key_Delete: self.deleteSelected() if e.key() == Qt.Key_R: action = self.dockRadialFit.toggleViewAction() if not action.isChecked(): action.trigger() if (self.cursor_coords.img_x != 0 and self.cursor_coords.img_x != None) and (self.cursor_coords.img_y != 0 and self.cursor_coords.img_y != None): self.painterComponent.add(self.cursor_coords.img_x, self.cursor_coords.img_y, type="circleCenter") self.painterComponent.paintAllShapes( self.central_widget.figure.axes[0]) if e.key() == Qt.Key_Control: self.isCmdPressed = True def keyReleaseEvent(self, event: PySide2.QtGui.QKeyEvent): if event.key() == Qt.Key_Control: self.isCmdPressed = False def canvasMousePressEvent(self, event): self.isMousePressed = not self.isMousePressed def mouseMoveEventOnCanvas(self, event): if self.isCmdPressed: if self.isMousePressed: self.dragging.mouseMoveEvent(event) def print_(self): document = self.textEdit.document() printer = QPrinter() dlg = QPrintDialog(printer, self) if dlg.exec_() != QDialog.Accepted: return document.print_(printer) self.statusBar().showMessage("Ready", 2000) def open_dialog(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open Image", ".", "Fits files (*.fits)") if fileName: self.open_fits(fileName) def save(self): filename, _ = QFileDialog.getSaveFileName(self, "Choose a file name", '.', "HTML (*.html *.htm)") if not filename: return file = QFile(filename) if not file.open(QFile.WriteOnly | QFile.Text): QMessageBox.warning( self, "Dock Widgets", "Cannot write file %s:\n%s." % (filename, file.errorString())) return out = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) out << self.textEdit.toHtml() QApplication.restoreOverrideCursor() self.statusBar().showMessage("Saved '%s'" % filename, 2000) def save_dialog(self): figure = self.central_widget.figure filetypes = figure.canvas.get_supported_filetypes_grouped() filterstr = ';;'.join([ k + ' (' + ' '.join(['*.' + ext for ext in v]) + ')' for k, v in filetypes.items() ]) dialog = QFileDialog.getSaveFileName( self, "Save Image As...", os.path.splitext(self.filename)[0], filterstr) if dialog[0] != "": try: self.central_widget.figure.savefig(dialog[0]) except ValueError: print("Unsupported format") def open_fits(self, fileName): """Opens specified FITS file and loads it to user interface""" self.fits_image.set_file(fileName) self.filename = fileName self.cursor_coords.set_wcs_from_fits( self.fits_image.header ) # TODO: one up and extract and set wcs in fits_image before plot self.fits_image.set_wcs(self.cursor_coords.wcs) self.fits_image.plot() self.radial_profile_widget.set_data(self.fits_image.data) self.radial_profile_iraf_widget.set_data(self.fits_image.data) self.updateHeaderData() self.zoom_view_widget.updateFits(self.fits_image) self.full_view_widget.updateFits(self.fits_image) self.saveLastFits() def saveLastFits(self): if self.tedaCommandLine.ignoreSettings: return settings = QSettings() settings.beginGroup("Files") settings.setValue("lastFile", self.filename) settings.endGroup() def openLastFits(self): if (self.tedaCommandLine.openFile is None): if self.tedaCommandLine.ignoreSettings: return settings = QSettings() settings.beginGroup("Files") filename = settings.value("lastFile") settings.endGroup() else: filename = self.tedaCommandLine.openFile if filename: self.open_fits(filename) def readAppState(self): if self.tedaCommandLine.ignoreSettings: return settings = QSettings() settings.beginGroup("WCS") self.wcsSexAct.setChecked(bool(settings.value("sexagesimal", True))) self.wcsGridAct.setChecked(bool(settings.value("grid", False))) settings.endGroup() settings.beginGroup("paint") self.painterComponent.auto_center = bool( settings.value("auto_center", True)) settings.endGroup() def writeAppState(self): if self.tedaCommandLine.ignoreSettings: return settings = QSettings() settings.beginGroup("WCS") settings.setValue("sexagesimal", self.wcsSexAct.isChecked()) settings.setValue("grid", self.wcsGridAct.isChecked()) settings.endGroup() settings.beginGroup("paint") settings.setValue("auto_center", self.painterComponent.auto_center) settings.endGroup() def undo(self): document = self.textEdit.document() document.undo() def insertCustomer(self, customer): if not customer: return def addParagraph(self, paragraph): if not paragraph: return def about(self): QMessageBox.about( self, "TeDa FITS Viewer", f"TeDa FITS Viewer {__version__} <br/>" "Authors: <ul> " "<li>Michał Brodniak</li>" "<li>Konrad Górski</li>" "<li>Mikołaj Kałuszyński</li>" "<li>Edward Lis</li>" "<li>Grzegorz Mroczkowski</li>" "</ul>" "Created by <a href='https://akond.com'>Akond Lab</a> for The " "<a href='https://araucaria.camk.edu.pl'>Araucaria Project</a><br/>" "Licence: MIT <br/>" "3rd party work used: " "<a href='https://material.io/resources/icons/'> Google Material Icons</a>, " "<a href='https://www.astropy.org'> AstroPy</a>, " "<a href='https://doc.qt.io/qtforpython/'> Qt5/PySide2</a>, " "<a href='https://www.scipy.org'> SciPy</a>, and other..." "<br/><br/>" "Visit the <a href='https://github.com/majkelx/teda'>project's GitHub page</a> for help" " and the issue tracker") def on_console_show(self): console.show(ax=self.fits_image.ax, window=self, data=self.fits_image.data, header=self.fits_image.header, wcs=self.cursor_coords.wcs) def on_sex_toggle(self): print('sex toggled to :', self.wcsSexAct.isChecked()) self.cursor_coords.wcs_sexagesimal = self.wcsSexAct.isChecked() def on_grid_toggle(self): self.fits_image.plot_grid = self.wcsGridAct.isChecked() def createActions(self): # ico1 = QPixmap('/Users/mka/projects/astro/teda/icons/png.png') # self.openAct = QAction(ico1, "&Open", self, shortcut=QKeySequence.Open, statusTip="Open FITS file", triggered=self.open) self.openAct = QAction(IconFactory.getIcon('note_add'), "&Open", self, shortcut=QKeySequence.Open, statusTip="Open FITS file", triggered=self.open_dialog) self.saveAct = QAction(IconFactory.getIcon('save'), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save FITS view", triggered=self.save_dialog) self.quitAct = QAction("&Quit", self, shortcut="Ctrl+Q", statusTip="Quit the application", triggered=self.close) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, statusTip="Show the Qt library's About box", triggered=QApplication.instance().aboutQt) self.qtConsoleAct = QAction('Python Console', self, statusTip="Open IPython console window", triggered=self.on_console_show) self.wcsSexAct = QAction( 'Sexagesimal', self, statusTip= "Format WCS coordinates as sexagesimal (RA in hour angle) instead of decimal deg" ) self.wcsSexAct.toggled.connect(self.on_sex_toggle) self.wcsSexAct.setCheckable(True) self.wcsGridAct = QAction( 'Show Grid', self, statusTip="Overlay WCS coordinates grid over image", ) self.wcsGridAct.setCheckable(True) self.wcsGridAct.toggled.connect(self.on_grid_toggle) self.prevHDUAct = QAction(IconFactory.getIcon('skip_previous'), 'Prev HDU', self, statusTip="Previous HDU", triggered=self.prevHDU) self.nextHDUAct = QAction(IconFactory.getIcon('skip_next'), 'Next HDU', self, statusTip="Next HDU", triggered=self.nextHDU) self.zoom4Act = QAction(IconFactory.getIcon("x4"), 'Zoom ×4', self, statusTip="Zoom ×4", triggered=self.setZoomButton4) self.zoom2Act = QAction(IconFactory.getIcon("x2"), 'Zoom ×2', self, statusTip="Zoom ×2", triggered=self.setZoomButton2) self.zoomHomeAct = QAction(IconFactory.getIcon('home'), 'Home', self, statusTip="Reset zoom an position", triggered=self.setZoomButtonHome) self.zoom05Act = QAction(IconFactory.getIcon("1-2"), 'Zoom 1/2', self, statusTip="Zoom 1/2", triggered=self.setZoomButton05) self.zoom025Act = QAction(IconFactory.getIcon("1-4"), 'Zoom 1/4', self, statusTip="Zoom 1/4", triggered=self.setZoomButton025) self.panningAct = QAction(IconFactory.getIcon('panning'), 'Panning', self, statusTip="Panning", triggered=self.changePanningStatus) self.circleAct = QAction(IconFactory.getIcon('circle'), 'Add Region', self, statusTip="Add Region", triggered=self.changeAddCircleStatus) self.centerCircleAct = QAction( IconFactory.getIcon('add_circle_outline'), 'Radial profile', self, statusTip="Radial profile with gaussoide fit [R]-key", triggered=self.changeAddCenterCircleStatus) self.autoCenterAct = QAction( 'Auto Center', self, statusTip="Automatically center cursor on star centroid", triggered=self.changeAutoCenter) self.deleteAct = QAction(IconFactory.getIcon('delete_forever'), 'Delete selected', self, statusTip="Delete selected [Del]-key", triggered=self.deleteSelected) self.slidersAct = QAction( IconFactory.getIcon('slider'), 'Dynamic Scale Sliders', self, statusTip='Show/Hide Dynamic Scale', triggered=self.dynamicScaleDockWidgetTriggerActions) self.panningAct.setCheckable(True) self.panningAct.setChecked(True) self.circleAct.setCheckable(True) self.autoCenterAct.setCheckable(True) self.autoCenterAct.setChecked(self.painterComponent.auto_center) self.centerCircleAct.setCheckable(True) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.scanObject.scanAct) self.fileMenu.addAction(self.scanObject.stopAct) self.fileMenu.addAction(self.scanObject.pauseAct) self.fileMenu.addAction(self.scanObject.resumeAct) self.fileMenu.addAction(self.scanObject.autopauseAct) self.fileMenu.addAction(self.scanObject.disabledautopauseAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.quitAct) self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.panningAct) self.editMenu.addAction(self.circleAct) self.editMenu.addAction(self.centerCircleAct) self.editMenu.addSeparator() self.editMenu.addAction(self.autoCenterAct) self.editMenu.addSeparator() self.editMenu.addAction(self.deleteAct) self.hduMenu = self.menuBar().addMenu("HDU") self.hduMenu.addAction(self.prevHDUAct) self.hduMenu.addAction(self.nextHDUAct) self.hduMenu.addSeparator() self.zoomMenu = self.menuBar().addMenu("Zoom") self.zoomMenu.addAction(self.zoom4Act) self.zoomMenu.addAction(self.zoom2Act) self.zoomMenu.addAction(self.zoomHomeAct) self.zoomMenu.addAction(self.zoom05Act) self.zoomMenu.addAction(self.zoom025Act) self.WcsMenu = self.menuBar().addMenu("W&CS") self.WcsMenu.addAction(self.wcsSexAct) self.WcsMenu.addSeparator() self.WcsMenu.addAction(self.wcsGridAct) self.viewMenu = self.menuBar().addMenu("&View") self.viewMenu.addAction(self.qtConsoleAct) self.viewMenu.addSeparator() self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def createToolBars(self): self.fileToolBar = self.addToolBar("File Toolbar") self.fileToolBar.addAction(self.openAct) self.fileToolBar.addAction(self.saveAct) self.hduToolBar = self.addToolBar("HDU Toolbar") self.hduToolBar.addAction(self.prevHDUAct) self.hduToolBar.addAction(self.nextHDUAct) self.scanToolBar = self.addToolBar("Scan Toolbar") self.scanToolBar.addAction(self.scanObject.scanAct) self.scanToolBar.addAction(self.scanObject.stopAct) self.scanToolBar.addAction(self.scanObject.pauseAct) self.scanToolBar.addAction(self.scanObject.resumeAct) self.scanToolBar.addAction(self.scanObject.autopauseAct) self.scanToolBar.addAction(self.scanObject.disabledautopauseAct) self.scanToolBar.hide() # self.infoToolBar = self.addToolBar("Info Toolbar") # self.mouse_x_label = QLabel('100.1') # self.mouse_y_label = QLabel('100.145') # self.infoToolBar.addWidget(QLabel('image x:')) # self.infoToolBar.addWidget(self.mouse_x_label) # self.infoToolBar.addWidget(QLabel('y:')) # self.infoToolBar.addWidget(self.mouse_y_label) # self.infoToolBar.hide() self.zoomToolBar = self.addToolBar("Zoom Toolbar") self.zoomToolBar.addAction(self.zoom4Act) self.zoomToolBar.addAction(self.zoom2Act) self.zoomToolBar.addAction(self.zoomHomeAct) self.zoomToolBar.addAction(self.zoom05Act) self.zoomToolBar.addAction(self.zoom025Act) self.mouseActionToolBar = self.addToolBar("Mouse Task Toolbar") self.mouseActionToolBar.addAction(self.panningAct) self.mouseActionToolBar.addAction(self.circleAct) self.mouseActionToolBar.addAction(self.centerCircleAct) self.mouseActionToolBar.addAction(self.deleteAct) self.sliderToolBar = self.addToolBar("Slider Toolbar") self.slidersAct.setChecked(True) self.sliderToolBar.addAction(self.slidersAct) self.viewMenu.addAction(self.fileToolBar.toggleViewAction()) self.viewMenu.addAction(self.hduToolBar.toggleViewAction()) self.viewMenu.addAction(self.scanToolBar.toggleViewAction()) # self.viewMenu.addAction(self.infoToolBar.toggleViewAction()) self.viewMenu.addAction(self.zoomToolBar.toggleViewAction()) self.viewMenu.addAction(self.mouseActionToolBar.toggleViewAction()) self.viewMenu.addAction(self.sliderToolBar.toggleViewAction()) self.viewMenu.addSeparator() def nextHDU(self): self.fits_image.changeHDU(True, 1) self.updateHeaderData() def prevHDU(self): self.fits_image.changeHDU(True, -1) self.updateHeaderData() def updateHeaderData(self): self.headerWidget.setHeader() self.prevHDUAct.setEnabled(self.fits_image._huds is not None and self.fits_image.hdu != 0) self.nextHDUAct.setEnabled( self.fits_image._huds is not None and self.fits_image.hdu != len(self.fits_image._huds) - 1) def setZoomButton4(self): self.setZoomButton(4, False) def setZoomButton2(self): self.setZoomButton(2, False) def setZoomButtonHome(self): self.setZoomButton(1, True) def setZoomButton05(self): self.setZoomButton(0.5, False) def setZoomButton025(self): self.setZoomButton(0.25, False) def setZoomButton(self, zoom: float, reset: bool): if self.fits_image.ax != None: self.fits_image.setZoom(zoom, reset) self.full_view_widget.updateMiniatureShape(self.fits_image.viewX, self.fits_image.viewY, self.fits_image.viewW, self.fits_image.viewH) def changePanningStatus(self): if self.panningAct.isChecked(): self.toogleOffRegionButtons() self.panningAct.toggle() self.painterComponent.stopPainting(self.central_widget) self.painterComponent.startMovingEvents(self.central_widget) else: self.painterComponent.stopPainting(self.central_widget) self.painterComponent.stopMovingEvents(self.central_widget) def changeAddCircleStatus(self): if self.circleAct.isChecked(): self.toogleOffRegionButtons() self.circleAct.toggle() self.painterComponent.startPainting(self.central_widget, "circle") else: self.painterComponent.stopPainting(self.central_widget) self.painterComponent.startMovingEvents(self.central_widget) self.panningAct.toggle() def changeAddCenterCircleStatus(self): if self.centerCircleAct.isChecked(): self.toogleOffRegionButtons() self.centerCircleAct.toggle() self.painterComponent.startPainting(self.central_widget, "circleCenter") else: self.painterComponent.stopPainting(self.central_widget) self.painterComponent.startMovingEvents(self.central_widget) self.panningAct.toggle() def changeAutoCenter(self): self.painterComponent.auto_center = self.autoCenterAct.isChecked() def deleteSelected(self): self.painterComponent.deleteSelectedShapes( self.central_widget.figure.axes[0]) def toogleOffRegionButtons(self): if self.panningAct.isChecked(): self.panningAct.toggle() if self.circleAct.isChecked(): self.circleAct.toggle() if self.centerCircleAct.isChecked(): self.centerCircleAct.toggle() self.painterComponent.stopPainting(self.central_widget) def createStatusBar(self): self.statusBar().showMessage("Ready") def createDockWindows(self): # Scale self.dynamic_scale_dock = QDockWidget("Dynamic Scale", self) self.dynamic_scale_dock.setObjectName("SCALE") self.dynamic_scale_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.scaleWidget = ScaleWidget(self, scales_model=self.scales_model, cmap_model=self.cmaps) self.dynamic_scale_dock.setWidget(self.scaleWidget) self.addDockWidget(Qt.RightDockWidgetArea, self.dynamic_scale_dock) self.viewMenu.addAction(self.dynamic_scale_dock.toggleViewAction()) self.dynamic_scale_dock.setFloating(True) self.dynamic_scale_dock.hide() #radial profiles dock = QDockWidget("Radial Profile Fit", self) dock.setObjectName("RADIAL_PROFILE_IRAF") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.radial_profile_iraf_widget = IRAFRadialProfileWidget( self.fits_image.data) dock.setWidget(self.radial_profile_iraf_widget) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) self.dockRadialFit = dock dock = QDockWidget("Radial Profile Curve", self) dock.setObjectName("RADIAL_PROFILE") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.radial_profile_widget = RadialProfileWidget(self.fits_image.data) dock.setWidget(self.radial_profile_widget) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) dock.hide() #info panel dock = QDockWidget("Info", self) dock.setObjectName("INFO_PANEL") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.info_widget = InfoWidget(self) dock.setWidget(self.info_widget) self.addDockWidget(Qt.TopDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) # FITS headers dock = QDockWidget("FITS header", self) dock.setObjectName("FTIS_DATA") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.addDockWidget(Qt.TopDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) self.headerWidget = HeaderTableWidget(self) self.headerWidget.setColumnCount(2) self.headerWidget.setHorizontalHeaderItem(0, QTableWidgetItem("KEY")) self.headerWidget.setHorizontalHeaderItem(1, QTableWidgetItem("VALUE")) self.headerWidget.horizontalHeader().setStretchLastSection(1) self.headerWidget.setEditTriggers( QtWidgets.QTableWidget.NoEditTriggers) self.headerWidget.clearFocus() dock.setWidget(self.headerWidget) # full dock = QDockWidget("Full view", self) dock.setObjectName("FULL_VIEW") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.full_view_widget = FullViewWidget(self.fits_image) self.full_view_widget.fits_image.set_scale_model(self.scales_model) self.full_view_widget.fits_image.set_cmap_model(self.cmaps) dock.setWidget(self.full_view_widget) self.addDockWidget(Qt.TopDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) # zoom dock = QDockWidget("Zoom view", self) dock.setObjectName("ZOOM_VIEW") dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea) self.zoom_view_widget = ZoomViewWidget(self.fits_image) self.zoom_view_widget.fits_image.set_scale_model(self.scales_model) self.zoom_view_widget.fits_image.set_cmap_model(self.cmaps) dock.setWidget(self.zoom_view_widget) self.addDockWidget(Qt.TopDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) # fileSelector dock = QDockWidget("Directory view", self) dock.setObjectName("DIRECTORY_VIEW") dock.setAllowedAreas(Qt.LeftDockWidgetArea) self.file_widget = FileSystemWidget(self) dock.setWidget(self.file_widget) self.addDockWidget(Qt.LeftDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) self.viewMenu.addSeparator() # def changeColor(self, color): # self.cmaps.set_active_color_map(color) # def on_colormap_change(self, change): # self.fits_image.cmap = self.cmaps.get_active_color_map() # self.fits_image.plot() # self.updateFitsInWidgets() def onAutoCenterChange(self, change): self.autoCenterAct.setChecked(change.new) def dynamicScaleDockWidgetTriggerActions(self): if self.dynamic_scale_dock.isHidden(): self.dynamic_scale_dock.show() else: self.dynamic_scale_dock.hide() def onCenterCircleChange(self, change): self.radial_profile_widget.set_centroid( self.painterComponent.ccenter_x, self.painterComponent.ccenter_y) self.radial_profile_iraf_widget.set_centroid( self.painterComponent.ccenter_x, self.painterComponent.ccenter_y) def onCenterCircleRadiusChange(self, change): self.radial_profile_widget.set_radius(self.painterComponent.cradius) self.radial_profile_iraf_widget.set_radius( self.painterComponent.cradius) def onRectangleInWidgetMove(self, change): changed = False if change.new is not None: changed = True if change.name == 'viewX': self.fullWidgetXcord = change.new elif change.name == 'viewY': self.fullWidgetYcord = change.new if changed: self.fits_image.moveToXYcords(self.fullWidgetXcord, self.fullWidgetYcord) def movingCentralWidget(self, change): changed = False if change.new is not None: changed = True if change.name == 'movingViewX': self.centralWidgetcordX = change.new elif change.name == 'movingViewY': self.centralWidgetcordY = change.new if changed: self.full_view_widget.updateMiniatureShapeXYonly( self.centralWidgetcordX, self.centralWidgetcordY) def onMouseMoveOnImage(self, change): display = '' val = 0 if change.new is not None: display = f'{change.new:f}' val = change.new if change.name == 'mouse_xdata': # self.mouse_x_label.setText(display) self.current_x_coord = val self.cursor_coords.set_img_x(change.new) elif change.name == 'mouse_ydata': # self.mouse_y_label.setText(display) self.current_y_coord = val self.cursor_coords.set_img_y(change.new) if display != '': self.zoom_view_widget.setXYofZoom(self.fits_image, self.current_x_coord, self.current_y_coord, self.fits_image.zoom) if not self.hasFocus(): self.setFocus() if self.scanObject.activeScan and self.scanObject.enableAutopause: #reser autopause if not self.scanObject.obserwableValue.autopauseFlag: self.scanObject.obserwableValue.autopauseFlag = True def activeLinearAdjustmentByMouseMovement(self): self.central_widget.mpl_connect('motion_notify_event', self.mouseMoveEventOnCanvas) self.central_widget.mpl_connect('button_press_event', self.canvasMousePressEvent) self.central_widget.mpl_connect('button_release_event', self.canvasMousePressEvent) def onMouseZoomOnImage(self, change): changed = False if change.new is not None: changed = True if changed: self.full_view_widget.updateMiniatureShape(self.fits_image.viewX, self.fits_image.viewY, self.fits_image.viewW, self.fits_image.viewH) def readWindowSettings(self): if self.tedaCommandLine.ignoreSettings: return settings = QSettings() settings.beginGroup("MainWindow") size, pos = settings.value("size"), settings.value("pos") settings.endGroup() if size is not None and pos is not None: print('settings: resize to {} and move to {}', size, pos) self.move(pos) # self.resize(size) print('Size reported ', self.size()) print('Size set ', size) self.resize(size) print('Size reported ', self.size()) else: self.resize(800, 600) geometry = settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) self.restoreState(settings.value("windowState")) self.headerWidget.readSettings(settings) self.file_widget.readSettings(settings) def writeWindowSettings(self): if self.tedaCommandLine.ignoreSettings: return settings = QSettings() settings.beginGroup("MainWindow") settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.endGroup() settings.setValue('geometry', self.saveGeometry()) settings.setValue('windowState', self.saveState()) self.headerWidget.writeSettings(settings) self.file_widget.writeSettings(settings)
class View(QMainWindow): def __init__(self, model, controller): super().__init__() self._model = model self._controller = controller self.segmentcursor = False self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"} ################################################################# # define GUI layout and connect input widgets to external slots # ################################################################# self.setWindowTitle("biopeaks") self.setGeometry(50, 50, 1750, 750) self.setWindowIcon(QIcon(":/python_icon.png")) # figure0 for signal self.figure0 = Figure() self.canvas0 = FigureCanvas(self.figure0) # Enforce minimum height, otherwise resizing with self.splitter causes # mpl to throw an error because figure is resized to height 0. The # widget can still be fully collapsed with self.splitter- self.canvas0.setMinimumHeight(1) # in pixels self.ax00 = self.figure0.add_subplot(1, 1, 1) self.ax00.set_frame_on(False) self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25) self.line00 = None self.scat = None self.segmentspan = None # figure1 for marker self.figure1 = Figure() self.canvas1 = FigureCanvas(self.figure1) self.canvas1.setMinimumHeight(1) self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00) self.ax10.get_xaxis().set_visible(False) self.ax10.set_frame_on(False) self.figure1.subplots_adjust(left=0.04, right=0.98) self.line10 = None # figure2 for statistics self.figure2 = Figure() self.canvas2 = FigureCanvas(self.figure2) self.canvas2.setMinimumHeight(1) self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00) self.ax20.get_xaxis().set_visible(False) self.ax20.set_frame_on(False) self.line20 = None self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00) self.ax21.get_xaxis().set_visible(False) self.ax21.set_frame_on(False) self.line21 = None self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00) self.ax22.get_xaxis().set_visible(False) self.ax22.set_frame_on(False) self.line22 = None self.figure2.subplots_adjust(left=0.04, right=0.98) # navigation bar self.navitools = CustomNavigationToolbar(self.canvas0, self) # peak editing self.editcheckbox = QCheckBox("editable", self) self.editcheckbox.stateChanged.connect(self._model.set_peakseditable) # peak saving batch self.savecheckbox = QCheckBox("save during batch processing", self) self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks) # peak auto-correction batch self.correctcheckbox = QCheckBox("correct during batch processing", self) self.correctcheckbox.stateChanged.connect( self._model.set_correctbatchpeaks) # selecting stats for saving self.periodcheckbox = QCheckBox("period", self) self.periodcheckbox.stateChanged.connect( lambda: self.select_stats("period")) self.ratecheckbox = QCheckBox("rate", self) self.ratecheckbox.stateChanged.connect( lambda: self.select_stats("rate")) self.tidalampcheckbox = QCheckBox("tidal amplitude", self) self.tidalampcheckbox.stateChanged.connect( lambda: self.select_stats("tidalamp")) # channel selection self.sigchanmenulabel = QLabel("biosignal") self.sigchanmenu = QComboBox(self) self.sigchanmenu.addItem("A1") self.sigchanmenu.addItem("A2") self.sigchanmenu.addItem("A3") self.sigchanmenu.addItem("A4") self.sigchanmenu.addItem("A5") self.sigchanmenu.addItem("A6") self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan) # initialize with default value self._model.set_signalchan(self.sigchanmenu.currentText()) self.markerchanmenulabel = QLabel("marker") self.markerchanmenu = QComboBox(self) self.markerchanmenu.addItem("none") self.markerchanmenu.addItem("I1") self.markerchanmenu.addItem("I2") self.markerchanmenu.addItem("A1") self.markerchanmenu.addItem("A2") self.markerchanmenu.addItem("A3") self.markerchanmenu.addItem("A4") self.markerchanmenu.addItem("A5") self.markerchanmenu.addItem("A6") self.markerchanmenu.currentTextChanged.connect( self._model.set_markerchan) # initialize with default value self._model.set_markerchan(self.markerchanmenu.currentText()) # processing mode (batch or single file) self.batchmenulabel = QLabel("mode") self.batchmenu = QComboBox(self) self.batchmenu.addItem("single file") self.batchmenu.addItem("multiple files") self.batchmenu.currentTextChanged.connect(self._model.set_batchmode) self.batchmenu.currentTextChanged.connect(self.toggle_options) # initialize with default value self._model.set_batchmode(self.batchmenu.currentText()) self.toggle_options(self.batchmenu.currentText()) # modality selection self.modmenulabel = QLabel("modality") self.modmenu = QComboBox(self) self.modmenu.addItem("ECG") self.modmenu.addItem("PPG") self.modmenu.addItem("RESP") self.modmenu.currentTextChanged.connect(self._model.set_modality) self.modmenu.currentTextChanged.connect(self.toggle_options) # initialize with default value self._model.set_modality(self.modmenu.currentText()) self.toggle_options(self.modmenu.currentText()) # segment selection; this widget can be openend / set visible from # the menu and closed from within itself (see mapping of segmentermap); # it provides utilities to select a segment from the signal self.segmentermap = QSignalMapper(self) self.segmenter = QDockWidget("select a segment", self) # disable closing such that widget can only be closed by confirming # selection or custom button self.segmenter.setFeatures(QDockWidget.NoDockWidgetFeatures) # Limit number of decimals to four. regex = QRegExp("[0-9]*\.?[0-9]{4}") validator = QRegExpValidator(regex) self.startlabel = QLabel("start") self.startedit = QLineEdit() self.startedit.setValidator(validator) self.endlabel = QLabel("end") self.endedit = QLineEdit() self.endedit.setValidator(validator) segmentfromcursor = QAction(QIcon(":/mouse_icon.png"), "select with mouse", self) segmentfromcursor.triggered.connect(self.enable_segmentedit) self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition) self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition) self.previewedit = QPushButton("preview segment") lambdafn = lambda: self._model.set_segment( [self.startedit.text(), self.endedit.text()]) self.previewedit.clicked.connect(lambdafn) self.confirmedit = QPushButton("confirm segment") self.confirmedit.clicked.connect(self._controller.segment_signal) self.confirmedit.clicked.connect(self.segmentermap.map) self.segmentermap.setMapping(self.confirmedit, 0) self.abortedit = QPushButton("abort segmentation") self.abortedit.clicked.connect(self.segmentermap.map) # reset the segment to None self.segmentermap.setMapping(self.abortedit, 2) self.segmenterlayout = QFormLayout() self.segmenterlayout.addRow(self.startlabel, self.startedit) self.segmenterlayout.addRow(self.endlabel, self.endedit) self.segmenterlayout.addRow(self.previewedit) self.segmenterlayout.addRow(self.confirmedit) self.segmenterlayout.addRow(self.abortedit) self.segmenterwidget = QWidget() self.segmenterwidget.setLayout(self.segmenterlayout) self.segmenter.setWidget(self.segmenterwidget) self.segmenter.setVisible(False) self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea) self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter) # Set up dialog to gather user input for custom files. regex = QRegExp("[1-9][0-9]") validator = QRegExpValidator(regex) self.signallabel = QLabel("biosignal column") self.signaledit = QLineEdit() self.signaledit.setValidator(validator) self.markerlabel = QLabel("marker column") self.markeredit = QLineEdit() self.markeredit.setValidator(validator) regex = QRegExp("[0-9]{2}") validator = QRegExpValidator(regex) self.headerrowslabel = QLabel("number of header rows") self.headerrowsedit = QLineEdit() self.headerrowsedit.setValidator(validator) regex = QRegExp("[0-9]{5}") validator = QRegExpValidator(regex) self.sfreqlabel = QLabel("sampling rate") self.sfreqedit = QLineEdit() self.sfreqedit.setValidator(validator) self.separatorlabel = QLabel("column separator") self.separatormenu = QComboBox(self) self.separatormenu.addItem("comma") self.separatormenu.addItem("tab") self.separatormenu.addItem("colon") self.separatormenu.addItem("space") self.continuecustomfile = QPushButton("continue loading file") self.continuecustomfile.clicked.connect(self.set_customheader) self.customfiledialog = QDialog() self.customfiledialog.setWindowTitle("custom file info") self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png")) self.customfiledialog.setWindowFlags( Qt.WindowCloseButtonHint ) # remove help button by only setting close button self.customfilelayout = QFormLayout() self.customfilelayout.addRow(self.signallabel, self.signaledit) self.customfilelayout.addRow(self.markerlabel, self.markeredit) self.customfilelayout.addRow(self.separatorlabel, self.separatormenu) self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit) self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit) self.customfilelayout.addRow(self.continuecustomfile) self.customfiledialog.setLayout(self.customfilelayout) # set up menubar menubar = self.menuBar() # signal menu signalmenu = menubar.addMenu("biosignal") openSignal = signalmenu.addMenu("load") openEDF = QAction("EDF", self) openEDF.triggered.connect(lambda: self._model.set_filetype("EDF")) openEDF.triggered.connect(self._controller.get_fpaths) openSignal.addAction(openEDF) openOpenSignals = QAction("OpenSignals", self) openOpenSignals.triggered.connect( lambda: self._model.set_filetype("OpenSignals")) openOpenSignals.triggered.connect(self._controller.get_fpaths) openSignal.addAction(openOpenSignals) openCustom = QAction("Custom", self) openCustom.triggered.connect( lambda: self._model.set_filetype("Custom")) openCustom.triggered.connect(lambda: self.customfiledialog.exec_()) openSignal.addAction(openCustom) segmentSignal = QAction("select segment", self) segmentSignal.triggered.connect(self.segmentermap.map) self.segmentermap.setMapping(segmentSignal, 1) signalmenu.addAction(segmentSignal) self.segmentermap.mapped.connect(self.toggle_segmenter) saveSignal = QAction("save", self) saveSignal.triggered.connect(self._controller.get_wpathsignal) signalmenu.addAction(saveSignal) # peak menu peakmenu = menubar.addMenu("peaks") findPeaks = QAction("find", self) findPeaks.triggered.connect(self._controller.find_peaks) peakmenu.addAction(findPeaks) autocorrectPeaks = QAction("autocorrect", self) autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks) peakmenu.addAction(autocorrectPeaks) savePeaks = QAction("save", self) savePeaks.triggered.connect(self._controller.get_wpathpeaks) peakmenu.addAction(savePeaks) loadPeaks = QAction("load", self) loadPeaks.triggered.connect(self._controller.get_rpathpeaks) peakmenu.addAction(loadPeaks) # stats menu statsmenu = menubar.addMenu("statistics") calculateStats = QAction("calculate", self) calculateStats.triggered.connect(self._controller.calculate_stats) statsmenu.addAction(calculateStats) saveStats = QAction("save", self) saveStats.triggered.connect(self._controller.get_wpathstats) statsmenu.addAction(saveStats) # set up status bar to display error messages and current file path self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.progressBar = QProgressBar(self) self.progressBar.setRange(0, 1) self.statusBar.addPermanentWidget(self.progressBar) self.currentFile = QLabel() self.statusBar.addPermanentWidget(self.currentFile) # set up the central widget containing the plot and navigationtoolbar self.centwidget = QWidget() self.setCentralWidget(self.centwidget) # connect canvas0 to keyboard and mouse input for peak editing; # only widgets (e.g. canvas) that currently have focus capture # keyboard input: "You must enable keyboard focus for a widget if # it processes keyboard events." self.canvas0.setFocusPolicy(Qt.ClickFocus) self.canvas0.setFocus() self.canvas0.mpl_connect("key_press_event", self._controller.edit_peaks) self.canvas0.mpl_connect("button_press_event", self.get_xcursor) # arrange the three figure canvases in splitter object self.splitter = QSplitter(Qt.Vertical) # setting opaque resizing to false is important, since resizing gets # very slow otherwise once axes are populated self.splitter.setOpaqueResize(False) self.splitter.addWidget(self.canvas0) self.splitter.addWidget(self.canvas1) self.splitter.addWidget(self.canvas2) self.splitter.setChildrenCollapsible(False) # define GUI layout self.vlayout0 = QVBoxLayout(self.centwidget) self.vlayout1 = QVBoxLayout() self.vlayoutA = QFormLayout() self.vlayoutB = QFormLayout() self.vlayoutC = QVBoxLayout() self.vlayoutD = QVBoxLayout() self.hlayout0 = QHBoxLayout() self.optionsgroupA = QGroupBox("processing options") self.vlayoutA.addRow(self.modmenulabel, self.modmenu) self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu) self.optionsgroupA.setLayout(self.vlayoutA) self.optionsgroupB = QGroupBox("channels") self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu) self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu) self.optionsgroupB.setLayout(self.vlayoutB) self.optionsgroupC = QGroupBox("peaks") self.vlayoutC.addWidget(self.editcheckbox) self.vlayoutC.addWidget(self.savecheckbox) self.vlayoutC.addWidget(self.correctcheckbox) self.optionsgroupC.setLayout(self.vlayoutC) self.optionsgroupD = QGroupBox("select statistics for saving") self.vlayoutD.addWidget(self.periodcheckbox) self.vlayoutD.addWidget(self.ratecheckbox) self.vlayoutD.addWidget(self.tidalampcheckbox) self.optionsgroupD.setLayout(self.vlayoutD) self.vlayout1.addWidget(self.optionsgroupA) self.vlayout1.addWidget(self.optionsgroupB) self.vlayout1.addWidget(self.optionsgroupC) self.vlayout1.addWidget(self.optionsgroupD) self.optionsgroupwidget = QWidget() self.optionsgroupwidget.setLayout(self.vlayout1) self.optionsgroup = QDockWidget("configurations", self) self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea) self.toggleoptionsgroup = self.optionsgroup.toggleViewAction() self.toggleoptionsgroup.setText("show/hide configurations") menubar.addAction(self.toggleoptionsgroup) self.optionsgroup.setWidget(self.optionsgroupwidget) self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup) self.vlayout0.addWidget(self.splitter) self.hlayout0.addWidget(self.navitools) self.vlayout0.addLayout(self.hlayout0) ############################################## # connect output widgets to external signals # ############################################## self._model.signal_changed.connect(self.plot_signal) self._model.marker_changed.connect(self.plot_marker) self._model.peaks_changed.connect(self.plot_peaks) self._model.period_changed.connect(self.plot_period) self._model.rate_changed.connect(self.plot_rate) self._model.tidalamp_changed.connect(self.plot_tidalamp) self._model.path_changed.connect(self.display_path) self._model.segment_changed.connect(self.plot_segment) self._model.status_changed.connect(self.display_status) self._model.progress_changed.connect(self.display_progress) self._model.model_reset.connect(self.reset_plot) ########### # methods # ########### def plot_signal(self, value): self.ax00.clear() self.ax00.relim() # reset navitools history self.navitools.update() self.line00 = self.ax00.plot(self._model.sec, value, zorder=1) self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy") self.canvas0.draw() # print("plot_signal listening") # print(self.ax0.collections, self.ax0.patches, self.ax0.artists) def plot_peaks(self, value): # self.scat is listed in ax.collections if self.ax00.collections: self.ax00.collections[0].remove() self.scat = self.ax00.scatter(self._model.sec[value], self._model.signal[value], c="m", zorder=2) self.canvas0.draw() # print("plot_peaks listening") # print(self.ax0.collections, self.ax0.patches, self.ax0.artists) def plot_segment(self, value): # If an invalid signal has been selected reset the segmenter interface. if value is None: self.toggle_segmenter(1) return if self.ax00.patches: # self.segementspan is listed in ax.patches self.ax00.patches[0].remove() self.segmentspan = self.ax00.axvspan(value[0], value[1], color="m", alpha=0.25) self.canvas0.draw() self.confirmedit.setEnabled(True) # print(self.ax0.collections, self.ax0.patches, self.ax0.artists) def plot_marker(self, value): self.ax10.clear() self.ax10.relim() self.line10 = self.ax10.plot(value[0], value[1]) self.canvas1.draw() # print("plot_marker listening") def plot_period(self, value): self.ax20.clear() self.ax20.relim() self.navitools.home() if self._model.savestats["period"]: self.line20 = self.ax20.plot(self._model.sec, value, c="m") else: self.line20 = self.ax20.plot(self._model.sec, value) self.ax20.set_ylim(bottom=min(value), top=max(value)) self.ax20.set_title("period", pad=0, fontweight="heavy") self.ax20.grid(True, axis="y") self.navitools.update() self.canvas2.draw() # print("plot_period listening") def plot_rate(self, value): self.ax21.clear() self.ax21.relim() self.navitools.home() if self._model.savestats["rate"]: self.line21 = self.ax21.plot(self._model.sec, value, c="m") else: self.line21 = self.ax21.plot(self._model.sec, value) self.ax21.set_ylim(bottom=min(value), top=max(value)) self.ax21.set_title("rate", pad=0, fontweight="heavy") self.ax21.grid(True, axis="y") self.navitools.update() self.canvas2.draw() # print("plot_rate listening") def plot_tidalamp(self, value): self.ax22.clear() self.ax22.relim() self.navitools.home() if self._model.savestats["tidalamp"]: self.line22 = self.ax22.plot(self._model.sec, value, c="m") else: self.line22 = self.ax22.plot(self._model.sec, value) self.ax22.set_ylim(bottom=min(value), top=max(value)) self.ax22.set_title("amplitude", pad=0, fontweight="heavy") self.ax22.grid(True, axis="y") self.navitools.update() self.canvas2.draw() # print("plot_tidalamp listening") def display_path(self, value): self.currentFile.setText(value) def display_status(self, status): # display status until new status is set self.statusBar.showMessage(status) def display_progress(self, value): # if value is 0, the progressbar indicates a busy state self.progressBar.setRange(0, value) def toggle_segmenter(self, value): if not self._model.loaded: return # Open segmenter when called from signalmenu or clear segmenter # upon selection of invalid segment. if value == 1: self.segmenter.setVisible(True) self.confirmedit.setEnabled(False) self.startedit.clear() self.endedit.clear() if self.ax00.patches: self.ax00.patches[0].remove() self.canvas0.draw() # Close segmenter after segment has been confirmed. elif value == 0: self.segmenter.setVisible(False) if self.ax00.patches: self.ax00.patches[0].remove() self.canvas0.draw() # Close segmenter after segmentation has been aborted (reset # segment). elif value == 2: self._model.set_segment([0, 0]) # This will reset the model to None self.segmenter.setVisible(False) if self.ax00.patches: self.ax00.patches[0].remove() self.canvas0.draw() def enable_segmentedit(self): # disable peak editing to avoid interference self.editcheckbox.setChecked(False) if self.startedit.hasFocus(): self.segmentcursor = "start" elif self.endedit.hasFocus(): self.segmentcursor = "end" def set_customheader(self): """Populate the customheader with inputs from the customfiledialog""" # Check if one of the mandatory fields is missing. mandatoryfields = self.signaledit.text() and self.headerrowsedit.text( ) and self.sfreqedit.text() if not mandatoryfields: self._model.status = ( "Please provide values for 'biosignal column'" ", 'number of header rows' and 'sampling" " rate'.") return seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "} self._model.customheader = dict.fromkeys( self._model.customheader, None ) # reset header here since it cannot be reset in controller.get_fpaths() self._model.customheader["signalidx"] = int(self.signaledit.text()) self._model.customheader["skiprows"] = int(self.headerrowsedit.text()) self._model.customheader["sfreq"] = int(self.sfreqedit.text()) self._model.customheader["separator"] = seps[ self.separatormenu.currentText()] if self.markeredit.text(): # not mandatory self._model.customheader["markeridx"] = int(self.markeredit.text()) self.customfiledialog.done(QDialog.Accepted) # close the dialog window self._controller.get_fpaths() # move on to file selection def get_xcursor(self, event): # event.button 1 corresponds to left mouse button if event.button != 1: return # limit number of decimal places to two if self.segmentcursor == "start": self.startedit.selectAll() self.startedit.insert("{:.2f}".format(event.xdata)) elif self.segmentcursor == "end": self.endedit.selectAll() self.endedit.insert("{:.2f}".format(event.xdata)) # disable segment cursor again after value has been set self.segmentcursor = False def select_stats(self, event): """ select or deselect statistics to be saved; toggle boolean with xor operator ^=, toggle color with dictionary """ self._model.savestats[event] ^= True line = None if event == "period": if self.line20: line = self.line20[0] elif event == "rate": if self.line21: line = self.line21[0] elif event == "tidalamp": if self.line22: line = self.line22[0] if line: line.set_color(self.togglecolors[line.get_color()]) self.canvas2.draw() def toggle_options(self, event): if event in ["ECG", "PPG"]: self.tidalampcheckbox.setEnabled(False) self.tidalampcheckbox.setChecked(False) self.ax22.set_visible(False) self.canvas2.draw() elif event == "RESP": self.tidalampcheckbox.setEnabled(True) self.ax22.set_visible(True) self.canvas2.draw() elif event == "multiple files": self.editcheckbox.setEnabled(False) self.editcheckbox.setChecked(False) self.savecheckbox.setEnabled(True) self.correctcheckbox.setEnabled(True) self.markerchanmenu.setEnabled(False) elif event == "single file": self.editcheckbox.setEnabled(True) self.markerchanmenu.setEnabled(True) self.savecheckbox.setEnabled(False) self.savecheckbox.setChecked(False) self.correctcheckbox.setEnabled(False) self.correctcheckbox.setChecked(False) def reset_plot(self): self.ax00.clear() self.ax00.relim() self.line00 = None self.scat = None self.segmentspan = None self.ax10.clear() self.ax10.relim() self.line10 = None self.ax20.clear() self.ax20.relim() self.line20 = None self.ax21.clear() self.ax21.relim() self.line21 = None self.ax22.clear() self.ax22.relim() self.line22 = None self.canvas0.draw() self.canvas1.draw() self.canvas2.draw() self.navitools.update() self.currentFile.clear()
def __init__(self, parent=None): super(MainWindow, self).__init__(parent) QApplication.setApplicationName("Sherloq") QApplication.setOrganizationName("Guido Bartoli") QApplication.setOrganizationDomain("www.guidobartoli.com") QApplication.setApplicationVersion(ToolTree().version) QApplication.setWindowIcon(QIcon("icons/sherloq_white.png")) self.setWindowTitle( f"{QApplication.applicationName()} {QApplication.applicationVersion()}" ) self.mdi_area = QMdiArea() self.setCentralWidget(self.mdi_area) self.filename = None self.image = None modify_font(self.statusBar(), bold=True) tree_dock = QDockWidget(self.tr("TOOLS"), self) tree_dock.setObjectName("tree_dock") tree_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.addDockWidget(Qt.LeftDockWidgetArea, tree_dock) self.tree_widget = ToolTree() self.tree_widget.setObjectName("tree_widget") self.tree_widget.itemDoubleClicked.connect(self.open_tool) tree_dock.setWidget(self.tree_widget) tools_action = tree_dock.toggleViewAction() tools_action.setText(self.tr("Show tools")) tools_action.setToolTip(self.tr("Toggle toolset visibility")) tools_action.setShortcut(QKeySequence(Qt.Key_Tab)) tools_action.setObjectName("tools_action") tools_action.setIcon(QIcon("icons/tools.svg")) help_action = QAction(self.tr("Show help"), self) help_action.setToolTip(self.tr("Toggle online help")) help_action.setShortcut(QKeySequence.HelpContents) help_action.setObjectName("help_action") help_action.setIcon(QIcon("icons/help.svg")) help_action.setCheckable(True) help_action.setEnabled(False) load_action = QAction(self.tr("&Load image..."), self) load_action.setToolTip(self.tr("Load an image to analyze")) load_action.setShortcut(QKeySequence.Open) load_action.triggered.connect(self.load_file) load_action.setObjectName("load_action") load_action.setIcon(QIcon("icons/load.svg")) quit_action = QAction(self.tr("&Quit"), self) quit_action.setToolTip(self.tr("Exit from Sherloq")) quit_action.setShortcut(QKeySequence.Quit) quit_action.triggered.connect(self.close) quit_action.setObjectName("quit_action") quit_action.setIcon(QIcon("icons/quit.svg")) tabbed_action = QAction(self.tr("&Tabbed"), self) tabbed_action.setToolTip(self.tr("Toggle tabbed view for window area")) tabbed_action.setShortcut(QKeySequence(Qt.Key_F10)) tabbed_action.setCheckable(True) tabbed_action.triggered.connect(self.toggle_view) tabbed_action.setObjectName("tabbed_action") tabbed_action.setIcon(QIcon("icons/tabbed.svg")) prev_action = QAction(self.tr("&Previous"), self) prev_action.setToolTip(self.tr("Select the previous tool window")) prev_action.setShortcut(QKeySequence.PreviousChild) prev_action.triggered.connect(self.mdi_area.activatePreviousSubWindow) prev_action.setObjectName("prev_action") prev_action.setIcon(QIcon("icons/previous.svg")) next_action = QAction(self.tr("&Next"), self) next_action.setToolTip(self.tr("Select the next tool window")) next_action.setShortcut(QKeySequence.NextChild) next_action.triggered.connect(self.mdi_area.activateNextSubWindow) next_action.setObjectName("next_action") next_action.setIcon(QIcon("icons/next.svg")) tile_action = QAction(self.tr("&Tile"), self) tile_action.setToolTip( self.tr("Arrange windows into non-overlapping views")) tile_action.setShortcut(QKeySequence(Qt.Key_F11)) tile_action.triggered.connect(self.mdi_area.tileSubWindows) tile_action.setObjectName("tile_action") tile_action.setIcon(QIcon("icons/tile.svg")) cascade_action = QAction(self.tr("&Cascade"), self) cascade_action.setToolTip( self.tr("Arrange windows into overlapping views")) cascade_action.setShortcut(QKeySequence(Qt.Key_F12)) cascade_action.triggered.connect(self.mdi_area.cascadeSubWindows) cascade_action.setObjectName("cascade_action") cascade_action.setIcon(QIcon("icons/cascade.svg")) close_action = QAction(self.tr("Close &All"), self) close_action.setToolTip(self.tr("Close all open tool windows")) close_action.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_W)) close_action.triggered.connect(self.mdi_area.closeAllSubWindows) close_action.setObjectName("close_action") close_action.setIcon(QIcon("icons/close.svg")) self.full_action = QAction(self.tr("Full screen"), self) self.full_action.setToolTip(self.tr("Switch to full screen mode")) self.full_action.setShortcut(QKeySequence.FullScreen) self.full_action.triggered.connect(self.change_view) self.full_action.setObjectName("full_action") self.full_action.setIcon(QIcon("icons/full.svg")) self.normal_action = QAction(self.tr("Normal view"), self) self.normal_action.setToolTip(self.tr("Back to normal view mode")) self.normal_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F12)) self.normal_action.triggered.connect(self.change_view) self.normal_action.setObjectName("normal_action") self.normal_action.setIcon(QIcon("icons/normal.svg")) about_action = QAction(self.tr("&About..."), self) about_action.setToolTip(self.tr("Information about this program")) about_action.triggered.connect(self.show_about) about_action.setObjectName("about_action") about_action.setIcon(QIcon("icons/sherloq_alpha.png")) about_qt_action = QAction(self.tr("About &Qt"), self) about_qt_action.setToolTip( self.tr("Information about the Qt Framework")) about_qt_action.triggered.connect(QApplication.aboutQt) about_qt_action.setIcon(QIcon("icons/Qt.svg")) file_menu = self.menuBar().addMenu(self.tr("&File")) file_menu.addAction(load_action) file_menu.addSeparator() self.recent_actions = [None] * self.max_recent for i in range(len(self.recent_actions)): self.recent_actions[i] = QAction(self) self.recent_actions[i].setVisible(False) self.recent_actions[i].triggered.connect(self.open_recent) file_menu.addAction(self.recent_actions[i]) file_menu.addSeparator() file_menu.addAction(quit_action) view_menu = self.menuBar().addMenu(self.tr("&View")) view_menu.addAction(tools_action) view_menu.addAction(help_action) view_menu.addSeparator() view_menu.addAction(self.full_action) view_menu.addAction(self.normal_action) window_menu = self.menuBar().addMenu(self.tr("&Window")) window_menu.addAction(prev_action) window_menu.addAction(next_action) window_menu.addSeparator() window_menu.addAction(tile_action) window_menu.addAction(cascade_action) window_menu.addAction(tabbed_action) window_menu.addSeparator() window_menu.addAction(close_action) help_menu = self.menuBar().addMenu(self.tr("&Help")) help_menu.addAction(help_action) help_menu.addSeparator() help_menu.addAction(about_action) help_menu.addAction(about_qt_action) main_toolbar = self.addToolBar(self.tr("&Toolbar")) main_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) main_toolbar.addAction(load_action) main_toolbar.addSeparator() main_toolbar.addAction(tools_action) main_toolbar.addAction(help_action) main_toolbar.addSeparator() main_toolbar.addAction(prev_action) main_toolbar.addAction(next_action) main_toolbar.addSeparator() main_toolbar.addAction(tile_action) main_toolbar.addAction(cascade_action) main_toolbar.addAction(tabbed_action) main_toolbar.addAction(close_action) # main_toolbar.addSeparator() # main_toolbar.addAction(self.normal_action) # main_toolbar.addAction(self.full_action) main_toolbar.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea) main_toolbar.setObjectName("main_toolbar") settings = QSettings() settings.beginGroup("main_window") self.restoreGeometry(settings.value("geometry")) self.restoreState(settings.value("state")) self.recent_files = settings.value("recent_files") if self.recent_files is None: self.recent_files = [] elif not isinstance(self.recent_files, list): self.recent_files = [self.recent_files] self.update_recent() settings.endGroup() prev_action.setEnabled(False) next_action.setEnabled(False) tile_action.setEnabled(False) cascade_action.setEnabled(False) close_action.setEnabled(False) tabbed_action.setEnabled(False) self.tree_widget.setEnabled(False) self.showNormal() self.normal_action.setEnabled(False) self.show_message(self.tr("Ready"))
class MainWindow(QMainWindow): """Provides the parent window that includes the BookmarkWidget, BrowserTabWidget, and a DownloadWidget, to offer the complete web browsing experience.""" def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle('PySide2 tabbed browser Example') self._tab_widget = BrowserTabWidget(create_main_window_with_browser) self._tab_widget.enabled_changed.connect(self._enabled_changed) self._tab_widget.download_requested.connect(self._download_requested) self.setCentralWidget(self._tab_widget) self.connect(self._tab_widget, QtCore.SIGNAL("url_changed(QUrl)"), self.url_changed) self._bookmark_dock = QDockWidget() self._bookmark_dock.setWindowTitle('Bookmarks') self._bookmark_widget = BookmarkWidget() self._bookmark_widget.open_bookmark.connect(self.load_url) self._bookmark_widget.open_bookmark_in_new_tab.connect(self.load_url_in_new_tab) self._bookmark_dock.setWidget(self._bookmark_widget) self.addDockWidget(Qt.LeftDockWidgetArea, self._bookmark_dock) self._find_tool_bar = None self._actions = {} self._create_menu() self._tool_bar = QToolBar() self.addToolBar(self._tool_bar) for action in self._actions.values(): if not action.icon().isNull(): self._tool_bar.addAction(action) self._addres_line_edit = QLineEdit() self._addres_line_edit.setClearButtonEnabled(True) self._addres_line_edit.returnPressed.connect(self.load) self._tool_bar.addWidget(self._addres_line_edit) self._zoom_label = QLabel() self.statusBar().addPermanentWidget(self._zoom_label) self._update_zoom_label() self._bookmarksToolBar = QToolBar() self.addToolBar(Qt.TopToolBarArea, self._bookmarksToolBar) self.insertToolBarBreak(self._bookmarksToolBar) self._bookmark_widget.changed.connect(self._update_bookmarks) self._update_bookmarks() def _update_bookmarks(self): self._bookmark_widget.populate_tool_bar(self._bookmarksToolBar) self._bookmark_widget.populate_other(self._bookmark_menu, 3) def _create_menu(self): file_menu = self.menuBar().addMenu("&File") exit_action = QAction(QIcon.fromTheme("application-exit"), "E&xit", self, shortcut = "Ctrl+Q", triggered=qApp.quit) file_menu.addAction(exit_action) navigation_menu = self.menuBar().addMenu("&Navigation") style_icons = ':/qt-project.org/styles/commonstyle/images/' back_action = QAction(QIcon.fromTheme("go-previous", QIcon(style_icons + 'left-32.png')), "Back", self, shortcut = QKeySequence(QKeySequence.Back), triggered = self._tab_widget.back) self._actions[QWebEnginePage.Back] = back_action back_action.setEnabled(False) navigation_menu.addAction(back_action) forward_action = QAction(QIcon.fromTheme("go-next", QIcon(style_icons + 'right-32.png')), "Forward", self, shortcut = QKeySequence(QKeySequence.Forward), triggered = self._tab_widget.forward) forward_action.setEnabled(False) self._actions[QWebEnginePage.Forward] = forward_action navigation_menu.addAction(forward_action) reload_action = QAction(QIcon(style_icons + 'refresh-32.png'), "Reload", self, shortcut = QKeySequence(QKeySequence.Refresh), triggered = self._tab_widget.reload) self._actions[QWebEnginePage.Reload] = reload_action reload_action.setEnabled(False) navigation_menu.addAction(reload_action) navigation_menu.addSeparator() new_tab_action = QAction("New Tab", self, shortcut = 'Ctrl+T', triggered = self.add_browser_tab) navigation_menu.addAction(new_tab_action) close_tab_action = QAction("Close Current Tab", self, shortcut = "Ctrl+W", triggered = self._close_current_tab) navigation_menu.addAction(close_tab_action) edit_menu = self.menuBar().addMenu("&Edit") find_action = QAction("Find", self, shortcut = QKeySequence(QKeySequence.Find), triggered = self._show_find) edit_menu.addAction(find_action) edit_menu.addSeparator() undo_action = QAction("Undo", self, shortcut = QKeySequence(QKeySequence.Undo), triggered = self._tab_widget.undo) self._actions[QWebEnginePage.Undo] = undo_action undo_action.setEnabled(False) edit_menu.addAction(undo_action) redo_action = QAction("Redo", self, shortcut = QKeySequence(QKeySequence.Redo), triggered = self._tab_widget.redo) self._actions[QWebEnginePage.Redo] = redo_action redo_action.setEnabled(False) edit_menu.addAction(redo_action) edit_menu.addSeparator() cut_action = QAction("Cut", self, shortcut = QKeySequence(QKeySequence.Cut), triggered = self._tab_widget.cut) self._actions[QWebEnginePage.Cut] = cut_action cut_action.setEnabled(False) edit_menu.addAction(cut_action) copy_action = QAction("Copy", self, shortcut = QKeySequence(QKeySequence.Copy), triggered = self._tab_widget.copy) self._actions[QWebEnginePage.Copy] = copy_action copy_action.setEnabled(False) edit_menu.addAction(copy_action) paste_action = QAction("Paste", self, shortcut = QKeySequence(QKeySequence.Paste), triggered = self._tab_widget.paste) self._actions[QWebEnginePage.Paste] = paste_action paste_action.setEnabled(False) edit_menu.addAction(paste_action) edit_menu.addSeparator() select_all_action = QAction("Select All", self, shortcut = QKeySequence(QKeySequence.SelectAll), triggered = self._tab_widget.select_all) self._actions[QWebEnginePage.SelectAll] = select_all_action select_all_action.setEnabled(False) edit_menu.addAction(select_all_action) self._bookmark_menu = self.menuBar().addMenu("&Bookmarks") add_bookmark_action = QAction("&Add Bookmark", self, triggered = self._add_bookmark) self._bookmark_menu.addAction(add_bookmark_action) add_tool_bar_bookmark_action = QAction("&Add Bookmark to Tool Bar", self, triggered = self._add_tool_bar_bookmark) self._bookmark_menu.addAction(add_tool_bar_bookmark_action) self._bookmark_menu.addSeparator() tools_menu = self.menuBar().addMenu("&Tools") download_action = QAction("Open Downloads", self, triggered = DownloadWidget.open_download_directory) tools_menu.addAction(download_action) window_menu = self.menuBar().addMenu("&Window") window_menu.addAction(self._bookmark_dock.toggleViewAction()) window_menu.addSeparator() zoom_in_action = QAction(QIcon.fromTheme("zoom-in"), "Zoom In", self, shortcut = QKeySequence(QKeySequence.ZoomIn), triggered = self._zoom_in) window_menu.addAction(zoom_in_action) zoom_out_action = QAction(QIcon.fromTheme("zoom-out"), "Zoom Out", self, shortcut = QKeySequence(QKeySequence.ZoomOut), triggered = self._zoom_out) window_menu.addAction(zoom_out_action) reset_zoom_action = QAction(QIcon.fromTheme("zoom-original"), "Reset Zoom", self, shortcut = "Ctrl+0", triggered = self._reset_zoom) window_menu.addAction(reset_zoom_action) about_menu = self.menuBar().addMenu("&About") about_action = QAction("About Qt", self, shortcut = QKeySequence(QKeySequence.HelpContents), triggered=qApp.aboutQt) about_menu.addAction(about_action) def add_browser_tab(self): return self._tab_widget.add_browser_tab() def _close_current_tab(self): if self._tab_widget.count() > 1: self._tab_widget.close_current_tab() else: self.close() def close_event(self, event): main_windows.remove(self) event.accept() def load(self): url_string = self._addres_line_edit.text().strip() if url_string: self.load_url_string(url_string) def load_url_string(self, url_s): url = QUrl.fromUserInput(url_s) if (url.isValid()): self.load_url(url) def load_url(self, url): self._tab_widget.load(url) def load_url_in_new_tab(self, url): self.add_browser_tab().load(url) def url_changed(self, url): self._addres_line_edit.setText(url.toString()) def _enabled_changed(self, web_action, enabled): action = self._actions[web_action] if action: action.setEnabled(enabled) def _add_bookmark(self): index = self._tab_widget.currentIndex() if index >= 0: url = self._tab_widget.url() title = self._tab_widget.tabText(index) icon = self._tab_widget.tabIcon(index) self._bookmark_widget.add_bookmark(url, title, icon) def _add_tool_bar_bookmark(self): index = self._tab_widget.currentIndex() if index >= 0: url = self._tab_widget.url() title = self._tab_widget.tabText(index) icon = self._tab_widget.tabIcon(index) self._bookmark_widget.add_tool_bar_bookmark(url, title, icon) def _zoom_in(self): new_zoom = self._tab_widget.zoom_factor() * 1.5 if (new_zoom <= WebEngineView.maximum_zoom_factor()): self._tab_widget.set_zoom_factor(new_zoom) self._update_zoom_label() def _zoom_out(self): new_zoom = self._tab_widget.zoom_factor() / 1.5 if (new_zoom >= WebEngineView.minimum_zoom_factor()): self._tab_widget.set_zoom_factor(new_zoom) self._update_zoom_label() def _reset_zoom(self): self._tab_widget.set_zoom_factor(1) self._update_zoom_label() def _update_zoom_label(self): percent = int(self._tab_widget.zoom_factor() * 100) self._zoom_label.setText("{}%".format(percent)) def _download_requested(self, item): # Remove old downloads before opening a new one for old_download in self.statusBar().children(): if type(old_download).__name__ == 'download_widget' and \ old_download.state() != QWebEngineDownloadItem.DownloadInProgress: self.statusBar().removeWidget(old_download) del old_download item.accept() download_widget = download_widget(item) download_widget.removeRequested.connect(self._remove_download_requested, Qt.QueuedConnection) self.statusBar().addWidget(download_widget) def _remove_download_requested(self): download_widget = self.sender() self.statusBar().removeWidget(download_widget) del download_widget def _show_find(self): if self._find_tool_bar is None: self._find_tool_bar = FindToolBar() self._find_tool_bar.find.connect(self._tab_widget.find) self.addToolBar(Qt.BottomToolBarArea, self._find_tool_bar) else: self._find_tool_bar.show() self._find_tool_bar.focus_find() def write_bookmarks(self): self._bookmark_widget.write_bookmarks()
def createDockWindows(self): # dock = QDockWidget('n', self) # dock.setFeatures(dock.NoDockWidgetFeatures) # dock.DockWidgetMovable = False # dock.setAllowedAreas(Qt.TopDockWidgetArea) # self.addDockWidget(Qt.TopDockWidgetArea, dock) dock = QDockWidget("Program", self) dock.setFeatures(dock.NoDockWidgetFeatures) dock.DockWidgetMovable = False dock.setAllowedAreas(Qt.LeftDockWidgetArea) self.multiWidget = QWidget() font1 = QFont("Courier New", 10) self.title = QLabel("SOLAR PANEL Program") font2 = QFont("Courier New", 10) font2.setBold(True) self.author = QLabel("Tomasz Dróżdż") self.author.setFont(font2) self.other = QLabel("Politechnika Wrocławska") self.other2 = QLabel("Automatyka i Robotyka") self.vLayout = QVBoxLayout() self.vLayout.addWidget(self.title) self.vLayout.addWidget(self.author) self.vLayout.addWidget(self.other) self.vLayout.addWidget(self.other2) self.multiWidget.setLayout(self.vLayout) dock.setWidget(self.multiWidget) self.addDockWidget(Qt.LeftDockWidgetArea, dock) dock = QDockWidget("Zegar", self) dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.multiWidget2 = QWidget() font3 = QFont("Arial", 13) font4 = QFont("Arial", 20) self.date = QLabel(QDate.currentDate().toString("d MMMM yyyy: ")) self.clock = QLabel(QTime.currentTime().toString("hh:mm:ss")) self.date.setFont(font3) self.clock.setFont(font4) font4.setBold(True) self.vLayout2 = QVBoxLayout() self.vLayout2.addWidget(self.date) self.vLayout2.addWidget(self.clock) self.multiWidget2.setLayout(self.vLayout2) dock.setWidget(self.multiWidget2) self.addDockWidget(Qt.LeftDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction()) dock = QDockWidget("Współrzędne Geograficzne Panelu: ", self) s = ephem.Sun() s.compute(epoch=ephem.now()) dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.multiWidget3 = QWidget() font5 = QFont("Arial", 12) font6 = QFont("Arial", 17) self.vLayout3 = QGridLayout() self.result = QLabel(self.latlong) self.name = QLabel('Nazwa') self.latitude = QLabel('Szerokość') self.longitude = QLabel('Długość') self.dateandtime = QLabel('Data i Czas') # self.result = QLabel('') # self.result2 = QLabel('') # self.result3 = QLabel('') self.solarpanelcor = QLabel('WSPÓŁRZĘDNE PANELU SŁONECZNEGO: ') self.nameEdit = QLineEdit() self.nameEdit.setFixedHeight(28) self.nameEdit.setFixedWidth(386) self.nameEdit.setStatusTip( "Wprowadź nazwę dla konfiguracji współrzędnych i czasu") self.latitudeEdit = QLineEdit() self.latitudeEdit.setFixedHeight(28) self.latitudeEdit.setFixedWidth(386) self.latitudeEdit.setStatusTip( "Wprowadzona szerokość powinna być w stopniach dziesiętnych (np.: 51.100000)" ) self.longitudeEdit = QLineEdit() self.longitudeEdit.setFixedHeight(28) self.longitudeEdit.setFixedWidth(386) self.longitudeEdit.setStatusTip( "Wprowadzona długość powinna być w stopniach dziesiętnych (np.: 17.03333)" ) self.dateandtimeEdit = QLineEdit() self.dateandtimeEdit.setFixedHeight(28) self.dateandtimeEdit.setFixedWidth(386) self.dateandtimeEdit.setStatusTip( "Wprowadzona data powinna być w formacie: rok/mies/dzień<spacja>godz:min:sek (np.: 2022/12/4 8:12:7)" ) self.button = QPushButton('Wylicz współrzędne / Przerwij liczenie', self) self.button.clicked.connect(self.handleButton4) self.name.setFont(font5) self.latitude.setFont(font5) self.longitude.setFont(font5) self.dateandtime.setFont(font5) self.button.setFont(font6) self.button.setStatusTip("Rozpoczyna Obliczenia") # self.button.addAction(self.buttonAct) # self.solarpanelcor.setFont(font5) # self.result.setFont(font6) # self.result2.setFont(font6) # self.result3.setFont(font6) self.vLayout3.addWidget(self.name) self.vLayout3.addWidget(self.nameEdit) self.vLayout3.addWidget(self.latitude) self.vLayout3.addWidget(self.latitudeEdit) self.vLayout3.addWidget(self.longitude) self.vLayout3.addWidget(self.longitudeEdit) self.vLayout3.addWidget(self.dateandtime) self.vLayout3.addWidget(self.dateandtimeEdit) self.vLayout3.addWidget(self.button) # self.vLayout3.addWidget(self.solarpanelcor) # self.vLayout3.addWidget(self.result) # self.vLayout3.addWidget(self.result2) # self.vLayout3.addWidget(self.result3) self.multiWidget3.setLayout(self.vLayout3) dock.setWidget(self.multiWidget3) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.viewMenu.addAction(dock.toggleViewAction())
class AsemblerIDE(QMainWindow): def __init__(self): super(AsemblerIDE, self).__init__() self.workspace = None self.backupTimer = 300000 PathManager.START_DIRECTORY = os.getcwd() self.workspaceConfiguration = WorkspaceConfiguration.loadConfiguration( ) self.snippetManager = SnippetManager.loadSnippetConfiguration() self.tooltipManager = TooltipManager.loadTooltipConfiguration() self.configurationManager = ConfigurationManager() self.editorTabs = EditorTabWidget(self.snippetManager, self.tooltipManager) self.menuBar = MenuBar() self.terminal = Terminal() self.toolBar = ToolBar(self.configurationManager) self.statusBar = StatusBar() self.treeView = TreeView(self.configurationManager) self.help = HelpWidget() self.ascii = AsciiTableWidget() self.setStatusBar(self.statusBar) self.addToolBar(self.toolBar) self.addDockWidget(Qt.BottomDockWidgetArea, self.terminal) self.addDockWidget(Qt.RightDockWidgetArea, self.help) self.addDockWidget(Qt.RightDockWidgetArea, self.ascii) self.splitDockWidget(self.help, self.ascii, Qt.Vertical) self.treeDock = QDockWidget() self.treeDock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.treeDock.setStyleSheet("background-color: #2D2D30; color: white;") self.treeDock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable) self.treeDock.setWindowTitle("Workspace explorer") self.treeDock.setWidget(self.treeView) header = QLabel("Workspace explorer") # header.setStyleSheet("background-color: #007ACC;") self.treeDock.setTitleBarWidget(header) self.addDockWidget(Qt.LeftDockWidgetArea, self.treeDock) self.setMenuBar(self.menuBar) self.setMinimumSize(1200, 800) self.setWindowTitle("i386 Assembly Integrated Development Environment") self.setCentralWidget(self.editorTabs) self.setStyleSheet("background-color: #3E3E42; color: white;") self.setWindowIcon(QIcon(resource_path("resources/app_icon.ico"))) self.addTabWidgetEventHandlers() self.addMenuBarEventHandlers() self.addToolBarEventHandlers() self.addTreeViewEventHandlers() self.checkWorkspaceConfiguration() #self.populateTreeView() #self.statusBar.comboBox.currentTextChanged.connect(self.changeEditorSyntax) self.statusBar.tabWidthComboBox.currentTextChanged.connect( self.changeEditorTabWidth) self.timer = QTimer() self.timer.start(self.backupTimer) self.timer.timeout.connect(self.makeBackupSave) self.terminal.console.setFocus() self.tabSwitcher = TabSwitcher(self.editorTabs) self.tabSwitcher.hide() self.projectSwitcher = ProjectSwitcher(self.configurationManager, self.toolBar.projectComboBox) self.projectSwitcher.hide() self.terminal.projectSwitchRequested.connect(self.showProjectSwitcher) self.terminal.tabSwitchRequested.connect(self.showTabSwitcher) self.editorTabs.tabSwitchRequested.connect(self.showTabSwitcher) self.editorTabs.projectSwitchRequested.connect( self.showProjectSwitcher) self.helpDocsDialog = GettingStartedDialog() self.helpDocsDialog.hide() def makeBackupSave(self): self.workspace.saveBackup() self.timer.setInterval( self.backupTimer ) # interval has to be reset cause the timer may have been paused def changeEditorTabWidth(self, text): currentTab: EditorTab = self.editorTabs.getCurrentTab() if currentTab: currentTab.widget.editor.tabSize = int(text) def changeEditorSyntax(self, text): currentTab: EditorTab = self.editorTabs.getCurrentTab() if currentTab: if text == "Assembly": currentTab.widget.editor.sintaksa = AsemblerSintaksa( currentTab.widget.editor.document()) elif text == "C": currentTab.widget.editor.sintaksa = CSyntax( currentTab.widget.editor.document()) currentTab.widget.editor.update() def checkWorkspaceConfiguration(self): defaultWorkspace = self.workspaceConfiguration.getDefaultWorkspace() if defaultWorkspace: if self.openWorkspaceAction(defaultWorkspace): self.show() return else: self.workspaceConfiguration.removeWorkspace(defaultWorkspace) dialog = WorkspaceConfigurationEditor(self.workspaceConfiguration, self) if dialog.exec_(): self.show() else: sys.exit(0) def addTabWidgetEventHandlers(self): self.editorTabs.currentChanged.connect(self.activeTabChanged) def addTreeViewEventHandlers(self): self.treeView.fileDoubleCliked.connect(self.loadFileText) self.treeView.workspaceReload.connect( lambda wsProxy: self.openWorkspaceAction(wsProxy.path, updateWorkspace=True)) self.treeView.workspaceRename.connect( lambda oldPath, wsProxy: self.workspaceConfiguration. replaceWorkpsace(oldPath, wsProxy.path)) self.treeView.newProjectAdded.connect( lambda: self.toolBar.updateComboBox()) self.treeView.projectCompile.connect( lambda proxy: self.compileAction(proxy)) self.treeView.projectDebug.connect( lambda proxy: self.debugAction(proxy)) self.treeView.projectRun.connect(lambda proxy: self.runAction(proxy)) self.treeView.projectRemove.connect( lambda proxy: self.removeProject(proxy)) self.treeView.projectRename.connect( lambda oldPath, project: self.renameProject(oldPath, project)) self.treeView.fileRemove.connect( lambda fileProxy: self.removeFile(fileProxy)) self.treeView.fileRename.connect( lambda oldPath, fileProxy: self.renameFile(oldPath, fileProxy)) self.treeView.fileSave.connect( lambda fileProxy: self.updateEditorTrie(fileProxy)) self.treeView.invalidWorkspace.connect(self.invalidWorkspace) self.treeView.projectSave.connect(self.saveProject) self.treeView.quickAssemblyFile.connect(self.loadFileText) self.treeView.newFile.connect(self.loadFileText) def saveProject(self, projectProxy: ProjectProxy): self.saveAllFiles(projectProxy) def invalidWorkspace(self, workspace: WorkspaceNode): workspace.deleted = True msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Workspace '{}' has been deleted from the disk.".format( workspace.path)) msg.setWindowTitle("Invalid workspace") msg.exec_() if workspace.path in self.workspaceConfiguration.getWorkspaces(): self.workspaceConfiguration.removeWorkspace(workspace.path) self.switchWorkspaceAction() def renameFile(self, oldPath: str, fileProxy: FileProxy): # fileProxy.text = None key = "{}/{}".format(fileProxy.parent.path, oldPath) if key in self.editorTabs.projectTabs: newKey = "{}/{}".format(fileProxy.parent.path, fileProxy.path) tab = self.editorTabs.projectTabs.pop(key) self.editorTabs.projectTabs[newKey] = tab tab.tabName = newKey index = self.editorTabs.tabs.index(fileProxy) tabText = newKey + "*" if fileProxy.hasUnsavedChanges else newKey self.editorTabs.setTabText(index, tabText) def renameProject(self, oldPath: str, project: ProjectNode): self.toolBar.updateComboBox() for fileProxy in project.proxy.files: oldKey = "{}/{}".format(oldPath, fileProxy.path) if oldKey in self.editorTabs.projectTabs: newKey = "{}/{}".format(project.proxy.path, fileProxy.path) tab = self.editorTabs.projectTabs.pop(oldKey) self.editorTabs.projectTabs[newKey] = tab tab.tabName = newKey index = self.editorTabs.tabs.index(fileProxy) tabText = newKey + "*" if fileProxy.hasUnsavedChanges else newKey self.editorTabs.setTabText(index, tabText) def removeFile(self, proxy: FileProxy): key = "{}/{}".format(proxy.parent.path, proxy.path) if key in self.editorTabs.projectTabs: self.editorTabs.closeTab(self.editorTabs.tabs.index(proxy), askToSave=False) def removeProject(self, proxy: ProjectProxy): for file in proxy.files: if file in self.editorTabs.tabs: self.editorTabs.closeTab(self.editorTabs.tabs.index(file), askToSave=False) self.toolBar.updateComboBox() def checkIfWorkspaceDeleted(self): if not os.path.exists(self.workspace.path): self.workspace.deleted = True def activeTabChanged(self, index): if index == -1: self.statusBar.tabWidthComboBox.setCurrentText('4') return syntax = "Assembly" if self.editorTabs.tabs[index].path[-1].lower( ) == "s" else "C" proxy = self.editorTabs.tabs[index] key = "{}/{}".format(proxy.parent.path, proxy.path) self.statusBar.comboBox.setCurrentText(syntax) self.statusBar.tabWidthComboBox.setCurrentText( str(self.editorTabs.projectTabs[key].widget.editor.tabSize)) #self.changeEditorSyntax(syntax) def populateTreeView(self): workspace = WorkspaceNode() workspace.setText(0, "My workspace") self.treeView.setRoot(workspace) for i in range(5): project = ProjectNode() project.setText(0, "My Project {}".format(i + 1)) assemblyFile = AssemblyFileNode() assemblyFile.setText(0, "procedure_{}.S".format(i + 1)) cFile = CFileNode() cFile.setText(0, "main_{}.c".format(i + 1)) self.treeView.addNode(workspace, project) self.treeView.addNode(project, assemblyFile) self.treeView.addNode(project, cFile) project.setExpanded(True) self.workspace = workspace def closeEvent(self, event): if self.tabSwitcher.isActiveWindow(): self.tabSwitcher.hide() if self.helpDocsDialog.isVisible(): self.helpDocsDialog.hide() for proxy in self.editorTabs.tabs: if proxy.hasUnsavedChanges: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setParent(None) msg.setModal(True) msg.setWindowTitle("Confirm Exit") msg.setText("The file {}/{} has been modified.".format( proxy.parent.path, proxy.path)) msg.setInformativeText("Do you want to save the changes?") msg.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) msg.setDefaultButton(QMessageBox.Save) retValue = msg.exec_() if retValue == QMessageBox.Save: if not self.saveFileAction(): event.ignore() return elif retValue == QMessageBox.Discard: pass else: event.ignore() return self.workspaceConfiguration.saveConfiguration() self.snippetManager.saveConfiguration() self.tooltipManager.saveConfiguration() self.checkIfWorkspaceDeleted() if not self.workspace.deleted: self.workspace.proxy.closedNormally = True self.saveWorkspaceAction() else: if self.workspace.path in self.workspaceConfiguration.getWorkspaces( ): self.workspaceConfiguration.removeWorkspace( self.workspace.path) super(AsemblerIDE, self).closeEvent(event) def addMenuBarEventHandlers(self): self.menuBar.quickAssemblyProjectAction.triggered.connect( self.quickAssemblyProject) self.menuBar.newWorkspaceAction.triggered.connect( self.newWorkspaceAction) self.menuBar.saveWorkspaceAction.triggered.connect( self.saveWorkpsaceAllFiles) self.menuBar.openWorkspaceAction.triggered.connect( self.openWorkspaceAction) self.menuBar.switchWorkspaceAction.triggered.connect( self.switchWorkspaceAction) self.menuBar.saveAction.triggered.connect(self.saveFileAction) self.menuBar.findAction.triggered.connect(self.findAction) self.menuBar.editDefaultWorkspace.triggered.connect( self.editDefaultWorkspaceConfiguration) self.menuBar.editCodeSnippets.triggered.connect(self.editCodeSnippets) self.menuBar.editSettings.triggered.connect(self.editSettings) self.menuBar.aboutAction.triggered.connect(self.showAbout) self.menuBar.helpAction.triggered.connect(self.showGettingStarted) self.menuBar.view.addAction(self.terminal.toggleViewAction()) self.menuBar.view.addAction(self.treeDock.toggleViewAction()) self.menuBar.view.addAction(self.help.toggleViewAction()) self.menuBar.view.addAction(self.ascii.toggleViewAction()) self.menuBar.view.addAction(self.toolBar.toggleViewAction()) def quickAssemblyProject(self): if self.workspace: self.workspace.createQuickAssemblyProject() def showAbout(self): dialog = AboutDialog() dialog.exec_() def showGettingStarted(self): if self.helpDocsDialog.isHidden(): self.helpDocsDialog.show() def findAction(self): currentTab: EditorTabWidget = self.editorTabs.getCurrentTab() if not currentTab: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Cannot open file and replace window because there is no open file at the moment." ) msg.setWindowTitle("Find & Replace error") msg.exec_() return currentTab.widget.find.setVisible(True) if currentTab.widget.editor.lastFind: currentTab.widget.find.findLabel.setText( currentTab.widget.editor.lastFind) currentTab.widget.find.findLabel.setFocus() def switchWorkspaceAction(self): remaining = self.timer.remainingTime() self.timer.stop( ) # timer for creating backups needs to be paused when switching ws dialog = WorkspaceConfigurationEditor(self.workspaceConfiguration, self, switch=True) if dialog.exec_() and dialog.workspaceDirectory: self.workspace.proxy.closedNormally = True self.saveWorkspaceAction() if not self.editorTabs.closeAllTabs(): self.timer.start( remaining) # timer for saving backups is resumed return self.openWorkspaceAction(dialog.workspaceDirectory) self.timer.start(remaining) # timer for saving backups is resumed def editDefaultWorkspaceConfiguration(self): editor = DefaultWorkspaceEditor(self.workspaceConfiguration) if editor.exec_(): self.workspaceConfiguration.saveConfiguration() def editCodeSnippets(self): editor = SnippetEditor(self.snippetManager) if editor.exec_(): self.snippetManager.saveConfiguration() def editSettings(self): editor = SettingsEditor(self.tooltipManager) if editor.exec_(): self.tooltipManager.saveConfiguration() def newWorkspaceAction(self): remaining = self.timer.remainingTime() self.timer.stop( ) # timer for creating backups needs to be paused when switching ws if not self.editorTabs.closeAllTabs(): self.timer.start(remaining) # timer for saving backups is resumed return False self.workspace.proxy.closedNormally = True self.saveWorkspaceAction() workspace = WorkspaceNode() name = QFileDialog.getExistingDirectory( self, "New workspace", "select new workspace directory") if name: path = os.path.join(name, ".metadata") backup_path = os.path.join(name, ".backup") if os.path.isdir(path) or os.path.isdir(backup_path): self.msgInvalidFolderError(name) self.timer.start( remaining) # timer for saving backups is resumed return wsname = name[name.rindex(os.path.sep) + 1:] regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if ' ' in name or regex.search(wsname): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Workspace path/name cannot contain whitespace or special characters." ) msg.setWindowTitle("Workspace creation error") msg.exec_() self.timer.start( remaining) # timer for saving backups is resumed return False workspace.path = name proxy = WorkspaceProxy() proxy.path = name workspace.proxy = proxy workspace.setIcon(0, QIcon(resource_path("resources/workspace.png"))) workspace.setText(0, wsname) self.workspace = workspace self.treeView.setRoot(self.workspace) self.saveWorkspaceAction() self.configurationManager.allProjects = [] self.configurationManager.currentProject = None self.toolBar.updateComboBox() self.terminal.executeCommand("cd {}".format(self.workspace.path)) self.workspaceConfiguration.addWorkspace(self.workspace.proxy.path) self.timer.start(remaining) # timer for saving backups is resumed return True self.timer.start(remaining) # timer for saving backups is resumed return False def saveWorkspaceAction(self, workspacePath=None): if self.workspace: self.workspace.saveWorkspace(workspacePath) def saveWorkpsaceAllFiles(self): self.saveAllFiles() self.saveWorkspaceAction() def updateProjectList(self): self.configurationManager.allProjects = [] self.configurationManager.currentProject = None projects = self.treeView.getProjects() if len(projects): self.configurationManager.allProjects = self.treeView.getProjects() self.configurationManager.currentProject = projects[0] self.toolBar.updateComboBox() def openWorkspaceAction(self, workspacePath=None, updateWorkspace=False): if not self.editorTabs.closeAllTabs(): return if not workspacePath: workspacePath = QFileDialog.getExistingDirectory( self, "Open workspace", "select new workspace directory") if not workspacePath: return regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if ' ' in workspacePath or regex.search( os.path.basename(workspacePath)): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Workspace path/name cannot contain whitespace or special characters." ) msg.setWindowTitle("Workspace creation error") msg.exec_() return False path = os.path.join(workspacePath, ".metadata") backup_path = os.path.join(workspacePath, ".backup") if os.path.isdir(path) or os.path.isdir(backup_path): self.msgInvalidFolderError(workspacePath) return workspace = WorkspaceProxy() self.workspace = WorkspaceNode() if os.path.exists(path): try: # in try block in case there is a corrupted .metadata file on the path with open(path, 'rb') as file: workspace = pickle.load(file) except: workspace.closedNormally = False # set it to false to trigger backup msg in case .metadata is corrupted elif not os.path.exists( backup_path ): # creates a .metadata file for a clean new workspace self.workspace.proxy = workspace self.applyWsCompatibilityFix(workspacePath) self.saveWorkspaceAction(workspacePath) self.workspace.proxy = workspace self.applyWsCompatibilityFix(workspacePath) attempted_backup = False if not self.workspace.proxy.closedNormally: if self.restoreBackupMessage(workspacePath, updateWorkspace=updateWorkspace): attempted_backup = True if self.loadWorkspaceAction( workspacePath, backup=True): # attempt to load backup self.updateProjectList() return True else: self.messageBackupError("closedAbruptly") if self.loadWorkspaceAction( workspacePath, backup=False): # attempt to load regular ws file self.updateProjectList() return True # If the regular file won't load for some reason and there was no backup attempt, ask to load the backup file elif not attempted_backup and self.restoreBackupMessage( workspacePath, failedToLoad=True): if self.loadWorkspaceAction( workspacePath, backup=True): # attempt to load the backup file self.updateProjectList() return True else: self.messageBackupError() return False def msgInvalidFolderError(self, path): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Invalid folder for a workspace." "\nEnsure there are no .metadata and .backup folders in \n{}". format(path)) msg.setWindowTitle("Failed to load workspace.") msg.exec_() def messageBackupError(self, msgType=None): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) if msgType == "closedAbruptly": msg.setText("Failed to load {}." "\nRegular workspace save will be restored.".format( ".backup workspace file")) else: msg.setText("Failed to load {}.".format(".backup workspace file")) msg.setWindowTitle("Failed to load backup workspace.") msg.exec_() def applyWsCompatibilityFix(self, workspacePath): try: closedNormally = self.workspace.proxy.closedNormally except AttributeError: closedNormally = True self.workspace.proxy.closedNormally = closedNormally # adds attribute to old ws files self.workspace.path = workspacePath # changes the path to currently selected dir, in case it was moved self.workspace.proxy.path = workspacePath def restoreBackupMessage(self, wsName, failedToLoad=False, updateWorkspace=False): try: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setParent(None) msg.setModal(True) msg.setWindowTitle("Workspace recovery") time = strftime( '%m/%d/%Y %H:%M:%S', localtime(os.path.getmtime(os.path.join(wsName, ".backup")))) if failedToLoad: msg.setText("The workplace {} could not be loaded.\n" "\nTime the backup was created: {}".format( wsName, time)) elif updateWorkspace: msg.setText( "Choose if you want to reload workspace or to recover from backup.\n" "\nTime the backup was created: {}".format(wsName, time)) else: msg.setText("The workplace {} was closed unexpectedly.\n" "\nTime the backup was created: {}".format( wsName, time)) if not updateWorkspace: msg.setInformativeText( "Would you like to recover from backup?") else: msg.setInformativeText( "Would you like to recover from backup? Select No if you just want to update the workspace." ) msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg.setDefaultButton(QMessageBox.Yes) retValue = msg.exec_() if retValue == QMessageBox.Yes: return True else: return except: return False def loadWorkspaceAction(self, workspacePath, backup=False): if backup: path = os.path.join(workspacePath, ".backup") else: path = os.path.join(workspacePath, ".metadata") if os.path.exists(path): try: # in try block in case there is a corrupted .metadata file on the path with open(path, 'rb') as file: workspace = pickle.load(file) except: return False else: return False self.workspace = WorkspaceNode() self.workspace.proxy = workspace self.applyWsCompatibilityFix(workspacePath) self.workspace.setIcon(0, QIcon(resource_path("resources/workspace.png"))) self.workspace.setText( 0, workspacePath[workspacePath.rindex(os.path.sep) + 1:]) self.workspace.proxy.closedNormally = False if backup: success = self.workspace.loadBackupWorkspace(workspacePath) else: success = self.workspace.loadWorkspace() if not success: return False self.treeView.setRoot(self.workspace) projects = self.treeView.getProjects() if projects: self.configurationManager.allProjects.clear() self.configurationManager.allProjects.extend(projects) self.toolBar.updateComboBox() #self.treeView.expandAll() self.terminal.executeCommand("cd {}".format(self.workspace.path)) self.workspaceConfiguration.addWorkspace(self.workspace.proxy.path) if workspacePath: self.saveWorkspaceAction(workspacePath) return True def addToolBarEventHandlers(self): self.toolBar.compile.triggered.connect(self.compileAction) self.toolBar.run.triggered.connect(self.runAction) self.toolBar.debug.triggered.connect(self.debugAction) def debugAction(self, projectProxy=None): currentProject: ProjectNode = self.configurationManager.currentProject if not currentProject: self.showNoCurrentProjectMessage("Debug") return if not os.path.exists(currentProject.proxy.getProjectPath()): currentProject.eventManager.invalidProject.emit(currentProject) return proxy = None if projectProxy: proxy = projectProxy else: if currentProject: proxy = currentProject.proxy if proxy: commandString = proxy.getProjectDebugCommand() self.terminal.console.setFocus() if self.terminal.executeCommand(proxy.getProjectCompileCommand()): copmileString = proxy.getProjectCompileCommand() if ' -g ' not in copmileString: msg = QMessageBox() msg.setStyleSheet( "background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Warning) msg.setText( "Please set '-g' option in compiler configuration to be able to debug your project." ) msg.setWindowTitle("Debug warning") msg.exec_() return False self.terminal.executeCommand(commandString) self.toolBar.projectComboBox.setCurrentText(proxy.path) def runAction(self, projectProxy=None): currentProject: ProjectNode = self.configurationManager.currentProject if not currentProject: self.showNoCurrentProjectMessage("Run") return if not os.path.exists(currentProject.proxy.getProjectPath()): currentProject.eventManager.invalidProject.emit(currentProject) return proxy = None if projectProxy: proxy = projectProxy else: if currentProject: proxy = currentProject.proxy if proxy: commandString = proxy.getProjectRunCommand() self.terminal.console.setFocus() if self.terminal.executeCommand(proxy.getProjectCompileCommand()): self.terminal.executeCommand(commandString) self.toolBar.projectComboBox.setCurrentText(proxy.path) def compileAction(self, projectProxy=None): currentProject: ProjectNode = self.configurationManager.currentProject if not currentProject: self.showNoCurrentProjectMessage("Compile") return if not os.path.exists(currentProject.proxy.getProjectPath()): currentProject.eventManager.invalidProject.emit(currentProject) return proxy = None if projectProxy: proxy = projectProxy else: if currentProject: proxy = currentProject.proxy if proxy: commandString = proxy.getProjectCompileCommand() self.terminal.console.setFocus() self.terminal.executeCommand(commandString) self.toolBar.projectComboBox.setCurrentText(proxy.path) def showNoCurrentProjectMessage(self, action: str): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("You have to select a project first.") msg.setWindowTitle("{} error".format(action.capitalize())) msg.exec_() def checkExecutable(self): if self.editor.filePath: destination = self.editor.filePath[:-1] + "out" return os.path.exists(destination) return None def loadFileText(self, fileProxy): key = "{}/{}".format(fileProxy.parent.path, fileProxy.path) if key in self.editorTabs.projectTabs: self.editorTabs.setCurrentIndex( self.editorTabs.tabs.index(fileProxy)) return text = self.openFileAction(fileProxy) fileProxy.text = text fileProxy.hasUnsavedChanges = False if fileProxy.getFilePath()[-1].lower() == "c": currentText = "C" else: currentText = "Assembly" update = True if len(self.editorTabs.tabs) == 0: self.editorTabs.tabs.append(fileProxy) update = False self.editorTabs.addNewTab(fileProxy, update) self.statusBar.comboBox.setCurrentText(currentText) if currentText == "Assembly": self.editorTabs.projectTabs[ key].widget.editor.sintaksa = AsemblerSintaksa( self.editorTabs.projectTabs[key].widget.editor.document()) elif currentText == "C": self.editorTabs.projectTabs[key].widget.editor.sintaksa = CSyntax( self.editorTabs.projectTabs[key].widget.editor.document()) def updateEditorTrie(self, proxy: FileProxy): key = "{}/{}".format(proxy.parent.path, proxy.path) if key in self.editorTabs.projectTabs: self.editorTabs.projectTabs[key].widget.editor.updateTrie() self.editorTabs.removeChangeIdentificator(proxy) def saveAllFiles(self, projectProxy=None): couldNotBeSaved = [] for i in range(len(self.editorTabs.tabs)): fileProxy = self.editorTabs.tabs[i] try: if fileProxy and fileProxy.hasUnsavedChanges: if projectProxy and fileProxy.parent.path != projectProxy.path: continue with open(fileProxy.getFilePath(), 'w') as file: file.write(fileProxy.text) fileProxy.hasUnsavedChanges = False self.updateEditorTrie(fileProxy) except: couldNotBeSaved.append(fileProxy.path) self.saveWorkspaceAction() if couldNotBeSaved: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("The following file(s) could not be saved: {}".format( ','.join(couldNotBeSaved))) msg.setWindowTitle("File save error") msg.exec_() def saveFileAction(self): if len(self.editorTabs.tabs): proxy = self.editorTabs.getCurrentFileProxy() if proxy and proxy.hasUnsavedChanges: try: with open(proxy.getFilePath(), 'w') as file: file.write(proxy.text) proxy.hasUnsavedChanges = False self.updateEditorTrie(proxy) except: msg = QMessageBox() msg.setStyleSheet( "background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "The following file could not be saved: {}".format( proxy.path)) msg.setWindowTitle("File save error") msg.exec_() self.saveWorkspaceAction() return True def openFileAction(self, fileName: FileProxy): text = None # if fileName.text: # return fileName.text with open(fileName.getFilePath(), 'r') as file: text = file.read() return text def keyPressEvent(self, event): if event.modifiers() == Qt.ControlModifier: if event.key() == Qt.Key_Tab: self.showTabSwitcher() elif event.key() == Qt.Key_E: self.showProjectSwitcher() super(AsemblerIDE, self).keyPressEvent(event) def showProjectSwitcher(self): if self.projectSwitcher.isHidden() and len( self.configurationManager.allProjects): self.projectSwitcher.showSwitcher() self.projectSwitcher.setFocus() def showTabSwitcher(self): if self.tabSwitcher.isHidden() and len(self.editorTabs.tabs): self.tabSwitcher.showSwitcher() self.tabSwitcher.setFocus()
class View(QMainWindow): """View component of the MVC application. Presents the state of the application as well as the available means of interaction. Receives updates about the state from the Model and informs Controller about user interactions. """ def __init__(self, model, controller): """Define GUI elements and their layout. Parameters ---------- model : QObject Model component of the MVC application. controller : QObject Controller component of the MVC application. """ super().__init__() self._model = model self._controller = controller self.segmentcursor = False self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"} self.setWindowTitle("biopeaks") self.setGeometry(50, 50, 1750, 750) self.setWindowIcon(QIcon(":/python_icon.png")) # Figure for biosignal. self.figure0 = Figure() self.canvas0 = FigureCanvas(self.figure0) # Enforce minimum height, otherwise resizing with self.splitter causes # mpl to throw an error because figure is resized to height 0. The # widget can still be fully collapsed with self.splitter. self.canvas0.setMinimumHeight(1) # in pixels self.ax00 = self.figure0.add_subplot(1, 1, 1) self.ax00.set_frame_on(False) self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25) self.line00 = None self.scat = None self.segmentspan = None # Figure for marker. self.figure1 = Figure() self.canvas1 = FigureCanvas(self.figure1) self.canvas1.setMinimumHeight(1) self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00) self.ax10.get_xaxis().set_visible(False) self.ax10.set_frame_on(False) self.figure1.subplots_adjust(left=0.04, right=0.98) self.line10 = None # Figure for statistics. self.figure2 = Figure() self.canvas2 = FigureCanvas(self.figure2) self.canvas2.setMinimumHeight(1) self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00) self.ax20.get_xaxis().set_visible(False) self.ax20.set_frame_on(False) self.line20 = None self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00) self.ax21.get_xaxis().set_visible(False) self.ax21.set_frame_on(False) self.line21 = None self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00) self.ax22.get_xaxis().set_visible(False) self.ax22.set_frame_on(False) self.line22 = None self.figure2.subplots_adjust(left=0.04, right=0.98) self.navitools = CustomNavigationToolbar(self.canvas0, self) # Peak editing. self.editcheckbox = QCheckBox("editable", self) self.editcheckbox.stateChanged.connect(self._model.set_peakseditable) # Peak saving during batch processing. self.savecheckbox = QCheckBox("save during batch processing", self) self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks) # Peak auto-correction during batch processing. self.correctcheckbox = QCheckBox("correct during batch processing", self) self.correctcheckbox.stateChanged.connect( self._model.set_correctbatchpeaks) # Selection of stats for saving. self.periodcheckbox = QCheckBox("period", self) self.periodcheckbox.stateChanged.connect( lambda: self.select_stats("period")) self.ratecheckbox = QCheckBox("rate", self) self.ratecheckbox.stateChanged.connect( lambda: self.select_stats("rate")) self.tidalampcheckbox = QCheckBox("tidal amplitude", self) self.tidalampcheckbox.stateChanged.connect( lambda: self.select_stats("tidalamp")) # Channel selection. self.sigchanmenulabel = QLabel("biosignal") self.sigchanmenu = QComboBox(self) self.sigchanmenu.addItem("A1") self.sigchanmenu.addItem("A2") self.sigchanmenu.addItem("A3") self.sigchanmenu.addItem("A4") self.sigchanmenu.addItem("A5") self.sigchanmenu.addItem("A6") self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan) self._model.set_signalchan( self.sigchanmenu.currentText()) # initialize with default value self.markerchanmenulabel = QLabel("marker") self.markerchanmenu = QComboBox(self) self.markerchanmenu.addItem("none") self.markerchanmenu.addItem("I1") self.markerchanmenu.addItem("I2") self.markerchanmenu.addItem("A1") self.markerchanmenu.addItem("A2") self.markerchanmenu.addItem("A3") self.markerchanmenu.addItem("A4") self.markerchanmenu.addItem("A5") self.markerchanmenu.addItem("A6") self.markerchanmenu.currentTextChanged.connect( self._model.set_markerchan) self._model.set_markerchan(self.markerchanmenu.currentText()) # Processing mode. self.batchmenulabel = QLabel("mode") self.batchmenu = QComboBox(self) self.batchmenu.addItem("single file") self.batchmenu.addItem("multiple files") self.batchmenu.currentTextChanged.connect(self._model.set_batchmode) self.batchmenu.currentTextChanged.connect(self.toggle_options) self._model.set_batchmode(self.batchmenu.currentText()) self.toggle_options(self.batchmenu.currentText()) # Modality selection. self.modmenulabel = QLabel("modality") self.modmenu = QComboBox(self) self.modmenu.addItem("ECG") self.modmenu.addItem("PPG") self.modmenu.addItem("RESP") self.modmenu.currentTextChanged.connect(self._model.set_modality) self.modmenu.currentTextChanged.connect(self.toggle_options) self._model.set_modality(self.modmenu.currentText()) self.toggle_options(self.modmenu.currentText()) # Segment selection. This widget can be openend / set visible from # the menu and closed from within itself (see mapping of segmentermap). self.segmentermap = QSignalMapper(self) self.segmenter = QDockWidget("select a segment", self) self.segmenter.setFeatures( QDockWidget.NoDockWidgetFeatures ) # disable closing such that widget can only be closed by confirming selection or custom button regex = QRegExp( "[0-9]*\.?[0-9]{4}") # Limit number of decimals to four validator = QRegExpValidator(regex) self.startlabel = QLabel("start") self.startedit = QLineEdit() self.startedit.setValidator(validator) self.endlabel = QLabel("end") self.endedit = QLineEdit() self.endedit.setValidator(validator) segmentfromcursor = QAction(QIcon(":/mouse_icon.png"), "select with mouse", self) segmentfromcursor.triggered.connect(self.enable_segmentedit) self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition) self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition) self.previewedit = QPushButton("preview segment") lambdafn = lambda: self._model.set_segment( [self.startedit.text(), self.endedit.text()]) self.previewedit.clicked.connect(lambdafn) self.confirmedit = QPushButton("confirm segment") self.confirmedit.clicked.connect(self._controller.segment_dataset) self.confirmedit.clicked.connect(self.segmentermap.map) self.segmentermap.setMapping(self.confirmedit, 0) self.abortedit = QPushButton("abort segmentation") self.abortedit.clicked.connect(self.segmentermap.map) self.segmentermap.setMapping(self.abortedit, 2) # resets the segment to None self.segmenterlayout = QFormLayout() self.segmenterlayout.addRow(self.startlabel, self.startedit) self.segmenterlayout.addRow(self.endlabel, self.endedit) self.segmenterlayout.addRow(self.previewedit) self.segmenterlayout.addRow(self.confirmedit) self.segmenterlayout.addRow(self.abortedit) self.segmenterwidget = QWidget() self.segmenterwidget.setLayout(self.segmenterlayout) self.segmenter.setWidget(self.segmenterwidget) self.segmenter.setVisible(False) self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea) self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter) # Custom file dialog. regex = QRegExp("[1-9][0-9]") validator = QRegExpValidator(regex) self.signallabel = QLabel("biosignal column") self.signaledit = QLineEdit() self.signaledit.setValidator(validator) self.markerlabel = QLabel("marker column") self.markeredit = QLineEdit() self.markeredit.setValidator(validator) regex = QRegExp("[0-9]{2}") validator = QRegExpValidator(regex) self.headerrowslabel = QLabel("number of header rows") self.headerrowsedit = QLineEdit() self.headerrowsedit.setValidator(validator) regex = QRegExp("[0-9]{5}") validator = QRegExpValidator(regex) self.sfreqlabel = QLabel("sampling rate") self.sfreqedit = QLineEdit() self.sfreqedit.setValidator(validator) self.separatorlabel = QLabel("column separator") self.separatormenu = QComboBox(self) self.separatormenu.addItem("comma") self.separatormenu.addItem("tab") self.separatormenu.addItem("colon") self.separatormenu.addItem("space") self.continuecustomfile = QPushButton("continue loading file") self.continuecustomfile.clicked.connect(self.set_customheader) self.customfiledialog = QDialog() self.customfiledialog.setWindowTitle("custom file info") self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png")) self.customfiledialog.setWindowFlags( Qt.WindowCloseButtonHint ) # remove help button by only setting close button self.customfilelayout = QFormLayout() self.customfilelayout.addRow(self.signallabel, self.signaledit) self.customfilelayout.addRow(self.markerlabel, self.markeredit) self.customfilelayout.addRow(self.separatorlabel, self.separatormenu) self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit) self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit) self.customfilelayout.addRow(self.continuecustomfile) self.customfiledialog.setLayout(self.customfilelayout) # Layout. menubar = self.menuBar() signalmenu = menubar.addMenu("biosignal") openSignal = signalmenu.addMenu("load") openEDF = QAction("EDF", self) openEDF.triggered.connect(lambda: self._model.set_filetype("EDF")) openEDF.triggered.connect(self._controller.load_channels) openSignal.addAction(openEDF) openOpenSignals = QAction("OpenSignals", self) openOpenSignals.triggered.connect( lambda: self._model.set_filetype("OpenSignals")) openOpenSignals.triggered.connect(self._controller.load_channels) openSignal.addAction(openOpenSignals) openCustom = QAction("Custom", self) openCustom.triggered.connect( lambda: self._model.set_filetype("Custom")) openCustom.triggered.connect(lambda: self.customfiledialog.exec_()) openSignal.addAction(openCustom) segmentSignal = QAction("select segment", self) segmentSignal.triggered.connect(self.segmentermap.map) self.segmentermap.setMapping(segmentSignal, 1) signalmenu.addAction(segmentSignal) self.segmentermap.mapped.connect(self.toggle_segmenter) saveSignal = QAction("save", self) saveSignal.triggered.connect(self._controller.save_channels) signalmenu.addAction(saveSignal) peakmenu = menubar.addMenu("peaks") findPeaks = QAction("find", self) findPeaks.triggered.connect(self._controller.find_peaks) peakmenu.addAction(findPeaks) autocorrectPeaks = QAction("autocorrect", self) autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks) peakmenu.addAction(autocorrectPeaks) savePeaks = QAction("save", self) savePeaks.triggered.connect(self._controller.save_peaks) peakmenu.addAction(savePeaks) loadPeaks = QAction("load", self) loadPeaks.triggered.connect(self._controller.load_peaks) peakmenu.addAction(loadPeaks) statsmenu = menubar.addMenu("statistics") calculateStats = QAction("calculate", self) calculateStats.triggered.connect(self._controller.calculate_stats) statsmenu.addAction(calculateStats) saveStats = QAction("save", self) saveStats.triggered.connect(self._controller.save_stats) statsmenu.addAction(saveStats) self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.progressBar = QProgressBar(self) self.progressBar.setRange(0, 1) self.statusBar.addPermanentWidget(self.progressBar) self.currentFile = QLabel() self.statusBar.addPermanentWidget(self.currentFile) self.centwidget = QWidget() # contains figures and navigationtoolbar self.setCentralWidget(self.centwidget) self.canvas0.setFocusPolicy( Qt.ClickFocus ) # only widgets (e.g. canvas) that currently have focus capture keyboard input self.canvas0.setFocus() self.canvas0.mpl_connect( "key_press_event", self._controller.edit_peaks ) # connect canvas to keyboard input for peak editing self.canvas0.mpl_connect( "button_press_event", self.get_xcursor) # connect canvas to mouse input for peak editing self.splitter = QSplitter( Qt.Vertical ) # arrange the three figure canvases in splitter object self.splitter.setOpaqueResize( False) # resizing gets very slow otherwise once axes are populated self.splitter.addWidget(self.canvas0) self.splitter.addWidget(self.canvas1) self.splitter.addWidget(self.canvas2) self.splitter.setChildrenCollapsible(False) self.vlayout0 = QVBoxLayout(self.centwidget) self.vlayout1 = QVBoxLayout() self.vlayoutA = QFormLayout() self.vlayoutB = QFormLayout() self.vlayoutC = QVBoxLayout() self.vlayoutD = QVBoxLayout() self.hlayout0 = QHBoxLayout() self.optionsgroupA = QGroupBox("processing options") self.vlayoutA.addRow(self.modmenulabel, self.modmenu) self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu) self.optionsgroupA.setLayout(self.vlayoutA) self.optionsgroupB = QGroupBox("channels") self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu) self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu) self.optionsgroupB.setLayout(self.vlayoutB) self.optionsgroupC = QGroupBox("peaks") self.vlayoutC.addWidget(self.editcheckbox) self.vlayoutC.addWidget(self.savecheckbox) self.vlayoutC.addWidget(self.correctcheckbox) self.optionsgroupC.setLayout(self.vlayoutC) self.optionsgroupD = QGroupBox("select statistics for saving") self.vlayoutD.addWidget(self.periodcheckbox) self.vlayoutD.addWidget(self.ratecheckbox) self.vlayoutD.addWidget(self.tidalampcheckbox) self.optionsgroupD.setLayout(self.vlayoutD) self.vlayout1.addWidget(self.optionsgroupA) self.vlayout1.addWidget(self.optionsgroupB) self.vlayout1.addWidget(self.optionsgroupC) self.vlayout1.addWidget(self.optionsgroupD) self.optionsgroupwidget = QWidget() self.optionsgroupwidget.setLayout(self.vlayout1) self.optionsgroup = QDockWidget("configurations", self) self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea) self.toggleoptionsgroup = self.optionsgroup.toggleViewAction() self.toggleoptionsgroup.setText("show/hide configurations") menubar.addAction(self.toggleoptionsgroup) self.optionsgroup.setWidget(self.optionsgroupwidget) self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup) self.vlayout0.addWidget(self.splitter) self.hlayout0.addWidget(self.navitools) self.vlayout0.addLayout(self.hlayout0) # Subscribe to updates from the Model. self._model.signal_changed.connect(self.plot_signal) self._model.marker_changed.connect(self.plot_marker) self._model.peaks_changed.connect(self.plot_peaks) self._model.period_changed.connect(self.plot_period) self._model.rate_changed.connect(self.plot_rate) self._model.tidalamp_changed.connect(self.plot_tidalamp) self._model.path_changed.connect(self.display_path) self._model.segment_changed.connect(self.plot_segment) self._model.status_changed.connect(self.display_status) self._model.progress_changed.connect(self.display_progress) self._model.model_reset.connect(self.reset_plot) def plot_signal(self, signal): """Plot the biosignal. Receives updates in signal from Model. Parameters ---------- signal : ndarray of float Vector representing the biosignal. See Also -------- model.Model.signal """ self.ax00.clear() self.ax00.relim() self.navitools.update() # reset navitools history self.line00 = self.ax00.plot(self._model.sec, signal, zorder=1) self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy") self.canvas0.draw() def plot_peaks(self, peaks): """Plot the extrema. Receives updates in peaks from Model. Parameters ---------- peaks : ndarray of int Vector representing the extrema. See Also -------- model.Model.peaks """ if self.ax00.collections: # self.scat is listed in ax.collections self.ax00.collections[0].remove() self.scat = self.ax00.scatter(self._model.sec[peaks], self._model.signal[peaks], c="m", zorder=2) self.canvas0.draw() def plot_segment(self, segment): """Show preview of segment. Receives updates in segment from Model. Parameters ---------- segment : list of float The start and end of the segment in seconds. See Also -------- model.Model.segment """ if segment is None: # if an invalid segment has been selected reset the segmenter interface self.toggle_segmenter(1) return if self.ax00.patches: # self.segementspan is listed in ax.patches self.ax00.patches[0].remove() self.segmentspan = self.ax00.axvspan(segment[0], segment[1], color="m", alpha=0.25) self.canvas0.draw() self.confirmedit.setEnabled(True) def plot_marker(self, marker): """Plot the marker channel. Receives updates in marker from Model. Parameters ---------- marker : list of ndarray Seconds element is vector representing the marker channel and first element is a vector representing the seconds associated with each sample in the marker channel. See Also -------- model.Model.marker """ self.ax10.clear() self.ax10.relim() self.line10 = self.ax10.plot(marker[0], marker[1]) self.canvas1.draw() def plot_period(self, period): """Plot instantaneous period. Receives updates in period from Model. Parameters ---------- period : ndarray of float Vector representing the instantaneous period. See Also -------- model.Model.periodintp """ self.ax20.clear() self.ax20.relim() self.navitools.home() if self._model.savestats["period"]: self.line20 = self.ax20.plot(self._model.sec, period, c="m") else: self.line20 = self.ax20.plot(self._model.sec, period) self.ax20.set_ylim(bottom=min(period), top=max(period)) self.ax20.set_title("period", pad=0, fontweight="heavy") self.ax20.grid(True, axis="y") self.navitools.update() self.canvas2.draw() def plot_rate(self, rate): """Plot instantaneous rate. Receives updates in rate from Model. Parameters ---------- rate : ndarray of float Vector representing the instantaneous rate. See Also -------- model.Model.rateintp """ self.ax21.clear() self.ax21.relim() self.navitools.home() if self._model.savestats["rate"]: self.line21 = self.ax21.plot(self._model.sec, rate, c="m") else: self.line21 = self.ax21.plot(self._model.sec, rate) self.ax21.set_ylim(bottom=min(rate), top=max(rate)) self.ax21.set_title("rate", pad=0, fontweight="heavy") self.ax21.grid(True, axis="y") self.navitools.update() self.canvas2.draw() def plot_tidalamp(self, tidalamp): """Plot instantaneous tidal amplitude. Receives updates in tidal amplitude from Model. Parameters ---------- tidalamp : ndarray of float Vector representing the instantaneous tidal amplitude. See Also -------- model.Model.tidalampintp """ self.ax22.clear() self.ax22.relim() self.navitools.home() if self._model.savestats["tidalamp"]: self.line22 = self.ax22.plot(self._model.sec, tidalamp, c="m") else: self.line22 = self.ax22.plot(self._model.sec, tidalamp) self.ax22.set_ylim(bottom=min(tidalamp), top=max(tidalamp)) self.ax22.set_title("amplitude", pad=0, fontweight="heavy") self.ax22.grid(True, axis="y") self.navitools.update() self.canvas2.draw() def display_path(self, path): """Display the path to the current dataset. Receives update in path from Model. Parameters ---------- path : str The path to the file containing the current dataset. See Also -------- model.Model.rpathsignal """ self.currentFile.setText(path) def display_status(self, status): """Display a status message. Receives updates in status message from Model. Parameters ---------- status : str A status message. See Also -------- model.Model.status """ self.statusBar.showMessage(status) def display_progress(self, progress): """Display task progress. Receives updates in progress from Model. Parameters ---------- progress : int Integer indicating the current task progress. See Also -------- model.Model.progress, controller.Worker, controller.threaded """ self.progressBar.setRange( 0, progress) # indicates busy state if progress is 0 def toggle_segmenter(self, visibility_state): """Toggle visibility of segmenter widget. Parameters ---------- visibility_state : int Update in state of the segmenter widget's visibility. """ if not self._model.loaded: return if visibility_state == 1: # open segmenter when called from signalmenu or clear segmenter upon selection of invalid segment self.segmenter.setVisible(True) self.confirmedit.setEnabled(False) self.startedit.clear() self.endedit.clear() elif visibility_state == 0: # close segmenter after segment has been confirmed self.segmenter.setVisible(False) elif visibility_state == 2: # close segmenter after segmentation has been aborted (reset segment) self._model.set_segment([0, 0]) self.segmenter.setVisible(False) if self.ax00.patches: self.ax00.patches[0].remove() self.canvas0.draw() def enable_segmentedit(self): """Associate cursor position with a specific segmenter text field. Regulate if cursor position is associated with editing the start or end of a segment. """ self.editcheckbox.setChecked( False) # disable peak editing to avoid interference if self.startedit.hasFocus(): self.segmentcursor = "start" elif self.endedit.hasFocus(): self.segmentcursor = "end" def get_xcursor(self, mouse_event): """Retrieve input to segmenter text fields from cursor position. Retrieve the start or end of a segment in seconds from the current cursor position. Parameters ---------- mouse_event : MouseEvent Event containing information about the current cursor position in data coordinates. See Also -------- matplotlib.backend_bases.MouseEvent """ if mouse_event.button != 1: # 1 = left mouse button return if self.segmentcursor == "start": self.startedit.selectAll() self.startedit.insert("{:.2f}".format( mouse_event.xdata)) # limit number of decimal places to two elif self.segmentcursor == "end": self.endedit.selectAll() self.endedit.insert("{:.2f}".format(mouse_event.xdata)) self.segmentcursor = False # disable segment cursor again after value has been set def set_customheader(self): """Populate the customheader with inputs from the customfiledialog.""" mandatoryfields = self.signaledit.text() and self.headerrowsedit.text( ) and self.sfreqedit.text( ) # check if one of the mandatory fields is missing if not mandatoryfields: self._model.status = ( "Please provide values for 'biosignal column'" ", 'number of header rows' and 'sampling" " rate'.") return seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "} self._model.customheader = dict.fromkeys( self._model.customheader, None ) # reset header here since it cannot be reset in controller.load_chanels self._model.customheader["signalidx"] = int(self.signaledit.text()) self._model.customheader["skiprows"] = int(self.headerrowsedit.text()) self._model.customheader["sfreq"] = int(self.sfreqedit.text()) self._model.customheader["separator"] = seps[ self.separatormenu.currentText()] if self.markeredit.text(): # not mandatory self._model.customheader["markeridx"] = int(self.markeredit.text()) self.customfiledialog.done(QDialog.Accepted) # close the dialog window self._controller.load_channels() # move on to file selection def select_stats(self, statistic): """Select statistics to be saved. Parameters ---------- statistic : str The selected statistic. """ self._model.savestats[ statistic] ^= True # toggle boolean with xor operator line = None if statistic == "period": if self.line20: line = self.line20[0] elif statistic == "rate": if self.line21: line = self.line21[0] elif statistic == "tidalamp": if self.line22: line = self.line22[0] if line: line.set_color(self.togglecolors[line.get_color()]) self.canvas2.draw() def toggle_options(self, state): """Toggle availability of configuration options. Based on current state. Parameters ---------- state : str The aspect of the current state to which the availability of configuration options needs to be adapted. """ if state in ["ECG", "PPG"]: self.tidalampcheckbox.setEnabled(False) self.tidalampcheckbox.setChecked(False) self.ax22.set_visible(False) self.canvas2.draw() elif state == "RESP": self.tidalampcheckbox.setEnabled(True) self.ax22.set_visible(True) self.canvas2.draw() elif state == "multiple files": self.editcheckbox.setEnabled(False) self.editcheckbox.setChecked(False) self.savecheckbox.setEnabled(True) self.correctcheckbox.setEnabled(True) self.markerchanmenu.setEnabled(False) elif state == "single file": self.editcheckbox.setEnabled(True) self.markerchanmenu.setEnabled(True) self.savecheckbox.setEnabled(False) self.savecheckbox.setChecked(False) self.correctcheckbox.setEnabled(False) self.correctcheckbox.setChecked(False) def reset_plot(self): """Reset plot elements associated with the current dataset.""" self.ax00.clear() self.ax00.relim() self.line00 = None self.scat = None self.segmentspan = None self.ax10.clear() self.ax10.relim() self.line10 = None self.ax20.clear() self.ax20.relim() self.line20 = None self.ax21.clear() self.ax21.relim() self.line21 = None self.ax22.clear() self.ax22.relim() self.line22 = None self.canvas0.draw() self.canvas1.draw() self.canvas2.draw() self.navitools.update() self.currentFile.clear()