class StatusDemo(QMainWindow): def __init__(self): super(StatusDemo, self).__init__() self.initUi() def initUi(self): bar = self.menuBar() file = bar.addMenu("File") file.addAction("show") file.addAction("add") file.addAction("remove") file.triggered[QAction].connect(self.processTrigger) self.setCentralWidget(QTextEdit()) self.statusBar = QStatusBar() self.b = QPushButton("click here") self.setWindowTitle("QStatusBar Example") self.setStatusBar(self.statusBar) self.show() def processTrigger(self, q): if (q.text() == "show"): self.statusBar.showMessage(q.text() + " is clicked", 2000) if q.text() == "add": self.statusBar.addWidget(self.b) if q.text() == "remove": self.statusBar.removeWidget(self.b) self.statusBar.show()
class Main(QMainWindow): def __init__(self, app, palette, editor, parent=None): super().__init__(parent) self.editor = editor # Current config chosen (can be one of 3 config<N>.json) self.onStart(choiceIndex) # Initializing config options self.status = QStatusBar( self ) # Status bar for displaying useful info like update found etc # Initializing the main widget where text is displayed self.tab = Tabs(self.cleanOpen, app, palette, self) # self.tabsOpen = [] self.pic_opened = False # This is used to open pictures but right now that feature is disabled self.dialog = MessageBox( self ) # Handles dialogs, for now it only creates the create new project dialog self.setWindowIcon(QIcon("resources/Python-logo-notext.svg_.png") ) # Setting the window icon self.setWindowTitle("Hydra") # Setting the window title self.status_font = QFont( editor["statusBarFont"], editor["statusBarFontSize"]) # Status bar font self.os = platform.system() self.tab.tabs.currentChanged.connect( self.fileNameChange ) # To change the title of the window when tab changes self.search = DocumentSearch( ) # To find documents in the whole system, also not quite working today # Initializing QActions that can be triggered from a QMenu or via keyboard shortcuts self.openterm() self.openterminal() # self.split2Tabs() self.new() self.newProject() self.findDocument() self.openProjectF() self.open() self.save() self.saveAs() self.exit() self.thread = UpdateThread( ) # Update checking runs on its own thread to prevent main GUI from blocking self.thread.start() # Data retrieved from the update thread gets processed check_updates self.thread.textSignal.connect(self.check_updates) # Attributes to manage opening directories and such self.dir_opened = False self._dir = None self.update_progress = QProgressBar() self.update_progress.setMaximumWidth(225) self.update_progress.setStyleSheet(self.update_progress.styleSheet()) self.setCentralWidget(self.tab) # QMainWindow's central widget self.files = None # Tracking the current file that is open self.dead_code_thread = DeadCodeCheker() # This checks for dead code self.dead_code_thread.infoSignal.connect(self.write_dead_code_info) self.stack = [] # Used for tracking when to check for dead code self.tab_to_write_to = None self.tagInfo = StatusLabel(text="", font=self.status_font) self.initUI() # Main UI def write_dead_code_info(self, text): self.tab.events.info_bar.setText(text) def check_updates(self, text): """ A function to check for updates and ask the user if they want to update or not """ self.update_label = QLabel() self.update_label.setFont( QFont(self.editor["generalFont"], self.editor["generalFontSize"])) self.update_label.setFont(self.status_font) self.update_label.setText(text) self.status.addWidget(self.update_label) if text != "An update is available, would you like to update?": pass else: self.button = QPushButton("Update") self.button.setFont( QFont(self.editor["generalFont"], self.editor["generalFontSize"])) self.status.addWidget(self.button) self.button.clicked.connect(self.update_Hydra) def update_Hydra(self): """ This function gets used when the user wants to update Hydra This function is not finished so it doesn't do any updating """ self.update_label.setText("Updating...") self.status.removeWidget(self.button) self.status.addWidget(self.update_progress) for i in range(101): self.update_progress.setValue(i) QTest.qWait(random.randint(50, 75)) # make_decision(True) def fileNameChange(self): try: currentFileName = self.tab.tabs.currentWidget().baseName self.setWindowTitle("Hydra ~ " + str(currentFileName)) except AttributeError: self.setWindowTitle("Hydra ~ ") def onStart(self, index): try: editor = configs[index]["editor"] if editor["windowStaysOnTop"] is True: self.setWindowFlags(Qt.WindowStaysOnTopHint) else: pass except Exception as err: pass # log exception self.font = QFont() self.font.setFamily(self.editor["editorFont"]) self.font.setPointSize(self.editor["editorFontSize"]) self.tabSize = self.editor["TabWidth"] def initUI(self): self.setStatusBar(self.status) # Initializing the status bar self.font.setFixedPitch(True) menuFont = QFont() menuFont.setFamily(self.editor["menuFont"]) menuFont.setPointSize(self.editor["menuFontSize"]) menu = self.menuBar() menu.setFont(menuFont) # Creating the file menu fileMenu = menu.addMenu("File") # Adding options to the file menu # self.setStatusBar(self.status) fileMenu.addAction(self.newAct) fileMenu.addAction(self.newProjectAct) fileMenu.addAction(self.openAct) fileMenu.addAction(self.openProjectAct) fileMenu.addAction(self.saveAct) fileMenu.addAction(self.saveAsAct) fileMenu.addSeparator() fileMenu.addAction(self.exitAct) toolMenu = menu.addMenu("Tools") toolMenu.addAction(self.openTermAct) toolMenu.addAction(self.openTerminalAct) # toolMenu.addAction(self.split2TabsAct) searchDoc = menu.addMenu("Find document") searchDoc.addAction(self.findDocumentAct) self.showMaximized() def open(self): self.openAct = QAction("Open...", self) self.openAct.setShortcut("Ctrl+O") self.openAct.setStatusTip("Open a file") self.openAct.triggered.connect(self.openFileFromMenu) def closeEvent(self, QCloseEvent): os._exit(42) # This makes sure every thread gets killed def new(self): self.newAct = QAction("New") self.newAct.setShortcut("Ctrl+N") self.newAct.setStatusTip("Create a new file") self.newAct.triggered.connect(self.newFile) def newProject(self): self.newProjectAct = QAction("New project") self.newProjectAct.setShortcut("Ctrl+Shift+N") self.newProjectAct.setStatusTip("Create a new project") self.newProjectAct.triggered.connect(self.newProjectFolder) def openProjectF(self): self.openProjectAct = QAction("Open project") self.openProjectAct.setShortcut("Ctrl+Shift+O") self.openProjectAct.setStatusTip("Open a project") self.openProjectAct.triggered.connect(self.openProject) def split2Tabs(self): self.split2TabsAct = QAction("Split the first 2 tabs") self.split2TabsAct.setShortcut("Ctrl+Alt+S") self.split2TabsAct.setStatusTip("Splits the first 2 tabs into one tab") self.split2TabsAct.triggered.connect(self.tab.split) def switchTabs(self): if self.tab.tabs.count() - 1 == self.tab.tabs.currentIndex(): self.tab.tabs.setCurrentIndex(0) else: self.tab.tabs.setCurrentIndex(self.tab.tabs.currentIndex() + 1) def save(self): self.saveAct = QAction("Save") self.saveAct.setShortcut("Ctrl+S") self.saveAct.setStatusTip("Save a file") self.saveAct.triggered.connect(self.saveFile) def openterm(self): self.openTermAct = QAction("Run", self) self.openTermAct.setShortcut("Shift+F10") self.openTermAct.setStatusTip("Run your code") self.openTermAct.triggered.connect(self.execute_file) def openterminal(self): self.openTerminalAct = QAction("Terminal", self) self.openTerminalAct.setShortcut("Ctrl+T") self.openTerminalAct.setStatusTip("Open a terminal") self.openTerminalAct.triggered.connect(self.realterminal) def saveAs(self): self.saveAsAct = QAction("Save As...") self.saveAsAct.setShortcut("Ctrl+Shift+S") self.saveAsAct.setStatusTip("Save a file as") self.saveAsAct.triggered.connect(self.saveFileAs) def findDocument(self): self.findDocumentAct = QAction("Find document") self.findDocumentAct.setShortcut("Ctrl+Shift+F") self.findDocumentAct.setStatusTip("Find a document") self.findDocumentAct.triggered.connect(self.temp) def temp(self): pass def findDocumentFunc(self): self.search.run() def exit(self): self.exitAct = QAction("Quit", self) self.exitAct.setShortcut("Ctrl+Q") self.exitAct.setStatusTip("Exit application") self.exitAct.triggered.connect(self.lets_exit) def lets_exit(self): # self.saveFile() qApp.quit() def openFileFromMenu(self): options = QFileDialog.Options() filenames, _ = QFileDialog.getOpenFileNames( self, "Open a file", "", "All Files (*);;Python Files (*.py);;Text Files (*.txt)", options=options, ) if filenames: # If file is selected, we can open it filename = filenames[0] if filename[-3:] in ["gif", "png", "jpg", "bmp" ] or filename[-4:] in ["jpeg"]: self.pic_opened = True self.cleanOpen(filename, self.pic_opened) def openBrowser(self): widget = Browser("https://duckduckgo.com") word = "" index = self.tab.tabs.addTab(widget, "Info about: " + str(word)) self.tab.tabs.setCurrentIndex(index) def cleanOpen(self, filename, pic_opened=False, searchCommand=None): basename = os.path.basename(filename) if os.path.isdir(filename): return if pic_opened: tab = Image(filename, basename) else: tab = Content("", filename, basename, self, False, searchCommand) for index, tab_name in enumerate(self.tab.tabCounter): if ( tab_name == basename ): # If we already have a file open and we're trying to open the same file, then do nothing if searchCommand: print(searchCommand, " search ocmmand") tab.searchFor(searchCommand) return tab.start_opening() # TODO: Only works for NON image files right now label = QLabel("Loading...") label.setAlignment(Qt.AlignCenter) index_to_remove = self.tab.tabs.addTab(label, "") # lmao it works tab.readyToShow.connect( lambda state: self.addTab(state, tab, basename, index_to_remove)) update_previous_file(filename) def addTab(self, state, tab, basename, index_to_remove): """ Removes given tab and adds a new tab and makes it active """ self.tab.tabs.removeTab(index_to_remove) index = self.tab.tabs.addTab(tab, basename) self.tab.tabs.setCurrentIndex(index) self.tab.tabCounter.append(basename) # Not in use def openFile(self, filename): try: for index, tabName in enumerate(self.tab.tabCounter): with open(filename, "r+") as file_o: print("first open") if filename[-3:] in ["gif", "png", "jpg", "bmp" ] or filename[-4:] in ["jpeg"]: self.pic_opened = True else: self.pic_opened = False try: text = file_o.read() except UnicodeDecodeError as E: text = str(E) basename = os.path.basename(filename) if not self.pic_opened: tab = Content(text, filename, basename, self) tab.saved = True tab.modified = False else: tab = Image(filename, basename) if tabName == tab.baseName: self.tab.tabs.removeTab(index) self.tab.tabCounter.remove(tab.baseName) try: with open(filename, "r+") as file_o: try: if self.pic_opened is not True: text = file_o.read() else: text = None except (FileNotFoundError, UnicodeDecodeError, AttributeError) as E: text = str(E) except FileNotFoundError: with open(filename, "w+") as newFileCreated: print("third open") text = newFileCreated.read() basename = os.path.basename(filename) if self.pic_opened is True: tab = Image(filename, basename) else: tab = Content(text, filename, basename, self) # Creating a tab object *IMPORTANT* tab.saved = True tab.modified = False self.tab.tabCounter.append(tab.baseName) dirPath = os.path.dirname(filename) self.files = filename # self.tabsOpen.append(self.files) index = self.tab.tabs.addTab( tab, tab.baseName ) # This is the index which we will use to set the current self.tab.tabs.setTabToolTip(index, str(tab.fileName)) if ( not self.dir_opened ): # If a project isn't opened then we open a directory everytime we open a file self.tab.directory.openDirectory(dirPath) self.tab.showDirectory() else: pass self.tab.setLayout(self.tab.layout) # Finally we set the layout update_previous_file(filename) self.tab.tabs.setCurrentIndex( index) # Setting the index so we could find the current widget self.currentTab = self.tab.tabs.currentWidget() if self.pic_opened is not True: self.currentTab.editor.setFont(self.font) # Setting the font self.currentTab.editor.setFocus( ) # Setting focus to the tab after we open it self.pic_opened = False except ( IsADirectoryError, AttributeError, UnboundLocalError, PermissionError, ) as E: print(E, " on line 346 in the file main.py") def newFile(self): text = "" if self._dir: base_file_name = "Untitled_file_" + str(random.randint( 1, 100)) + ".py" fileName = str(self._dir) + "/" + base_file_name else: base_file_name = "Untitled_file_" + str(random.randint( 1, 100)) + ".py" current = os.getcwd() fileName = current + "/" + base_file_name self.pyFileOpened = True # Creates a new blank file file = Content(text, fileName, base_file_name, self) self.tab.splitterH.addWidget( self.tab.tabs ) # Adding tabs, now the directory tree will be on the left self.tab.tabCounter.append(file.fileName) self.tab.setLayout(self.tab.layout) # Finally we set the layout index = self.tab.tabs.addTab( file, file.baseName ) # addTab method returns an index for the tab that was added self.tab.tabs.setTabToolTip(index, str(file.fileName)) self.tab.tabs.setCurrentIndex( index) # Setting focus to the new tab that we created widget = self.tab.tabs.currentWidget() def newProjectFolder(self): self.dialog = NewProject(self) self.dialog.show() def openProject(self): self._dir = QFileDialog.getExistingDirectory(None, "Select a folder:", "", QFileDialog.ShowDirsOnly) self.tab.directory.openDirectory(self._dir) self.dir_opened = True # Generating tags file self.generateTagFile(self._dir) self.tab.showDirectory() def generateTagFile(self, directoryLocation: str) -> bool: location = shutil.which("ctags") appDir = os.getcwd() if location is None: print( "Please download universal ctags from the website https://github.com/universal-ctags/ctags" ) return False else: os.chdir(directoryLocation) generateProcess = QProcess(self) command = [location, "-R"] generateProcess.start(" ".join(command)) self.tagInfo.setText("Generating tags file...") self.status.addWidget(self.tagInfo, Qt.AlignRight) generateProcess.finished.connect( lambda: self.afterTagGeneration(appDir)) def afterTagGeneration(self, appDir: str) -> None: os.chdir(appDir) print(os.getcwd()) self.status.removeWidget(self.tagInfo) def parseTagFile(self): pass def openProjectWithPath(self, path): self.tab.directory.openDirectory(path) self.dir_opened = True self._dir = path self.tab.showDirectory() def saveFile(self): self.stack.append(1) try: active_tab = self.tab.tabs.currentWidget() if self.tab.tabs.count(): # If a file is already opened # self.save_thread.add_args(active_tab) # self.save_thread.start() active_tab.start_saving() active_tab.saved = True # active_tab.start_from = os.path.getsize(active_tab.fileName) # self.dead_code_thread.add_args(active_tab.editor.toPlainText()) # self.dead_code_thread.start() # TODO: THrow this analyzer into a code analyzer if len(self.stack) > 5: self.dead_code_thread.add_args( active_tab.editor.toPlainText()) self.dead_code_thread.start( ) # TODO: THrow this analyzer into a code analyzer self.stack = [] active_tab.modified = False """f if active_tab.fileName.endswith(".py"): active_tab.editor.updateAutoComplete(active_tab.fileName) """ else: options = QFileDialog.Options() name = QFileDialog.getSaveFileName( self, "Save File", "", "All Files (*);;Python Files (*.py);;Text Files (*.txt)", options=options, ) fileName = name[0] with open(fileName, "w+") as saveFile: active_tab.saved = True active_tab.modified = False # self.tabsOpen.append(fileName) saveFile.write(active_tab.editor.toPlainText()) self.tab.events.look_for_dead_code( active_tab.editor.toPlainText()) saveFile.close() """ if fileName.endswith(".py"): active_tab.editor.updateAutoComplete(active_tab.fileName) """ self.setWindowTitle("Hydra ~ " + str(active_tab.baseName) + " [SAVED]") active_tab.tokenize_file() except Exception as E: print(E, " on line 403 in the file main.py") def choose_python(self): return sys.executable def saveFileAs(self): try: active_tab = self.tab.tabs.currentWidget() if active_tab is not None: active_index = self.tab.tabs.currentIndex() options = QFileDialog.Options() name = QFileDialog.getSaveFileName( self, "Save File", "", "All Files (*);;Python Files (*.py);;Text Files (*.txt)", options=options, ) fileName = name[0] with open(fileName, "w+") as saveFile: active_tab.saved = True active_tab.modified = False # self.tabsOpen.append(fileName) try: baseName = os.path.basename(fileName) except AttributeError: print("All tabs closed") saveFile.write(active_tab.editor.toPlainText()) text = active_tab.editor.toPlainText() newTab = Content(str(text), fileName, baseName, self) newTab.ready = True self.tab.tabs.removeTab( active_index ) # When user changes the tab name we make sure we delete the old one index = self.tab.tabs.addTab( newTab, newTab.baseName) # And add the new one! self.tab.tabs.setTabToolTip(index, str(newTab.fileName)) self.tab.tabs.setCurrentIndex(index) newActiveTab = self.tab.tabs.currentWidget() newActiveTab.editor.setFont(self.font) newActiveTab.editor.setFocus() saveFile.close() self.setWindowTitle("Hydra ~ " + str(active_tab.baseName) + " [SAVED]") else: print("No file opened") except FileNotFoundError: print("File dialog closed") def realterminal(self): """ Checking if the file executing widget already exists in the splitter layout: If it does exist, then we're going to replace the widget with the terminal widget, if it doesn't exist then just add the terminal widget to the layout and expand the splitter. """ if self.tab.splitterV.indexOf(self.tab.Console) == 1: self.tab.splitterV.replaceWidget( self.tab.splitterV.indexOf(self.tab.Console), self.tab.terminal) self.tab.splitterV.setSizes([400, 10]) else: self.tab.showConsole() def open_documentation(self, data, word): """ Opens documentation for a built in function """ data = data.replace("|", "") index = self.tab.tabs.addTab( Content( data, os.getcwd() + "/" + str(word) + ".doc", str(word) + ".doc", self, True, ), str(word), ) self.tab.tabs.setCurrentIndex(index) def execute_file(self): """ Checking if the terminal widget already exists in the splitter layout: If it does exist, then we're going to replace it, if it doesn't then we're just gonna add our file executer to the layout, expand the splitter and run the file. Then check if the file executer already exists, but is called again to run the file again """ active_tab = self.tab.tabs.currentWidget() python_command = self.choose_python() if self.tab.splitterV.indexOf(self.tab.terminal) == 1: self.tab.splitterV.replaceWidget( self.tab.splitterV.indexOf(self.tab.terminal), self.tab.Console) self.tab.Console.run( "{} ".format(python_command) + active_tab.fileName, active_tab.fileName) self.tab.splitterV.setSizes([400, 10]) elif self.tab.splitterV.indexOf(self.tab.Console) == 1: self.tab.Console.run( "{} ".format(python_command) + active_tab.fileName, active_tab.fileName) self.tab.splitterV.setSizes([400, 10]) else: self.tab.showFileExecuter() self.tab.Console.run( "{} ".format(python_command) + active_tab.fileName, active_tab.fileName) self.tab.splitterV.setSizes([400, 10]) def jumpToDef(self, tagList: list): print(tagList) tagInfo = tagList[0] fileName = tagList[1] searchCommand = tagList[2] self.cleanOpen(fileName, False, searchCommand)
class Main(QMainWindow): def __init__(self, app, palette, editor, parent=None): super().__init__(parent) self.editor = editor self.onStart(choiceIndex) self.status = QStatusBar(self) # Initializing the main widget where text is displayed self.tab = Tabs(self.openFile, app, palette, self) self.tabsOpen = [] self.pic_opened = False self.dialog = MessageBox(self) self.setWindowIcon(QIcon('resources/Python-logo-notext.svg_.png') ) # Setting the window icon self.setWindowTitle('PyPad') # Setting the window title self.status_font = QFont(editor["statusBarFont"], editor["statusBarFontSize"]) self.os = platform.system() self.tab.tabs.currentChanged.connect(self.fileNameChange) self.search = DocumentSearch() self.openterm() self.openterminal() # self.split2Tabs() self.new() self.newProject() self.findDocument() self.openProjectF() self.open() self.save() self.saveAs() self.exit() self.thread = UpdateThread() self.thread.start() self.thread.textSignal.connect(self.check_updates) self.dir_opened = False self._dir = None self.update_progress = QProgressBar() self.update_progress.setMaximumWidth(225) self.update_progress.setStyleSheet(self.update_progress.styleSheet()) self.setCentralWidget(self.tab) self.files = None # Tracking the current file that is open self.cFileOpened = False self.update_process = QProcess() self.initUI() # Main UI def check_updates(self, text): self.update_label = QLabel() self.update_label.setFont( QFont(self.editor["generalFont"], self.editor["generalFontSize"])) self.update_label.setFont(self.status_font) self.update_label.setText(text) self.status.addWidget(self.update_label) if text != "An update is available, would you like to update?": pass else: self.button = QPushButton("Update") self.button.setFont( QFont(self.editor["generalFont"], self.editor["generalFontSize"])) self.status.addWidget(self.button) self.button.clicked.connect(self.update_pypad) def update_pypad(self): self.update_label.setText("Updating...") self.status.removeWidget(self.button) self.status.addWidget(self.update_progress) """ So "updating" means I should first have an executeable or something of that sorts """ for i in range(101): self.update_progress.setValue(i) QTest.qWait(random.randint(50, 75)) # make_decision(True) def fileNameChange(self): try: currentFileName = self.tab.tabs.currentWidget().baseName self.setWindowTitle("PyPad ~ " + str(currentFileName)) except AttributeError: self.setWindowTitle("PyPad ~ ") def onStart(self, index): try: editor = configs[index]['editor'] if editor["windowStaysOnTop"] is True: self.setWindowFlags(Qt.WindowStaysOnTopHint) else: pass except Exception as err: pass # log exception self.font = QFont() self.font.setFamily(self.editor["editorFont"]) self.font.setPointSize(self.editor["editorFontSize"]) self.tabSize = self.editor["TabWidth"] def initUI(self): self.setStatusBar(self.status) # Initializing the status bar self.font.setFixedPitch(True) menuFont = QFont() menuFont.setFamily(self.editor["menuFont"]) menuFont.setPointSize(self.editor['menuFontSize']) menu = self.menuBar() menu.setFont(menuFont) # Creating the file menu fileMenu = menu.addMenu('File') # Adding options to the file menu # self.setStatusBar(self.status) fileMenu.addAction(self.newAct) fileMenu.addAction(self.newProjectAct) fileMenu.addAction(self.openAct) fileMenu.addAction(self.openProjectAct) fileMenu.addAction(self.saveAct) fileMenu.addAction(self.saveAsAct) fileMenu.addSeparator() fileMenu.addAction(self.exitAct) toolMenu = menu.addMenu('Tools') toolMenu.addAction(self.openTermAct) toolMenu.addAction(self.openTerminalAct) # toolMenu.addAction(self.split2TabsAct) searchDoc = menu.addMenu('Find document') searchDoc.addAction(self.findDocumentAct) self.showMaximized() def open(self): self.openAct = QAction('Open...', self) self.openAct.setShortcut('Ctrl+O') self.openAct.setStatusTip('Open a file') self.openAct.triggered.connect(self.openFileFromMenu) def closeEvent(self, QCloseEvent): os._exit(42) # This makes sure every thread gets killed def new(self): self.newAct = QAction('New') self.newAct.setShortcut('Ctrl+N') self.newAct.setStatusTip('Create a new file') self.newAct.triggered.connect(self.newFile) def newProject(self): self.newProjectAct = QAction('New project') self.newProjectAct.setShortcut('Ctrl+Shift+N') self.newProjectAct.setStatusTip('Create a new project') self.newProjectAct.triggered.connect(self.newProjectFolder) def openProjectF(self): self.openProjectAct = QAction('Open project') self.openProjectAct.setShortcut('Ctrl+Shift+O') self.openProjectAct.setStatusTip('Open a project') self.openProjectAct.triggered.connect(self.openProject) def split2Tabs(self): self.split2TabsAct = QAction('Split the first 2 tabs') self.split2TabsAct.setShortcut('Ctrl+Alt+S') self.split2TabsAct.setStatusTip("Splits the first 2 tabs into one tab") self.split2TabsAct.triggered.connect(self.tab.split) def switchTabs(self): if self.tab.tabs.count() - 1 == self.tab.tabs.currentIndex(): self.tab.tabs.setCurrentIndex(0) else: self.tab.tabs.setCurrentIndex(self.tab.tabs.currentIndex() + 1) def save(self): self.saveAct = QAction('Save') self.saveAct.setShortcut('Ctrl+S') self.saveAct.setStatusTip('Save a file') self.saveAct.triggered.connect(self.saveFile) def openterm(self): self.openTermAct = QAction('Run', self) self.openTermAct.setShortcut('Shift+F10') self.openTermAct.setStatusTip('Run your code') self.openTermAct.triggered.connect(self.execute_file) def openterminal(self): self.openTerminalAct = QAction("Terminal", self) self.openTerminalAct.setShortcut("Ctrl+T") self.openTerminalAct.setStatusTip("Open a terminal") self.openTerminalAct.triggered.connect(self.realterminal) def saveAs(self): self.saveAsAct = QAction('Save As...') self.saveAsAct.setShortcut('Ctrl+Shift+S') self.saveAsAct.setStatusTip('Save a file as') self.saveAsAct.triggered.connect(self.saveFileAs) def findDocument(self): self.findDocumentAct = QAction('Find document') self.findDocumentAct.setShortcut('Ctrl+Shift+F') self.findDocumentAct.setStatusTip('Find a document') self.findDocumentAct.triggered.connect(self.temp) def temp(self): pass def findDocumentFunc(self): self.search.run() def exit(self): self.exitAct = QAction('Quit', self) self.exitAct.setShortcut('Ctrl+Q') self.exitAct.setStatusTip('Exit application') self.exitAct.triggered.connect(qApp.quit) def openFileFromMenu(self): options = QFileDialog.Options() filenames, _ = QFileDialog.getOpenFileNames( self, 'Open a file', '', 'All Files (*);;Python Files (*.py);;Text Files (*.txt)', options=options) if filenames: # If file is selected, we can open it filename = filenames[0] if filename[-3:] in ['gif', 'png', 'jpg', 'bmp' ] or filename[-4:] in ['jpeg']: self.pic_opened = True self.openFile(filename) def openBrowser(self, url, word): widget = Browser(url) index = self.tab.tabs.addTab(widget, "Info about: " + str(word)) self.tab.tabs.setCurrentIndex(index) def openFile(self, filename): try: for index, tabName in enumerate(self.tab.tabCounter): with open(filename, 'r+') as file_o: if filename[-3:] in ['gif', 'png', 'jpg', 'bmp' ] or filename[-4:] in ['jpeg']: self.pic_opened = True else: self.pic_opened = False try: text = file_o.read() except UnicodeDecodeError as E: text = str(E) basename = os.path.basename(filename) if not self.pic_opened: tab = Content(text, filename, basename, self) tab.saved = True tab.modified = False else: tab = Image(filename, basename) if tabName == tab.baseName: self.tab.tabs.removeTab(index) self.tab.tabCounter.remove(tab.baseName) try: with open(filename, 'r+') as file_o: try: if self.pic_opened is not True: text = file_o.read() else: text = None except (FileNotFoundError, UnicodeDecodeError, AttributeError) as E: text = str(E) except FileNotFoundError: with open(filename, 'w+') as newFileCreated: text = newFileCreated.read() basename = os.path.basename(filename) if self.pic_opened is True: tab = Image(filename, basename) else: tab = Content(text, filename, basename, self) # Creating a tab object *IMPORTANT* tab.saved = True tab.modified = False self.tab.tabCounter.append(tab.baseName) dirPath = os.path.dirname(filename) self.files = filename self.tabsOpen.append(self.files) index = self.tab.tabs.addTab( tab, tab.baseName ) # This is the index which we will use to set the current self.tab.tabs.setTabToolTip(index, str(tab.fileName)) if not self.dir_opened: # If a project isnt opened then we open a directory everytime we open a file self.tab.directory.openDirectory(dirPath) self.tab.showDirectory() else: pass self.tab.setLayout(self.tab.layout) # Finally we set the layout update_previous_file(filename) self.tab.tabs.setCurrentIndex( index) # Setting the index so we could find the current widget self.currentTab = self.tab.tabs.currentWidget() if self.pic_opened is not True: self.currentTab.editor.setFont(self.font) # Setting the font self.currentTab.editor.setFocus( ) # Setting focus to the tab after we open it self.pic_opened = False except (IsADirectoryError, AttributeError, UnboundLocalError, PermissionError) as E: print(E, " on line 346 in the file main.py") def newFile(self): text = "" if self._dir: base_file_name = "Untitled_file_" + str(random.randint( 1, 100)) + ".py" fileName = str(self._dir) + "/" + base_file_name else: base_file_name = "Untitled_file_" + str(random.randint( 1, 100)) + ".py" current = os.getcwd() fileName = current + "/" + base_file_name self.pyFileOpened = True # Creates a new blank file file = Content(text, fileName, base_file_name, self) self.tab.splitterH.addWidget( self.tab.tabs ) # Adding tabs, now the directory tree will be on the left self.tab.tabCounter.append(file.fileName) self.tab.setLayout(self.tab.layout) # Finally we set the layout index = self.tab.tabs.addTab( file, file.baseName ) # addTab method returns an index for the tab that was added self.tab.tabs.setTabToolTip(index, str(file.fileName)) self.tab.tabs.setCurrentIndex( index) # Setting focus to the new tab that we created widget = self.tab.tabs.currentWidget() def newProjectFolder(self): self.dialog = NewProject(self) self.dialog.show() def openProject(self): self._dir = QFileDialog.getExistingDirectory(None, 'Select a folder:', '', QFileDialog.ShowDirsOnly) self.tab.directory.openDirectory(self._dir) self.dir_opened = True self.tab.showDirectory() def openProjectWithPath(self, path): self.tab.directory.openDirectory(path) self.dir_opened = True self._dir = path self.tab.showDirectory() def saveFile(self): try: active_tab = self.tab.tabs.currentWidget() if self.tab.tabs.count(): # If a file is already opened with open(active_tab.fileName, 'w+') as saveFile: saveFile.write(active_tab.editor.toPlainText()) active_tab.saved = True self.tab.events.look_for_dead_code( active_tab.editor.toPlainText()) active_tab.modified = False saveFile.close() """ if active_tab.fileName.endswith(".py"): active_tab.editor.updateAutoComplete(active_tab.fileName) """ else: options = QFileDialog.Options() name = QFileDialog.getSaveFileName( self, 'Save File', '', 'All Files (*);;Python Files (*.py);;Text Files (*.txt)', options=options) fileName = name[0] with open(fileName, "w+") as saveFile: active_tab.saved = True active_tab.modified = False self.tabsOpen.append(fileName) saveFile.write(active_tab.editor.toPlainText()) self.tab.events.look_for_dead_code( active_tab.editor.toPlainText()) saveFile.close() """ if fileName.endswith(".py"): active_tab.editor.updateAutoComplete(active_tab.fileName) """ self.setWindowTitle("PyPad ~ " + str(active_tab.baseName) + " [SAVED]") active_tab.tokenize_file() except Exception as E: print(E, " on line 403 in the file main.py") def choose_python(self): if self.os == "Windows": return "python" elif self.os == "Linux": return "python3" elif self.os == "Darwin": return "python3" def saveFileAs(self): try: active_tab = self.tab.tabs.currentWidget() if active_tab is not None: active_index = self.tab.tabs.currentIndex() options = QFileDialog.Options() name = QFileDialog.getSaveFileName( self, 'Save File', '', 'All Files (*);;Python Files (*.py);;Text Files (*.txt)', options=options) fileName = name[0] with open(fileName, "w+") as saveFile: active_tab.saved = True active_tab.modified = False self.tabsOpen.append(fileName) try: baseName = os.path.basename(fileName) except AttributeError: print("All tabs closed") saveFile.write(active_tab.editor.toPlainText()) text = active_tab.editor.toPlainText() newTab = Content(str(text), fileName, baseName, self) newTab.ready = True self.tab.tabs.removeTab( active_index ) # When user changes the tab name we make sure we delete the old one index = self.tab.tabs.addTab( newTab, newTab.baseName) # And add the new one! self.tab.tabs.setTabToolTip(index, str(newTab.fileName)) self.tab.tabs.setCurrentIndex(index) newActiveTab = self.tab.tabs.currentWidget() newActiveTab.editor.setFont(self.font) newActiveTab.editor.setFocus() saveFile.close() self.setWindowTitle("PyPad ~ " + str(active_tab.baseName) + " [SAVED]") else: print("No file opened") except FileNotFoundError: print("File dialog closed") def realterminal(self): """ Checking if the file executing widget already exists in the splitter layout: If it does exist, then we're going to replace the widget with the terminal widget, if it doesn't exist then just add the terminal widget to the layout and expand the splitter. """ if self.tab.splitterV.indexOf(self.tab.Console) == 1: self.tab.splitterV.replaceWidget( self.tab.splitterV.indexOf(self.tab.Console), self.tab.terminal) self.tab.splitterV.setSizes([400, 10]) else: self.tab.showConsole() def open_documentation(self, data, word): """ Opens documentation for a built in function """ data = data.replace("|", "") index = self.tab.tabs.addTab( Content(data, os.getcwd() + "/" + str(word) + ".doc", str(word) + ".doc", self, True), str(word)) self.tab.tabs.setCurrentIndex(index) def execute_file(self): """ Checking if the terminal widget already exists in the splitter layout: If it does exist, then we're going to replace it, if it doesn't then we're just gonna add our file executer to the layout, expand the splitter and run the file. Then check if the file executer already exists, but is called again to run the file again """ active_tab = self.tab.tabs.currentWidget() python_command = self.choose_python() if self.tab.splitterV.indexOf(self.tab.terminal) == 1: self.tab.splitterV.replaceWidget( self.tab.splitterV.indexOf(self.tab.terminal), self.tab.Console) self.tab.Console.run( "{} ".format(python_command) + active_tab.fileName, active_tab.fileName) self.tab.splitterV.setSizes([400, 10]) elif self.tab.splitterV.indexOf(self.tab.Console) == 1: self.tab.Console.run( "{} ".format(python_command) + active_tab.fileName, active_tab.fileName) self.tab.splitterV.setSizes([400, 10]) else: self.tab.showFileExecuter() self.tab.Console.run( "{} ".format(python_command) + active_tab.fileName, active_tab.fileName) self.tab.splitterV.setSizes([400, 10])
class ErrorsSettingsWindow(QWidget): """ Window to control PID components errors (proportional and integral). QStatusBar is used to display service messages """ def __init__(self, app, parent=None): """ ErrorsSettingsWindow constructor :param app: parent MainApplication instance :param parent: [optional] parent class """ super(ErrorsSettingsWindow, self).__init__(parent) self.app = app self.setWindowTitle("PID errors settings") self.setWindowIcon(QIcon(util.resource_path('../img/set_errors.png'))) grid = QGridLayout() self.setLayout(grid) self.lineEdits = { 'err_P_limits': { 'min': QLineEdit(), 'max': QLineEdit() }, 'err_I_limits': { 'min': QLineEdit(), 'max': QLineEdit() } } # we have 2 similar parameters but for integral error also declare reset button with the value tooltip for key, name in (('err_P_limits', "P error limits"), ('err_I_limits', "I error limits")): setButton = QPushButton( QIcon(self.style().standardIcon(QStyle.SP_DialogApplyButton)), 'Set') setButton.clicked.connect(functools.partial( self.setErrLimits, key)) hBox = QHBoxLayout() hBox.addWidget(QLabel("Min:")) hBox.addWidget(self.lineEdits[key]['min']) hBox.addWidget(QLabel("Max:")) hBox.addWidget(self.lineEdits[key]['max']) hBox.addWidget(setButton) groupBox = QGroupBox(name) if key == 'err_I_limits': self.resetButton = QPushButton( QIcon(self.style().standardIcon( QStyle.SP_DialogCancelButton)), "Reset I error") self.resetButton.clicked.connect(self.resetIerr) self.event(QEvent(QEvent.ToolTip)) hBox2 = QHBoxLayout() hBox2.addWidget(self.resetButton) vBox = QVBoxLayout() vBox.addLayout(hBox) vBox.addLayout(hBox2) groupBox.setLayout(vBox) else: groupBox.setLayout(hBox) grid.addWidget(groupBox) self.statusBar = QStatusBar() grid.addWidget(self.statusBar) def event(self, event: QEvent) -> bool: """ Overridden method is used to catch QEvent.ToolTip to display current value :param event: QEvent instance :return: bool """ if event.type() == QEvent.ToolTip: self.resetButton.setToolTip(f"Current I error: " + self.app.settings['pid']['valueFormat'] .format(self.app.conn.read('err_I'))) return super(ErrorsSettingsWindow, self).event(event) def show(self): """ Overridden method to update displaying widgets before showing the window itself :return: None """ self.updateDisplayingValues('err_P_limits', 'err_I_limits') super(ErrorsSettingsWindow, self).show() def removeStatusBarWidget(self, widget) -> None: """ Callback function to remove given widget from the status bar after timeout :param widget: widget to remove :return: None """ self.statusBar.removeWidget(widget) def updateDisplayingValues(self, *what) -> None: """ Refresh one or more widgets displaying values :param what: strings representing values names that need to be updated :return: None """ for item in what: valMin, valMax = self.app.conn.read(item) self.lineEdits[item]['min'].setText( self.app.settings['pid']['valueFormat'].format(valMin)) self.lineEdits[item]['max'].setText( self.app.settings['pid']['valueFormat'].format(valMax)) def setErrLimits(self, what: str) -> None: """ 'Set' button clicked slot :param what: string representing PID component error to set (proportional or integral) :return: None """ try: # QDoubleValidator doesn't work in combine with explicitly set value valMin = float(self.lineEdits[what]['min'].text()) valMax = float(self.lineEdits[what]['max'].text()) except ValueError: # user enters not valid number or NaN pass else: if valMax < valMin: resultLabel = QLabel( "<font color='red'>Upper limit value is less than lower</font>" ) else: self.app.conn.write(what, valMin, valMax) resultLabel = QLabel('Success') self.statusBar.addWidget(resultLabel) QTimer().singleShot( STATUSBAR_MSG_TIMEOUT, functools.partial(self.removeStatusBarWidget, resultLabel)) self.updateDisplayingValues(what) def resetIerr(self) -> None: """ Set integral PID component to 0 :return: None """ if self.app.conn.reset_i_err() == remotecontroller.result['ok']: statusLabel = QLabel("I error has been reset") else: statusLabel = QLabel( "<font color='red'>I error reset failed</font>") self.statusBar.addWidget(statusLabel) QTimer().singleShot( STATUSBAR_MSG_TIMEOUT, functools.partial(self.removeStatusBarWidget, statusLabel))
class MainForm(Ui_Main_Form): def __init__(self, form): self.form = form self.setupUi(form) self.set_controls_disabled() # set status bar self.statusBar = QStatusBar() self.statusBar.showMessage("Соединение с базой данных...") form.setStatusBar(self.statusBar) # set checked flags of export settings self.fileRadio.setChecked(True) self.scoresCheckBox.setChecked(True) self.rankCheckBox.setChecked(True) self.levelCheckBox.setChecked(True) # set header of list header = QTreeWidgetItem(["Позывной", "Очки", "Звание", "Уровень"]) self.sostavList.setHeaderItem(header) self.sostavList.header().resizeSection(0, 120) self.sostavList.header().resizeSection(1, 100) self.sostavList.header().resizeSection(2, 95) self.sostavList.header().resizeSection(3, 40) # wait for ssh connecting and select data if database is remote if remote_db: future_ssh = asyncio.ensure_future(run_process()) future_ssh.add_done_callback(self.get_data_remote) else: self.get_data() def get_data(self): self.select_data() self.set_controls_disabled(False) self.ready() self.fill_data() def get_data_remote(self, future): global ssh_process try: ssh_process = future.result() except FileNotFoundError: sys.exit( QMessageBox.about(self.form, "Ошибка", "Не удается найти файл: plink.exe")) return try: # select and bind data self.select_data() except OperationalError: ssh_process.terminate() exit_code = QMessageBox.about( self.form, "Ошибка соединения с базой данных", "Подключение не установлено, т.к. конечный компьютер отверг запрос на подключение" ) sys.exit(exit_code) self.set_controls_disabled(False) self.ready() changes = self.players.count_diff() self.players.save_dump() self.fill_data(changes=changes) def ready(self): self.statusBar.showMessage("Игроков: %d" % len(self.players)) def remove_progress(self): self.statusBar.removeWidget(self.progressBar) def set_controls_disabled(self, disabled=True): self.rank.setDisabled(disabled) self.sostavList.setDisabled(disabled) self.scores.setDisabled(disabled) self.plusScoresButton.setDisabled(disabled) self.plusScoresButton_2.setDisabled(disabled) self.saveButton.setDisabled(disabled) self.saveScoresButton.setDisabled(disabled) self.cancelScoresButton.setDisabled(disabled) self.updateButton.setDisabled(disabled) self.sortButton.setDisabled(disabled) def select_data(self): # select players self.players = PlayersList([ p for p in session.query(Player).order_by( Player.scores.desc()).all() ]) # select and bind ranks self.ranks = [] for rank in session.query(Rank).order_by(Rank.scores).all(): self.ranks.append(rank) self.rank.addItem(rank.name) def fill_data(self, changes=None): # disable gui controls if database is empty if len(self.players) == 0: self.scores.setDisabled(True) self.plusScoresButton.setDisabled(True) self.plusScoresButton_2.setDisabled(True) self.saveButton.setDisabled(True) return # else enable controls self.scores.setDisabled(False) self.plusScoresButton.setDisabled(False) self.plusScoresButton_2.setDisabled(False) self.saveButton.setDisabled(False) # clear list, sort players by scores and fill self.sostavList.clear() self.players.sort(key=lambda x: x.scores, reverse=True) for player in self.players: item = QTreeWidgetItem(self.sostavList, [ player.name, '%.2f' % player.scores, player.rank.name, '%s' % player.level ]) # if players got scores set background and show bonuses if not changes: continue plus_sc = changes.get(player.name) if plus_sc is None: continue znak = "+" if plus_sc < 0: znak = "-" item.setText(1, '%.2f (%s%.2f)' % (player.scores, znak, abs(plus_sc))) for i in range(4): item.setBackground(i, QBrush(QColor('#e8fbb2'))) # set first item selected self.sostavList.setCurrentItem(self.sostavList.topLevelItem(0)) def update_info(self): # get selected player's object index = self.sostavList.currentIndex().row() p = self.players[index] # update group box, line edit and labels self.rank.setCurrentText(p.rank.name) self.scores.setText('%.2f' % p.scores) self.exp.setText('%d' % p.experience) self.kills.setText('%d' % p.kills) self.dies.setText('%d' % p.dies) self.kd.setText('%.2f' % p.kd) self.victories.setText('%d' % p.victories) self.fails.setText('%d' % (p.matches - p.victories)) self.matches.setText('%d' % p.matches) self.winrate.setText(('%.1f' % p.winrate) + '%') self.avgExp.setText('%d' % p.avg_stat) self.lastUpdate.setText(p.last_update.strftime("%d.%m.%Y %H:%M:%S")) def change_rank(self): if len(self.players) == 0: return # get selected player's and rank's objects p_index = self.sostavList.currentIndex().row() r_index = self.rank.currentIndex() p = self.players[p_index] r = self.ranks[r_index] # ignore on scores changed event and on first binding if p.rank.name == r.name or p_index == -1: return # update model and ui p.rank = r p.scores = r.scores self.scores.setText('%.2f' % r.scores) self.sostavList.currentItem().setText(1, '%.2f' % r.scores) self.sostavList.currentItem().setText(2, r.name) def change_score(self): # if scores is empty set zero scores = self.scores.text() if scores == "": scores = 0 self.scores.setText("0.00") # raise exception if value is not float or gt max scores try: scores = float(scores) max = self.ranks[-1].scores if scores > max: raise Exception("Значение очков не может превышать %d" % max) if scores < 0: raise Exception("Значение очков не может быть отрицательным") except ValueError: QMessageBox.about(self.form, "Ошибка ввода", "Значение очков должно быть числом") return except Exception as ex: QMessageBox.about(self.form, "Ошибка ввода", ex.args[0]) return # get selected player's object and new scores p_index = self.sostavList.currentIndex().row() p = self.players[p_index] # update player's scores p.scores = scores self.sostavList.currentItem().setText(1, '%.2f' % scores) # detect new rank self.find_rank(scores) # update player's rank p.rank = self.find_rank(scores) self.rank.setCurrentText(p.rank.name) self.sostavList.currentItem().setText(2, p.rank.name) def find_rank(self, scores): current_rank = None for rank in self.ranks[:-1]: if scores >= rank.scores: current_rank = rank else: break return current_rank def change_add_score(self): # if scores is empty set zero scores = self.scoresAdd.text() if scores == "": scores = 0 self.scoresAdd.setText("0") def plus_minus_scores(self, func): # raise exception if value is not float try: scores = float(self.scoresAdd.text()) except ValueError: QMessageBox.about(self.form, "Ошибка ввода", "Значение очков должно быть числом") else: # sum scores and emit text changed event newScores = func(float(self.scores.text()), scores) self.scores.setText('%.2f' % newScores) self.change_score() def add_scores(self): self.plus_minus_scores(lambda x, y: x + y) def sub_scores(self): self.plus_minus_scores(lambda x, y: x - y) def save(self): if not remote_db: session.commit() return # save all changes asynchronusly and inform user about it future = asyncio.ensure_future(commit(session, self.players)) future.add_done_callback(self.saved) self.statusBar.showMessage("Сохранение изменений") self.set_controls_disabled() def saved(self, _): self.set_controls_disabled(False) self.statusBar.showMessage("Изменения сохранены") QTimer.singleShot(3000, self.ready) def cancel(self): if not remote_db: session.rollback() self.fill_data() return # cancel all changes future = asyncio.ensure_future(rollback(session, self.players)) future.add_done_callback(self.canceled) self.statusBar.showMessage("Откат изменений") self.set_controls_disabled() def canceled(self, future): self.fill_data(changes=future.result()) self.set_controls_disabled(False) self.ready() def export(self): # export data to file or clipboard if self.fileRadio.isChecked(): self.export_to_file() else: self.export_to_clipboard() def generate_export_data(self): # generate export format fmt = '{num}. {name} -' if self.scoresCheckBox.isChecked(): fmt += ' {scores:.2f}' if self.rankCheckBox.isChecked(): fmt += ' {rank}.' if self.levelCheckBox.isChecked(): fmt += ' ({level})' if fmt[-1] == '-': fmt = fmt[:-2] # yield current datetime and formatted player's strings yield "Состав клана на {}".format( datetime.now().strftime("%d.%m.%Y %H:%M")) for i, player in enumerate(self.players): yield fmt.format(num=(i + 1), name=player.name, scores=player.scores, rank=player.rank.name, level=player.level) def export_to_clipboard(self): # generate formatted text and copy to clipboard text = '\n'.join(w_str for w_str in self.generate_export_data()) QClipboard.setText(QApplication.clipboard(), text) # inform user in status bar self.statusBar.showMessage("Скопировано в буфер обмена.") QTimer.singleShot(4000, self.ready) def export_to_file(self): # select path to export path = QFileDialog.getSaveFileName(self.form, 'Выберите файл', '', 'Text File (*.txt)') if path[0] == '': return # write data to selected file with open(path[0], 'w', encoding='utf-8') as file: for w_str in self.generate_export_data(): file.write(w_str + '\n') # inform user in status bar self.statusBar.showMessage("Сохранено: " + path[0]) QTimer.singleShot(4000, self.ready) def update(self): self.statusBar.showMessage("Синхронизация с survarium.pro") future = asyncio.ensure_future(self.update_model()) self.progressBar = QProgressBar() self.statusBar.insertPermanentWidget(0, self.progressBar) self.progressBar.setValue(0) self.updateButton.setDisabled(True) # update model and send requests to get stats async def update_model(self): try: players = await get_players(self) except ConnectionError: self.connection_error() return except HTTPError as err: self.connection_error(msg=err.args[0]) return except Exception: self.connection_error(msg="Неизвестная ошибка") return # delete players who does'n exist in response players_to_delete = filter( lambda x: x.name not in [p['nickname'] for p in players], self.players) for player in list(players_to_delete): self.players.remove(player) session.delete(player) # update players for p in players: try: self.players.update_player(p) except IndexError: # skip all players instead of existing in local db continue # add players who doesn't exist in local storage but had came in response players_to_add = list(filter(lambda p: p not in self.players, players)) # create db objects from json create_player_func = partial(self.players.create_player, self.ranks[0]) players_to_add = list(map(create_player_func, players_to_add)) self.players += players_to_add # get players staistics and recount scores try: updates = await get_stats(self) except ConnectionError: self.connection_error() return except HTTPError as err: self.connection_error(msg=err.args[0]) return except Exception: self.connection_error(msg="Неизвестная ошибка") return # refresh gui QTimer.singleShot(2000, self.remove_progress) self.fill_data(changes=updates) self.ready() self.updateButton.setDisabled(False) if not remote_db: session.commit() return # save changes asynchronusly future = asyncio.ensure_future(commit(session, self.players)) future.add_done_callback(self.saved) self.statusBar.showMessage("Сохранение изменений") self.set_controls_disabled() def connection_error(self, msg=None): if msg is None: msg = "Не удалось установить соединение с survarium.pro" QMessageBox.about(self.form, "Ошибка соединения", msg) self.updateButton.setDisabled(False) self.remove_progress() self.statusBar.showMessage("Соединение прервано") QTimer.singleShot(2000, self.ready)
class WebBrowser(QMainWindow): def __init__(self): super().__init__() # Create lists that will keep track of the new windows, # tabs and urls self.window_list = [] self.list_of_web_pages = [] self.list_of_urls = [] self.initializeUI() def initializeUI(self): self.setMinimumSize(300, 200) self.setWindowTitle("12.6 – Web Browser") self.setWindowIcon(QIcon(os.path.join('images', 'pyqt_logo.png'))) self.positionMainWindow() self.createMenu() self.createToolbar() self.createTabs() self.show() def createMenu(self): """ Set up the menu bar. """ new_window_act = QAction('New Window', self) new_window_act.setShortcut('Ctrl+N') new_window_act.triggered.connect(self.openNewWindow) new_tab_act = QAction('New Tab', self) new_tab_act.setShortcut('Ctrl+T') new_tab_act.triggered.connect(self.openNewTab) quit_act = QAction("Quit Browser", self) quit_act.setShortcut('Ctrl+Q') quit_act.triggered.connect(self.close) # Create the menu bar menu_bar = self.menuBar() menu_bar.setNativeMenuBar(False) # Create file menu and add actions file_menu = menu_bar.addMenu('File') file_menu.addAction(new_window_act) file_menu.addAction(new_tab_act) file_menu.addSeparator() file_menu.addAction(quit_act) self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) def createToolbar(self): """ Set up the navigation toolbar. """ tool_bar = QToolBar("Address Bar") tool_bar.setIconSize(QSize(30, 30)) self.addToolBar(tool_bar) # Create toolbar actions back_page_button = QAction(QIcon(os.path.join('icons', 'back.png')), "Back", self) back_page_button.triggered.connect(self.backPageButton) forward_page_button = QAction(QIcon(os.path.join('icons', 'forward.png')), "Forward", self) forward_page_button.triggered.connect(self.forwardPageButton) refresh_button = QAction(QIcon(os.path.join('icons', 'refresh.png')), "Refresh", self) refresh_button.triggered.connect(self.refreshButton) home_button = QAction(QIcon(os.path.join('icons', 'home.png')), "Home", self) home_button.triggered.connect(self.homeButton) stop_button = QAction(QIcon(os.path.join('icons', 'stop.png')), "Stop", self) stop_button.triggered.connect(self.stopButton) # Set up the address bar self.address_line = QLineEdit() # addAction() is used here to merely display the icon in the line edit. self.address_line.addAction(QIcon('icons/search.png'), QLineEdit.LeadingPosition) self.address_line.setPlaceholderText("Enter website address") self.address_line.returnPressed.connect(self.searchForUrl) tool_bar.addAction(home_button) tool_bar.addAction(back_page_button) tool_bar.addAction(forward_page_button) tool_bar.addAction(refresh_button) tool_bar.addWidget(self.address_line) tool_bar.addAction(stop_button) def createTabs(self): """ Create the QTabWidget object and the different pages. Handle when a tab is closed. """ self.tab_bar = QTabWidget() self.tab_bar.setTabsClosable(True) # Add close buttons to tabs self.tab_bar.setTabBarAutoHide(True) # Hides tab bar when less than 2 tabs self.tab_bar.tabCloseRequested.connect(self.closeTab) # Create tab self.main_tab = QWidget() self.tab_bar.addTab(self.main_tab, "New Tab") # Call method that sets up each page self.setupTab(self.main_tab) self.setCentralWidget(self.tab_bar) def setupWebView(self): """ Create the QWebEngineView object that is used to view web docs. Set up the main page, and handle web_view signals. """ web_view = QWebEngineView() web_view.setUrl(QUrl("https://google.com")) # Create page loading progress bar that is displayed in # the status bar. self.page_load_pb = QProgressBar() self.page_load_label = QLabel() web_view.loadProgress.connect(self.updateProgressBar) # Display url in address bar web_view.urlChanged.connect(self.updateUrl) ok = web_view.loadFinished.connect(self.updateTabTitle) if ok: # Web page loaded return web_view else: print("The request timed out.") def setupTab(self, tab): """ Create individual tabs and widgets. Add the tab's url and web view to the appropriate list. Update the address bar if the user switches tabs. """ # Create the web view that will be displayed on the page. self.web_page = self.setupWebView() tab_v_box = QVBoxLayout() # Sets the left, top, right, and bottom margins to use around the layout. tab_v_box.setContentsMargins(0,0,0,0) tab_v_box.addWidget(self.web_page) # Append new web_page and url to the appropriate lists self.list_of_web_pages.append(self.web_page) self.list_of_urls.append(self.address_line) self.tab_bar.setCurrentWidget(self.web_page) # If user switches pages, update the url in the address to # reflect the current page. self.tab_bar.currentChanged.connect(self.updateUrl) tab.setLayout(tab_v_box) def openNewWindow(self): """ Create new instance of the WebBrowser class. """ new_window = WebBrowser() new_window.show() self.window_list.append(new_window) def openNewTab(self): """ Create new tabs. """ new_tab = QWidget() self.tab_bar.addTab(new_tab, "New Tab") self.setupTab(new_tab) # Update the tab_bar index to keep track of the new tab. # Load the url for the new page. tab_index = self.tab_bar.currentIndex() self.tab_bar.setCurrentIndex(tab_index + 1) self.list_of_web_pages[self.tab_bar.currentIndex()].load(QUrl("https://google.com")) def updateProgressBar(self, progress): """ Update progress bar in status bar. This provides feedback to the user that page is still loading. """ if progress < 100: self.page_load_pb.setVisible(progress) self.page_load_pb.setValue(progress) self.page_load_label.setVisible(progress) self.page_load_label.setText("Loading Page... ({}/100)".format(str(progress))) self.status_bar.addWidget(self.page_load_pb) self.status_bar.addWidget(self.page_load_label) else: self.status_bar.removeWidget(self.page_load_pb) self.status_bar.removeWidget(self.page_load_label) def updateTabTitle(self): """ Update the title of the tab to reflect the website. """ tab_index = self.tab_bar.currentIndex() title = self.list_of_web_pages[self.tab_bar.currentIndex()].page().title() self.tab_bar.setTabText(tab_index, title) def updateUrl(self): """ Update the url in the address to reflect the current page being displayed. """ url = self.list_of_web_pages[self.tab_bar.currentIndex()].page().url() formatted_url = QUrl(url).toString() self.list_of_urls[self.tab_bar.currentIndex()].setText(formatted_url) def searchForUrl(self): """ Make a request to load a url. """ url_text = self.list_of_urls[self.tab_bar.currentIndex()].text() # Append http to url url = QUrl(url_text) if url.scheme() == "": url.setScheme("http") # Request url if url.isValid(): self.list_of_web_pages[self.tab_bar.currentIndex()].page().load(url) else: url.clear() def backPageButton(self): tab_index = self.tab_bar.currentIndex() self.list_of_web_pages[tab_index].back() def forwardPageButton(self): tab_index = self.tab_bar.currentIndex() self.list_of_web_pages[tab_index].forward() def refreshButton(self): tab_index = self.tab_bar.currentIndex() self.list_of_web_pages[tab_index].reload() def homeButton(self): tab_index = self.tab_bar.currentIndex() self.list_of_web_pages[tab_index].setUrl(QUrl("https://google.com")) def stopButton(self): tab_index = self.tab_bar.currentIndex() self.list_of_web_pages[tab_index].stop() def closeTab(self, tab_index): """ This signal is emitted when the close button on a tab is clicked. The index is the index of the tab that should be removed. """ self.list_of_web_pages.pop(tab_index) self.list_of_urls.pop(tab_index) self.tab_bar.removeTab(tab_index) def positionMainWindow(self): """ Use QDesktopWidget class to access information about your screen and use it to position the application window when starting a new application. """ desktop = QDesktopWidget().screenGeometry() screen_width = desktop.width() screen_height = desktop.height() self.setGeometry(0, 0, screen_width, screen_height)
class App(QMainWindow): def __init__(self): super().__init__() self.setWindowIcon(QIcon('icon/pdf.png')) self.initUI() def initUI(self): self.toolBar() self.initWorkSpace() self.initPaint() self.initDev() self.resize(1000, 800) #self.showMaximized() self.center() self.setWindowTitle('pdfSeparator') def initDev(self): self.start = None # Variable for time study of the program def toolBar(self): self.toolbar = self.addToolBar('toolbar') self.toolbar.setMovable(False) # Exit Icon to quit the App self.exitAction = QAction(QIcon('icon/exit.png'), '&Quit', self) self.exitAction.setShortcut('Ctrl+Q') self.exitAction.triggered.connect(self.onExit) self.openAction = QAction(QIcon('icon/open.png'), '&Open file', self) self.openAction.setShortcut('Ctrl+O') self.openAction.triggered.connect(self.open) #self.toolbar.addWidget(QIcon('icon/zoom.png')) self.zoomOutAction = QAction(QIcon('icon/minus.png'), '&Zoom Out', self) self.zoomOutAction.triggered.connect(self.zoomOut) self.zoomOutAction.setEnabled(False) self.zoomInAction = QAction(QIcon('icon/plus.png'), '&Zoom In', self) self.zoomInAction.triggered.connect(self.zoomIn) self.zoomInAction.setEnabled(False) self.fitWindowAction = QAction(QIcon('icon/fitOff.png'), '&Adjust', self) self.fitWindowAction.triggered.connect(self.fitToWindow) self.fitWindowAction.setEnabled(False) self.penAction = QAction(QIcon('icon/penOff.png'), '&Pen', self) self.penAction.triggered.connect(self.onPenStatus) self.penActivationStatus = False self.zoneAction = QAction(QIcon('icon/zoneRectangleOff.png'), '&Zone rectangle', self) self.zoneAction.triggered.connect(self.onZoneStatus) self.zoneActivationStatus = False self.zoneLineAction = QAction(QIcon('icon/zoneLineOff.png'), '&Zone line', self) self.zoneLineAction.triggered.connect(self.onZoneLineStatus) self.zoneLineActivationStatus = False self.zonePointAction = QAction(QIcon('icon/zonePointOff.png'), '&Zone point', self) self.zonePointAction.triggered.connect(self.onZonePointStatus) self.zonePointActivationStatus = False self.extractAction = QAction(QIcon('icon/exportOff.png'), '&Extract area', self) self.extractAction.triggered.connect(self.onExtractActivationStatus) self.extractActivation = False self.checkedAction = QAction(QIcon('icon/checkedOff.png'), '&Sign pdf as processed', self) self.checkedAction.triggered.connect(self.onCheckedActivationStatus) self.checkedActivation = False self.toolbar.addAction(self.exitAction) self.toolbar.addAction(self.openAction) self.toolbar.addAction(self.zoomOutAction) self.toolbar.addAction(self.zoomInAction) self.toolbar.addAction(self.fitWindowAction) self.toolbar.addAction(self.penAction) self.toolbar.addAction(self.zoneAction) self.toolbar.addAction(self.zoneLineAction) self.toolbar.addAction(self.zonePointAction) self.toolbar.addAction(self.extractAction) self.toolbar.addAction(self.checkedAction) self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) def initWorkSpace(self): self.fitWindowBool = False self.label = QLabel() self.label.setBackgroundRole(QPalette.Base) self.label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.label.setScaledContents(True) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.label) self.scrollArea.setVisible(False) self.setCentralWidget(self.scrollArea) def initPaint(self): self.sketch = False self.penWidth = 4 self.penColor = QColor(0, 0, 255) self.pen = QPen(self.penColor, self.penWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) self.lastPoint = QPoint() self.image = None # The history parameter is to remove last element drawn self.history = [] self.historyLength = 10 self.historyShortCut = QShortcut(QKeySequence('Ctrl+Z'), self).activated.connect(self.goBack) self.inEvent = False self.zonePointList = [] self.extractOriginPosition = QPoint() self.isDrawn = False def paintEvent(self, event): painter = QPainter(self) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: if self.sketch: self.lastPoint = self.cropEventPos(event) if not self.inEvent and not self.extractActivation: self.addHistory() if self.zoneActivationStatus or self.zoneLineActivationStatus: self.topCorner = self.lastPoint if self.penActivationStatus or self.zonePointActivationStatus: painter = QPainter(self.image) painter.setPen(self.pen) painter.drawPoint(self.cropEventPos(event)) self.isDrawn = True if self.zonePointActivationStatus: self.zonePointList.append(self.cropEventPos(event)) if len(self.zonePointList) == 2: painter.drawLine(self.zonePointList[0].x(), self.zonePointList[0].y(), self.zonePointList[1].x(), self.zonePointList[1].y()) self.zonePointList.pop(0) self.displayUpdate() if self.extractActivation: self.extractOriginPosition = self.cropEventPos(event) self.onExtract(self.extractOriginPosition) def mouseReleaseEvent(self, event): if (event.button() == Qt.LeftButton) and self.sketch: painter = QPainter(self.image) painter.setPen(self.pen) if self.zoneActivationStatus: position = self.cropEventPos(event) (endCornerX, endCornerY) = (position.x(), position.y()) if endCornerX < 0: endCornerX = 0 elif endCornerX > self.image.width(): endCornerX = self.image.width() if endCornerY < 0: endCornerY = 0 elif endCornerY > self.image.height(): endCornerY = self.image.height() painter.drawRect(self.topCorner.x(), self.topCorner.y(), endCornerX - self.topCorner.x(), endCornerY - self.topCorner.y()) self.isDrawn = True if self.zoneLineActivationStatus: painter.drawLine(self.topCorner.x(), self.topCorner.y(), self.cropEventPos(event).x(), self.cropEventPos(event).y()) self.isDrawn = True self.displayUpdate() self.inEvent = False def mouseMoveEvent(self, event): if (event.buttons() & Qt.LeftButton) and self.sketch: if (self.penActivationStatus): painter = QPainter(self.image) painter.setPen( QPen(self.penColor, self.penWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(self.lastPoint, self.cropEventPos(event)) self.lastPoint = self.cropEventPos(event) self.displayUpdate() self.isDrawn = True def displayUpdate(self): self.update() self.label.setPixmap(self.image) def addHistory(self): if len(self.history) == self.historyLength: self.history.pop(0) self.history.append(self.image.copy()) self.inEvent = True def goBack(self): if len(self.history) > 1: del self.history[-1] self.image = self.history[-1] self.displayUpdate() #self.update() def cropEventPos(self, event): """ Because of the toolbar there is an offset on the y axis. To have the correct position based on the image we need to correct this offset Moreover we need to take in account the scrollbar positions. """ return QPoint( (event.pos().x() + self.scrollArea.horizontalScrollBar().value()) / self.factor, (event.pos().y() - 38 + self.scrollArea.verticalScrollBar().value()) / self.factor) def open(self): options = QFileDialog.Options() filename, _ = QFileDialog.getOpenFileName( self, 'Sélectionner un fichier', '', 'Images (*.pdf *.png *.jpeg *.jpg *.bmp *.gif)', options=options) self.filename = filename if developerMode: self.start = time.time() print('Open and convert file:') print('\tGot filename: ' + filename + ' => ' + str(time.time() - self.start)) if filename[-3:] == 'pdf': file = convert_from_path(filename, 300) filename = 'tmp/' + filename.split('/')[-1][:-3] + 'png' print(filename) file[0].save(filename, 'PNG') if developerMode: print('\tFile converted with success => ' + str(time.time() - self.start)) if filename: image = QImage(filename) if image is None: QMessageBox.information( self, '', "Impossible de charger {}".format(filename)) return self.imagePath = filename self.image = QPixmap.fromImage(image) self.label.resize(self.image.width(), self.image.height()) self.label.setPixmap(self.image) # Insert the image in the history self.history.append(self.image.copy()) self.factor = 1.0 self.sketch = True self.scrollArea.setVisible(True) self.fitWindowAction.setEnabled(True) self.zoomInAction.setEnabled(True) self.zoomOutAction.setEnabled(True) if not self.fitWindowAction.isChecked(): self.label.adjustSize() self.initAdjust() if developerMode: print('\tFile is now adjusted to the window => ' + str(time.time() - self.start)) else: pass # Remove a previous processed image if exists pathProcessed = os.listdir('tmp/') if "processedImage.png" in pathProcessed: os.remove('tmp/processedImage.png') def initAdjust(self): while self.label.height() > self.height() and self.label.width( ) > self.width(): self.scaleImage(0.8) def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.75) def normalSize(self): self.label.adjustSize() self.factor = 1.0 def fitToWindow(self): self.scrollArea.setWidgetResizable(not self.fitWindowBool) print('Before: ', self.fitWindowBool) if self.fitWindowBool: self.fitWindowAction.setIcon(QIcon('icon/fitOff.png')) self.normalSize() else: self.fitWindowAction.setIcon(QIcon('icon/fitOn.png')) self.updateFit() self.fitWindowBool = not self.fitWindowBool def updateFit(self): self.zoomInAction.setEnabled(self.fitWindowBool) self.zoomOutAction.setEnabled(self.fitWindowBool) def scaleImage(self, factor): self.factor *= factor self.label.resize(self.factor * self.label.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAction.setEnabled(self.factor < 10.0) self.zoomOutAction.setEnabled(self.factor > .1) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue( int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep() / 2))) def onExit(self, event): reply = QMessageBox.question(self, '', 'Are you sure you want to quit?', QMessageBox.Cancel | QMessageBox.Ok, QMessageBox.Ok) if reply == QMessageBox.Ok: app.quit() else: pass def onExtractActivationStatus(self): self.disableAllElements(None) if self.extractActivation: self.extractAction.setIcon(QIcon('icon/exportOff.png')) else: self.extractAction.setIcon(QIcon('icon/exportOn.png')) self.extractActivation = not self.extractActivation def pixmapToArray(self, img): channels_count = 4 s = img.bits().asstring(img.width() * img.height() * channels_count) return np.frombuffer(s, dtype=np.uint8).reshape( (img.height(), img.width(), channels_count)) def arrayToPixmap(self, im): gray_color_table = [qRgb(i, i, i) for i in range(256)] if im is None: return QImage() if len(im.shape) == 2: # 1 channel image qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_Indexed8) qim.setColorTable(gray_color_table) return qim elif len(im.shape) == 3: if im.shape[2] == 3: qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_RGB888) return qim elif im.shape[2] == 4: qim = QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QImage.Format_ARGB32) return qim def colorCheck(self, pixelToDetect, colorToDetect): if developerMode: print('\tCheck color to detect => ' + str(time.time() - self.start) + 's') if pixelToDetect is None or (pixelToDetect[0][0] != colorToDetect).all(): pixelToDetect[0][0] = [ colorToDetect[0], colorToDetect[1], colorToDetect[2] ] cv2.imwrite('tmp/colorReference.png', pixelToDetect) if developerMode: print('\tAdd color to detect => ' + str(time.time() - self.start) + ' s') def contours(self, opencvImg): pixel = cv2.imread('tmp/colorReference.png') pixel2 = cv2.cvtColor(pixel, cv2.COLOR_BGR2HSV) boundary = pixel2[0][0] opencvImg = cv2.cvtColor(opencvImg, cv2.COLOR_BGR2HSV) if developerMode: cv2.imwrite('tmp/inter.png', opencvImg) print('\tConversion to HSV => ' + str(time.time() - self.start) + ' s') mask = cv2.inRange(opencvImg, boundary, boundary) _, cnts, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) return opencvImg, mask, cnts def centroids(self, opencvImg, mask, cnts): extremePoints = [] """ The extremPOints list store the extreme points of each polygon and their centroid found in the image. This will be helpfull to crop the image later. """ for c in cnts: M = cv2.moments(c) # calculate x,y coordinate of center cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) mask = cv2.drawContours(mask, [c], -1, (255, 255, 255), -1) if developerMode: cv2.circle(opencvImg, (cX, cY), 5, (0, 0, 255), -1) cv2.putText(opencvImg, "centroid", (cX - 25, cY - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2) x, y, w, h = cv2.boundingRect(c) extremePoints.append([(cX, cY), (x, y, w, h)]) return opencvImg, mask, extremePoints if developerMode: cv2.imwrite('tmp/mask.png', mask) print('\tMask and centroids created => ' + str(time.time() - start) + ' s') def closestArea(self, extremePoints, originX, originY, closestDist, indexClosest, index): """ The centroidX and centroidY parameters are the centroids argument of an element in the extremePoints list. indexCLosest is the index of the closest area in the extremePoints list. This function is recursive so index is the current position in """ if index >= len(extremePoints) or len(extremePoints) == 0: return indexClosest (centroidX, centroidY) = extremePoints[index][0] dist = np.sqrt((originX - centroidX)**2 + (originY - centroidY)**2) if dist < closestDist: closestDist = dist indexClosest = index return self.closestArea(extremePoints, originX, originY, closestDist, indexClosest, index + 1) def onExtract(self, origin): colorToDetect = np.uint8(list(self.penColor.getRgb()[:-1])[::-1]) """ We get only the R,G,B values without the alpha factor and we reverse the list because colorToDetect is RGB and the images are BGR. """ if not self.isDrawn: return None self.progressBar = QProgressBar() self.progressBar.setAlignment(Qt.AlignCenter) self.progressBar.setMaximum(100) self.statusBar.addWidget(self.progressBar) informationLabel = QLabel() informationLabel.setText('Extracting the area...') self.statusBar.addWidget(informationLabel) self.progressBar.setValue(0) if developerMode: self.start = time.time() print('Extraction of the area: ') print('\tBackup of the new image => ' + str(time.time() - self.start) + ' s') img = self.image.toImage() self.opencvImg = self.pixmapToArray(img) cv2.imwrite('tmp/extremeLocation.png', self.opencvImg) if developerMode: print('\tImage saved => ' + str(time.time() - self.start) + ' s') self.progressBar.setValue(20) pixelToDetect = cv2.imread('tmp/colorReference.png') self.colorCheck(pixelToDetect, colorToDetect) self.opencvImg, mask, cnts = self.contours(self.opencvImg) self.opencvImg, mask, extremePoints = self.centroids( self.opencvImg, mask, cnts) self.progressBar.setValue(40) # The origin is the position clicked by the user which is inside the wanted area originX = origin.x() originY = origin.y() if developerMode: cv2.circle(self.opencvImg, (originX, originY), 5, (255, 255, 255), -1) cv2.imwrite('tmp/moments.png', self.opencvImg) """ In the case of several areas, the user can only extract one where he clicked. So we need to find the closest area from the clicked position. """ indexExtremePoints = self.closestArea(extremePoints, originX, originY, self.opencvImg.shape[0], 0, 0) if indexExtremePoints < len(extremePoints): (X, Y, W, H) = extremePoints[indexExtremePoints][1] else: self.statusBar.removeWidget(self.progressBar) self.statusBar.removeWidget(informationLabel) return None self.progressBar.setValue(60) if developerMode: cv2.rectangle(self.opencvImg, (X, Y), (X + W, Y + H), (0, 255, 0), 2) cv2.imwrite('tmp/areaToExtract.png', self.opencvImg) print('\tFind area to extract => ' + str(time.time() - self.start) + ' s') """ Now we have the extreme points of the area, it's time to crop it. """ tmpList = os.listdir('tmp/') if "processedImage.png" in tmpList: fullImage = cv2.imread('tmp/processedImage.png') else: fullImage = cv2.imread(self.imagePath) cropImage = np.zeros([H, W, 3], dtype=np.uint8) cropImage.fill(255) res = cv2.bitwise_and(fullImage, fullImage, mask=mask) self.progressBar.setValue(80) res[mask == 0] = (255, 255, 255) if developerMode: cv2.imwrite('tmp/bitwise.png', res) print('\tApplication of the mask: ' + str(time.time() - self.start) + 's') cropImage = res[Y:Y + H, X:X + W] cv2.imwrite('tmp/croppedImage.png', cropImage) self.progressBar.setValue(100) fullImage[mask == 255] = (188, 185, 196) if developerMode: self.preview = PDF(self, fullImage, dpi, savepath, developerMode, self.start) else: self.preview = PDF(self, fullImage, dpi, savepath) self.preview.setWindowModality(Qt.WindowModal) self.statusBar.removeWidget(self.progressBar) self.statusBar.removeWidget(informationLabel) if len(cnts) == 1: self.isDrawn = False def onPenStatus(self): self.disableAllElements(self.penAction) if self.penActivationStatus: self.penAction.setIcon(QIcon('icon/penOff.png')) else: self.penAction.setIcon(QIcon('icon/penOn.png')) self.penActivationStatus = not self.penActivationStatus def onZoneStatus(self): self.disableAllElements(self.zoneAction) if self.zoneActivationStatus: self.zoneAction.setIcon(QIcon('icon/zoneRectangleOff.png')) else: self.zoneAction.setIcon(QIcon('icon/zoneRectangleOn.png')) self.zoneActivationStatus = not self.zoneActivationStatus def onZoneLineStatus(self): self.disableAllElements(self.zoneLineAction) if self.zoneLineActivationStatus: self.zoneLineAction.setIcon(QIcon('icon/zoneLineOff.png')) else: self.zoneLineAction.setIcon(QIcon('icon/zoneLineOn.png')) self.zoneLineActivationStatus = not self.zoneLineActivationStatus def onZonePointStatus(self): self.disableAllElements(self.zonePointAction) if self.zonePointActivationStatus: self.zonePointAction.setIcon(QIcon('icon/zonePointOff.png')) else: self.zonePointAction.setIcon(QIcon('icon/zonePointOn.png')) self.zonePointActivationStatus = not self.zonePointActivationStatus self.zonePointList = [] def onCheckedActivationStatus(self): msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Information) msgBox.setText("Voulez vous marquer le fichier comme traité") msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) returnValue = msgBox.exec() if returnValue == QMessageBox.Ok: path = '/'.join(self.filename.split('/')[:-1]) newFilename = "/PROCESSED-" if newFilename[1:] + self.filename.split('/')[-1] in os.listdir( path): newFilename += '-'.join( str(datetime.datetime.now()).split('.')[0].split( ' ')) + '-' newFilename += self.filename.split('/')[-1] os.rename(self.filename, path + newFilename) self.disableAllElements(None) self.checkedAction.setIcon(QIcon('icon/checkedOn.png')) def disableAllElements(self, elementClicked): if elementClicked != self.penAction: self.penActivationStatus = False self.penAction.setIcon(QIcon('icon/penOff.png')) if elementClicked != self.zoneAction: self.zoneActivationStatus = False self.zoneAction.setIcon(QIcon('icon/zoneRectangleOff.png')) if elementClicked != self.zoneLineAction: self.zoneLineActivationStatus = False self.zoneLineAction.setIcon(QIcon('icon/zoneLineOff.png')) if elementClicked != self.zonePointAction: self.zonePointActivationStatus = False self.zonePointAction.setIcon(QIcon('icon/zonePointOff.png')) if elementClicked != self.extractAction: self.extractActivation = False self.extractAction.setIcon(QIcon('icon/exportOff.png')) self.zonePointList = [] def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft())
class App(QMainWindow): def __init__(self): super().__init__() self.title = 'EOVSA Imager' self.left = 0 self.top = 0 self.width = 1200 self.height = 900 self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self._main = QWidget() self.setCentralWidget(self._main) self.initUI() self.threadpool = QThreadPool() def initUI(self): self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.progressBar = QProgressBar() self.progressBar.setGeometry(10, 10, 200, 15) layout = QVBoxLayout() # Initialize tab screen self.tabs = QTabWidget() tab1 = QWidget() tab2 = QWidget() tab3 = QWidget() tab4 = QWidget() tab5 = QWidget() tab6 = QWidget() # Add tabs self.tabs.addTab(tab1, "Data Select") self.tabs.addTab(tab2, "Viewer") self.tabs.addTab(tab3, "Imager") self.tabs.addTab(tab4, "Selfcal") self.tabs.addTab(tab5, "Production") self.tabs.addTab(tab6, "Export") # Each tab's user interface is complex, so this splits them into separate functions. self.initUItab1() self.initUItab2() self.initUItab3() self.initUItab4() self.initUItab5() self.initUItab6() self.tabs.currentChanged.connect(self.tabChanged) # Add tabs to widget layout.addWidget(self.tabs) self._main.setLayout(layout) self.show() def tabChanged(self, i): if i == 2: self.update_params() # # Data Select Tab User Interface # def initUItab1(self): # Create main layout (a Vertical Layout) mainlayout = QVBoxLayout() # Create Data Select tab upperbox = QHBoxLayout() # Has two hboxes: leftbox and msinfobox leftbox = QVBoxLayout( ) # Has a gridlayout: filenamebox; and groupBox: selectBox # Filename entry filenamebox = QGridLayout() leftbox.addLayout(filenamebox) # Create LineEdit widget for ms filename self.msentry = QLineEdit() self.fname = '<Select or enter a valid ms filename>' self.msentry.resize(8 * len(self.fname), 20) self.ms = None # No ms yet self.msdata = None # No amplitude data read from ms yet self.msentry.setText(self.fname) self.msentry.returnPressed.connect(self.on_return) filenamebox.addWidget(QLabel("MS Filename"), 0, 0) filenamebox.addWidget(self.msentry, 1, 0, 1, 4) # Create Browse button myButton1 = QPushButton("Browse") myButton1.clicked.connect(self.on_click) filenamebox.addWidget(myButton1, 1, 5) upperbox.addLayout(leftbox) # Create label and TextEdit widget for ms information msinfobox = QVBoxLayout() upperbox.addLayout(msinfobox) self.infoEdit = QTextEdit() #f = QFont("Courier",9) #self.infoEdit.setCurrentFont(f) self.infoEdit.setReadOnly(True) self.infoEdit.setMinimumHeight(300) self.infoEdit.setMinimumWidth(550) msinfobox.addWidget(QLabel("MS Information")) msinfobox.addWidget(self.infoEdit) mainlayout.addLayout(upperbox) # Data Selection selectBox = QGroupBox("Data Selection Criteria") selectarea = QFormLayout() self.trangeEdit = QLineEdit() selectarea.addRow("Timerange", self.trangeEdit) self.spwEdit = QLineEdit() selectarea.addRow("Sp. Window", self.spwEdit) self.baselineEdit = QLineEdit() selectarea.addRow("Baseline", self.baselineEdit) self.stokesEdit = QLineEdit() selectarea.addRow("Stokes", self.stokesEdit) self.uvrangeEdit = QLineEdit() selectarea.addRow("UV range", self.uvrangeEdit) selectBox.setLayout(selectarea) leftbox.addWidget(selectBox) drawselect = QPushButton('Draw Selection on Plot') drawselect.clicked.connect(self.drawsel) leftbox.addWidget(drawselect) leftbox.addStretch(1) #hbox.addStretch(1) playarea = QHBoxLayout() playarea.addStretch(1) xferarea = QVBoxLayout() xferDownRight = QPushButton('Use Selection for Plot Limits') xferDownRight.setIcon(QIcon('icons/down_right.png')) xferDownRight.clicked.connect(self.xferDR) xferarea.addWidget(xferDownRight) xferUpLeft = QPushButton('Use Plot Limits for Selection') xferUpLeft.setIcon(QIcon('icons/up_left.png')) xferUpLeft.clicked.connect(self.xferUL) xferarea.addWidget(xferUpLeft) xferarea.addStretch(1) playarea.addLayout(xferarea) # Add a figure to the canvas. plotarea = QVBoxLayout() self.speccanvas = FigureCanvas(Figure(figsize=(8, 3))) plotarea.addWidget(self.speccanvas) xferarea.addWidget(NavigationToolbar(self.speccanvas, self)) maxmin = QHBoxLayout() minlabel = QLabel('Min[sfu]') maxlabel = QLabel('Max[sfu]') self.minentry = QLineEdit() self.maxentry = QLineEdit() maxmin.addStretch(1) maxmin.addWidget(minlabel) maxmin.addWidget(self.minentry) maxmin.addWidget(maxlabel) maxmin.addWidget(self.maxentry) maxmin.addStretch(1) self.ignore_gaps = QCheckBox('Ignore Gaps') maxmin.addWidget(self.ignore_gaps) maxmin.addStretch(1) self.minentry.returnPressed.connect(self.plot_data) self.maxentry.returnPressed.connect(self.plot_data) plotarea.addLayout(maxmin) playarea.addLayout(plotarea) # self.addToolBar(Qt.BottomToolBarArea, NavigationToolbar(self.speccanvas, self)) self.dspec_ax = self.speccanvas.figure.subplots() baseline_layout = QGridLayout() baseline_layout.setSpacing(1) ant1 = [] ant2 = [] nant = 13 bl2ord = get_bl2ord(nant) # Lookup table for 13-ant baseline matrix nbl = int(nant * (nant + 1) / 2) self.blcheck = [ 0 ] * nbl # This will hold the check box widgets for the baselines self.blchecked = [False] * nbl # This is the state of the buttons self.prev = None for i in range(nant): ant1.append(QPushButton('{:2d}'.format(i + 1))) ant1[-1].setCheckable(True) ant1[-1].setMaximumSize(12, 12) # Ant button size in pixels ant1[-1].setStyleSheet("font : 8px;") ant1[-1].clicked.connect(partial(self.ant_click, ant1[-1])) baseline_layout.addWidget( ant1[-1], 0, i) # Grid location of ant buttons along top ant2.append(QPushButton('{:2d}'.format(i + 1))) ant2[-1].setCheckable(True) ant2[-1].setMaximumSize(12, 12) # Ant button size in pixels ant2[-1].setStyleSheet("font : 8px;") ant2[-1].clicked.connect(partial(self.ant_click, ant2[-1])) baseline_layout.addWidget( ant2[-1], i + 1, nant) # Grid location of ant buttons along side for j in range(i, nant): if i == j: self.blcheck[bl2ord[i, j]] = QCheckBox("") button = self.blcheck[bl2ord[i, j]] # Just saves typing button.setStyleSheet("background-color : #8888ff;") else: self.blcheck[bl2ord[i, j]] = QCheckBox("") button = self.blcheck[bl2ord[i, j]] # Just saves typing button.setStyleSheet("background-color : #ff8888;") button.setMaximumSize(12, 12) button.clicked.connect(self.baselineClicked) baseline_layout.addWidget( button, i + 1, j) # Grid location of baseline buttons self.ant1buttons = ant1 self.ant2buttons = ant2 unsel = QPushButton('Unselect All') unsel.setStyleSheet("font : 10px;") unsel.clicked.connect(partial(self.sel_clicked, False)) sel = QPushButton('Select All') sel.setStyleSheet("font : 10px;") sel.clicked.connect(partial(self.sel_clicked, True)) baseline_layout.addWidget(unsel, 9, 0, 2, 7) baseline_layout.addWidget(sel, 11, 0, 2, 7) space = QVBoxLayout() # Add Polarization Button Group self.polGroup = QButtonGroup() self.polGroup.buttonClicked.connect(self.get_data) xxButton = QRadioButton("XX") xxButton.setChecked(True) yyButton = QRadioButton("YY") xyButton = QRadioButton("XY") yxButton = QRadioButton("YX") rrButton = QRadioButton("RR") llButton = QRadioButton("LL") self.polGroup.addButton(xxButton) self.polGroup.addButton(yyButton) self.polGroup.addButton(xyButton) self.polGroup.addButton(yxButton) self.polGroup.addButton(rrButton) self.polGroup.addButton(llButton) pspace = QHBoxLayout() pspace.addWidget(xxButton) pspace.addWidget(yyButton) pspace.addWidget(xyButton) pspace.addWidget(yxButton) pspace.addWidget(rrButton) pspace.addWidget(llButton) space.addStretch(1) space.addLayout(pspace) space.addWidget(QLabel('Baseline Selection Map')) space.addLayout(baseline_layout) space.addStretch(1) gobutton = QPushButton('Go') gobutton.clicked.connect(self.get_data) gobutton.setStyleSheet("background-color : #ffff88;") space.addWidget(gobutton) playarea.addLayout(space) mainlayout.addLayout(playarea) self.tabs.widget(0).setLayout(mainlayout) # # Viewer Tab User Interface # def initUItab2(self): # Create main layout (a Vertical Layout) mainlayout = QVBoxLayout() mainlayout.addWidget(QLabel("This is the viewer tab")) self.tabs.widget(1).setLayout(mainlayout) # # Imager Tab User Interface # def initUItab3(self): # Create main layout (a Horizontal Layout) mainlayout = QHBoxLayout() # Create a left and right side (both Vertical Layouts) leftlayout = QVBoxLayout() rightlayout = QVBoxLayout() # Create a table for interacting with parameters self.table = QTableView() self.table.doubleClicked.connect(self.tblRowClicked) # For playing with, I will make a static table of tclean parameters tbl = [ ["Data Selection", "", []], [" vis", "''", []], #[" field","''",[]], [ " spw", "''", [], "Spectral Window:\n default: ''=all; examples:\n spw='0~2,4'; spectral windows 0,1,2,4 (all channels)\n spw='0:5~61'; spw 0, channels 5 to 61\n spw='<2'; spectral windows less than 2 (i.e. 0,1)" ], [ " timerange", "''", [], "Range of time to select from data\n default: '' (all); examples:\n timerange = 'YYYY/MM/DD/hh:mm:ss~YYYY/MM/DD/hh:mm:ss'\n Note: if YYYY/MM/DD is missing date defaults to first day in data set\n timerange='09:14:0~09:54:0' picks 40 min on first day\n timerange='25:00:00~27:30:00' picks 1 hr to 3 hr 30 min on NEXT day\n timerange='09:44:00' pick data within one integration of time\n timerange='> 10:24:00' data after this time" ], [ " uvrange", "''", [], "Select data within uvrange (default unit is meters) [default: '' (all)]\n examples:\n uvrange='0~1000klambda'; uvrange from 0-1000 kilo-lambda\n uvrange='> 4klambda';uvranges greater than 4 kilo lambda" ], [ " antenna", "''", [], "Select data on 0-based antenna/baseline index [default: '' (all)]\n examples:\n antenna='0~5,7~12&0~5,7~12'; all baselines not including antenna index 6\n antenna='5&6;7&8'; baselines 5-6 and 7-8\n antenna='5'; all baselines with antenna index 5\n antenna='5,6,9'; all baselines with antenna index numbers 5,6,9" ], [" datacolumn", "'data'", ["'data'", "'corrected'", "'model'"]], ["Image Definition", "", []], [" imagename", "''", []], [" imsize", "[128,128]", []], [" cellsize", "'2arcsec'", []], [ " phaseshift", "[0, 0]", [], "X, Y offset of center of map from Sun Center" ], [ " stokes", "'XX'", ["'XX'", "'YY'", "'I'", "'V'", "'IV'", "'RR'", "'LL'"] ], [" startmodel", "''", []], [" specmode", "'mfs'", ["'mfs'", "'cubedata'"]], ["Deconvolution Options", "", []], [ " deconvolver", "'multiscale'", [ "'hogbom'", "'clark'", "'clarkstokes'", "'multiscale'", "'mem'" ] ], [" scales", "[1,5,10]", []], [" restoringbeam", "''", []], [" pbcor", "False", ['True', 'False']], ["Weighting", "", []], [ " weighting", "'briggs'", ["'natural'", "'uniform'", "'briggs'"] ], [" robust", "0.5", []], [" uvtaper", "''", []], ["Other Options", "", []], [" niter", "0", []], [" gain", "0.1", []], [" threshold", "0", []], [" interactive", "False", ['True', 'False']], [" mask", "''", []] ] self.table.verticalHeader().setDefaultSectionSize( 14) # Sets height of cells to 14px self.table.setModel(TableModel( tbl)) # The TableModel class is defined at the top of this file # For parameters that have to be only a limited set of fixed values, create a combobox dropdown # for editing them. for idx in range(len(tbl)): if len(tbl[idx]) > 2: if len(tbl[idx][2]) > 1: i = self.table.model().index(idx, 2) c = QComboBox() for item in tbl[idx][2]: c.addItem(item) c.currentTextChanged.connect(self.handleCombo) c.index = self.table.model().index(idx, 1) self.table.setIndexWidget(i, c) self.table.resizeColumnsToContents() self.table.model().dataChanged.connect(self.update_view) # Determine the header rows in the table (indicated by NOT starting with a blank space). self.headerrows = [] for i, tblrow in enumerate(tbl): if tbl[i][0][0] != ' ': self.headerrows.append(i) self.table.setSpan(i, 0, 1, 2) self.headerrows.append( len(tbl)) #Add length of table, for finding length of last section titlelayout = QHBoxLayout() titlelayout.addWidget(QLabel("TCLEAN Parameters")) titlelayout.addSpacing(100) updateButton = QPushButton("Update Parameters") titlelayout.addWidget(updateButton) updateButton.clicked.connect(self.update_params) titlelayout.addSpacing(100) scriptButton = QPushButton("Save to CASA Script") titlelayout.addWidget(scriptButton) scriptButton.clicked.connect(self.save2CASAscript) titlelayout.addStretch(1) tablelayout = QHBoxLayout() self.table.setMinimumSize(600, 300) tablelayout.addWidget(self.table) self.nofits = QCheckBox('Skip conversion to FITS') self.nocleanup = QCheckBox('Keep CASA image files') tablelayout.addStretch(1) leftlayout.addLayout(titlelayout) leftlayout.addLayout(tablelayout) leftlayout.addWidget(self.nofits) leftlayout.addWidget(self.nocleanup) self.scriptEdit = QTextEdit() #f = QFont("Courier",9) #self.infoEdit.setCurrentFont(f) self.scriptEdit.setReadOnly(True) self.scriptEdit.setMinimumHeight(300) self.scriptEdit.setMinimumWidth(550) leftlayout.addWidget(QLabel("Generated Script")) leftlayout.addWidget(self.scriptEdit) execButton = QPushButton('Execute Script') execButton.clicked.connect(self.execscript) eblayout = QHBoxLayout() eblayout.addWidget(execButton) eblayout.addStretch(1) leftlayout.addLayout(eblayout) leftlayout.addStretch(1) mainlayout.addLayout(leftlayout) self.imgcanvas = FigureCanvas(Figure(figsize=(7, 6))) rightlayout.addWidget(self.imgcanvas) rightlayout.addWidget(NavigationToolbar(self.imgcanvas, self)) self.img_ax = self.imgcanvas.figure.subplots() mainlayout.addLayout(rightlayout) self.tabs.widget(2).setLayout(mainlayout) def save2CASAscript(self): script = ['from casatasks import tclean'] tbl = self.table.model()._data for k, row in enumerate(tbl): if row[0][0] == ' ': # This is a parameter row. Most such rows can be defined directly, but some require translation param = row[0].lstrip() if param == 'phaseshift': # Determine appropriate phase center and set accordingly try: pshift = [ float(i) for i in row[1].replace('[', '').replace( ']', '').split(',') ] except: self.statusBar.showMessage( 'Error translating phaseshift parameter--using zero', 2000) pshift = [0.0, 0.0] script.append("phasecenter='" + pshift2pcenter(pshift, self.ms) + "'") elif param == 'imagename': if row[1] == "''": msstem = os.path.basename( os.path.splitext(self.fname)[0]) script.append("imagename='images/" + msstem + "'") else: script.append(param + "=" + row[1]) else: if row[1] == "''": script.append(param + "=''") else: script.append(param + "=" + row[1]) script.append( "tclean(vis=vis, selectdata=True, field='', spw=spw, timerange=timerange, uvrange=uvrange, antenna=antenna, scan='', observation='', intent='', datacolumn=datacolumn, imagename=imagename, imsize=imsize, cell=cellsize, phasecenter=phasecenter, stokes=stokes, projection='SIN', startmodel='', specmode=specmode, reffreq='', nchan=-1, start='', width='', outframe='LSRK', veltype='radio', restfreq=[], interpolation='linear', perchanweightdensity=True, gridder='standard', facets=1, psfphasecenter='', chanchunks=1, wprojplanes=1, vptable='', mosweight=True, aterm=True, psterm=False, wbawp=True, conjbeams=False, cfcache='', usepointing=False, computepastep=360.0, rotatepastep=360.0, pointingoffsetsigdev=[], pblimit=0.2, normtype='flatnoise', deconvolver=deconvolver, scales=scales, nterms=2, smallscalebias=0.0, restoration=True, restoringbeam=restoringbeam, pbcor=pbcor, outlierfile='', weighting=weighting, robust=robust, noise='1.0Jy', npixels=0, uvtaper=uvtaper, niter=niter, gain=gain, threshold=threshold, nsigma=0.0, cycleniter=-1, cyclefactor=1.0, minpsffraction=0.05, maxpsffraction=0.8, interactive=interactive, usemask='user', mask=mask, pbmask=0.0, sidelobethreshold=3.0, noisethreshold=5.0, lownoisethreshold=1.5, negativethreshold=0.0, smoothfactor=1.0, minbeamfrac=0.3, cutthreshold=0.01, growiterations=75, dogrowprune=True, minpercentchange=-1.0, verbose=False, fastnoise=True, restart=True, savemodel='none', calcres=True, calcpsf=True, parallel=False)" ) if not self.nofits.isChecked(): script.append("import helioim2fits as hf") script.append( "fitsfile = hf.imreg(vis=vis,imagefile=imagename+'.image',fitsdir='fits',timerange=timerange)" ) if not self.nocleanup.isChecked(): script.append("import glob, shutil") script.append( "for file in glob.glob(imagename+'.*'): shutil.rmtree(file)") if self.nofits.isChecked(): self.statusBar.showMessage( 'Warning! You have selected no FITs output and do not keep CASA images, so you will get no output!', 2000) self.script = script self.scriptEdit.setPlainText('\n'.join(script)) def execscript(self): #print('Before call:') #print(exec) #print(['\n'.join(self.script)]) worker = Worker(exec, '\n'.join(self.script)) worker.signals.finished.connect(self.thread_complete) self.threadpool.start(worker) print('The thread is started--just waiting for the signal.') def thread_complete(self): print('The script execution thread is done!') print( 'The fits file should have been completed, and can be read now...') sleep(1) # Make sure file is closed... list_of_files = glob.glob('fits/*') latest_file = max(list_of_files, key=os.path.getctime) img, h = fits.getdata(latest_file, header=True) img.shape = (h['NAXIS2'], h['NAXIS1']) self.img_ax.cla() xval = np.linspace((h['CRVAL1'] - h['NAXIS1']) * h['CDELT1'] / 2., (h['CRVAL1'] + h['NAXIS1']) * h['CDELT1'] / 2., h['NAXIS1']) yval = np.linspace((h['CRVAL2'] - h['NAXIS2']) * h['CDELT2'] / 2., (h['CRVAL2'] + h['NAXIS2']) * h['CDELT2'] / 2., h['NAXIS2']) im = self.img_ax.pcolormesh(xval, yval, img) self.img_ax.set_aspect('equal') self.img_ax.set_ylabel('Y [arcsec]') self.img_ax.set_xlabel('X [arcsec]') self.img_ax.set_title(latest_file) self.imgcanvas.draw() def update_params(self): ''' Take entries from Data Select tab and update the tclean parameter table ''' tbl = self.table.model()._data params = ['vis', 'spw', 'timerange', 'uvrange', 'antenna', 'stokes'] curvalues = [ self.fname, self.spwEdit.text(), self.trangeEdit.text(), self.uvrangeEdit.text(), self.baselineEdit.text(), self.stokesEdit.text() ] for k, row in enumerate(tbl): r = row[0][2:] # parameter name (after removing two blank spaces) for idx, p in enumerate(params): if r == p: i = self.table.model().index( k, 1) # Table index of parameter value # Write the new value into the table (all of these are strings, so include quotes) self.table.model().setData(i, "'" + curvalues[idx] + "'", Qt.EditRole) if r == 'stokes': # Stokes has a combobox, so set it according to the updated parameter i2 = self.table.model().index( k, 2) # Table index of combobox widget c = self.table.indexWidget(i2) loc = c.findText("'" + curvalues[idx] + "'") if loc == -1: # If curvalue is not found in the combobox, indicate stokes as unset. self.table.model().setData(i, '<unset>', Qt.EditRole) else: c.setCurrentIndex(loc) def update_view(self): self.table.resizeColumnsToContents() def handleCombo(self): sender = self.sender() text = sender.currentText() self.table.model().setData(sender.index, text, Qt.EditRole) def tblRowClicked(self, index): ''' If a header row is double-clicked, show or hide the rows corresponding to that section ''' hrows = self.headerrows # Row numbers corresponding to headers for n, row in enumerate(hrows): if index.row() == row: # The row of the clicked cell is a header row, so identify the lines between it # and the next header and toggle hidden k1 = hrows[n] + 1 k2 = hrows[n + 1] if self.table.isRowHidden(k1): # Rows are aleady hidden, so show them for i in range(k1, k2): self.table.showRow(i) else: # Rows are visible, so hide them for i in range(k1, k2): self.table.hideRow(i) break # # Selfcal Tab User Interface # def initUItab4(self): # Create main layout (a Vertical Layout) mainlayout = QVBoxLayout() mainlayout.addWidget(QLabel("This is the selfcal tab")) self.tabs.widget(3).setLayout(mainlayout) # # Production Tab User Interface # def initUItab5(self): # Create main layout (a Vertical Layout) mainlayout = QVBoxLayout() mainlayout.addWidget(QLabel("This is the production tab")) self.tabs.widget(4).setLayout(mainlayout) # # Export Tab User Interface # def initUItab6(self): # Create main layout (a Vertical Layout) mainlayout = QVBoxLayout() mainlayout.addWidget(QLabel("This is the export tab")) self.tabs.widget(5).setLayout(mainlayout) def baselineClicked(self): button = self.sender() if button.isChecked(): if not self.prev is None: if self.prev.isChecked(): self.prev.setChecked(False) self.prev = button self.get_data() def on_return(self): ''' Called when the ms filename LineEdit widget gets a carriage-return. Trys to connect to the ms and return some info (no data read at this time) ''' self.fname = self.msentry.text() self.msdata = None try: if self.ms: self.ms.close() else: self.ms = mstool() self.ms.open(self.fname) self.msentry.setText(self.fname) lines = pr_summary(self.ms) self.infoEdit.setPlainText('\n'.join(lines)) except: self.statusBar.showMessage('Filename is not a valid ms', 2000) self.fname = '<Select or enter a valid ms filename>' self.msentry.setText(self.fname) self.infoEdit.setPlainText('') try: t1str, t2str = lines[2].replace('-', '/').split(': ')[1:3] trange = t1str[0:10] + '/' + t1str[11:21] + '~' + t2str[ 0:10] + '/' + t2str[11:21] except: trange = '<unset>' try: spwstr = lines[-1].strip().split(' ')[0] spw = '0~' + spwstr except: spw = '<unset>' self.trangeEdit.setText(trange) self.spwEdit.setText(spw) self.baselineEdit.setText('0~12&0~12') self.stokesEdit.setText('XX') self.uvrangeEdit.setText('0~2km') if spwstr == '30': self.ignore_gaps.setChecked(True) else: self.ignore_gaps.setChecked(False) def on_click(self): ''' Handle Browse button ''' self.fname = QFileDialog.getExistingDirectory(self, 'Select MS', './', QFileDialog.ShowDirsOnly) self.msentry.setText(self.fname) self.on_return() def ant_click(self, button): ''' An antenna button was clicked, so check the checkboxes corresponding to the currently checked antennas. button is a dictionary with keys 'button' (actual button handle) and 'iant' (ordinal location in list of buttons). Each antenna has two buttons, so we have to ensure that both are toggled. iant is an integer from 0-25. If even, toggle buttons iant and iant+1. If odd, toggle buttons iant and iant-1. ''' k = 0 ant = -1 for b in self.ant1buttons: if b == button: self.ant2buttons[k].setChecked(button.isChecked()) ant = k break else: k += 1 k = 0 for b in self.ant2buttons: if b == button: self.ant1buttons[k].setChecked(button.isChecked()) ant = k break else: k += 1 self.statusBar.showMessage('Antenna ' + str(ant + 1) + ' hit', 1000) nant = 13 bl2ord = get_bl2ord(nant) # Reflect antenna state on baselines for i in range(nant): for j in range(i, nant): if i != j: if i == ant: self.blcheck[bl2ord[i, j]].setChecked(button.isChecked()) if j == ant: self.blcheck[bl2ord[i, j]].setChecked(button.isChecked()) # Have to go back and set any baselines whose antennas are checked. for i in range(nant): if self.ant1buttons[i].isChecked(): for k in range(i): self.blcheck[bl2ord[k, i]].setChecked(True) for j in range(i + 1, nant): self.blcheck[bl2ord[i, j]].setChecked(True) def sel_clicked(self, state): nant = 13 bl2ord = get_bl2ord(nant) for i in range(nant): self.ant1buttons[i].setChecked(state) self.ant2buttons[i].setChecked(state) for j in range(i, nant): self.blcheck[bl2ord[i, j]].setChecked(state) def get_data(self): # Find which baselines are checked: bl = [] blid = [] isauto = [] ord13 = get_bl2ord(13) ord16 = get_bl2ord(16) for i in range(13): for j in range(i, 13): if self.blcheck[ord13[i, j]].isChecked(): bl.append(ord16[i, j]) isauto.append(i == j) blid.append([i, j]) if len(bl) == 0: self.statusBar.showMessage('No baselines have been selected.', 2000) return if len(bl) != 1: self.statusBar.showMessage('Only one baseline can be shown.', 2000) return # If no data have been read, read the data if self.msdata is None: self.read_msdata(fillnan=np.nan) npol, nf, nbl, nt = self.msdata['data'].shape self.plotbl = bl[0] # Determine which polarization state is selected by radiobuttons polnum = {'XX': 0, 'YY': 1, 'XY': 2, 'YX': 3, 'RR': 4, 'LL': 5} ipol = polnum[self.polGroup.checkedButton().text()] if ipol < 4: # The selected polarization is just one of the native polarizations (XX, YY, XY, or YX) spec = np.abs(self.msdata['data'][ipol, :, self.plotbl]) elif ipol == 4: # The selected polarization is RR (not implemented because we only have amplitudes...) xx = self.msdata['data'][0, :, self.plotbl] yy = self.msdata['data'][1, :, self.plotbl] xy = self.msdata['data'][2, :, self.plotbl] yx = self.msdata['data'][3, :, self.plotbl] rr = (xx + yy + 1j * (xy - yx)) / 2 spec = np.abs(rr) elif ipol == 5: # The selected polarization is LL (not implemented because we only have amplitudes...) xx = self.msdata['data'][0, :, self.plotbl] yy = self.msdata['data'][1, :, self.plotbl] xy = self.msdata['data'][2, :, self.plotbl] yx = self.msdata['data'][3, :, self.plotbl] ll = (xx + yy - 1j * (xy - yx)) / 2 spec = np.abs(ll) i, j = blid[0] if isauto[0]: self.ptitle = 'Auto-Correlation for Ant {}'.format(i + 1) else: self.ptitle = 'Cross-Correlation for Baseline {}-{}'.format( i + 1, j + 1) spec1d = spec.flatten() spec1dg = np.sort(spec1d[~np.isnan(spec1d)]) if len(spec1dg) == 0: self.statusBar.showMessage('This baseline is all NaN', 2000) self.dspec_ax.set_title('This baseline is all NaN') self.speccanvas.draw() else: n95 = int(len(spec1dg) * 0.95) ampmax = spec1dg[n95] ampmin = spec1dg[0] self.minentry.setText('{:10.2f}'.format(ampmin / 10000.)) self.maxentry.setText('{:10.2f}'.format(ampmax / 10000.)) self.plot_data() def plot_data(self): ''' Plot the data selected by get_data(). ''' # Determine which polarization state is selected by radiobuttons polnum = {'XX': 0, 'YY': 1, 'XY': 2, 'YX': 3, 'RR': 4, 'LL': 5} ipol = polnum[self.polGroup.checkedButton().text()] if ipol < 4: # The selected polarizatin is just one of the native polarizations (XX, YY, XY, or YX) spec = np.abs(self.msdata['data'][ipol, :, self.plotbl]) elif ipol == 4: # The selected polarization is RR (not implemented because we only have amplitudes...) xx = self.msdata['data'][0, :, self.plotbl] yy = self.msdata['data'][1, :, self.plotbl] xy = self.msdata['data'][2, :, self.plotbl] yx = self.msdata['data'][3, :, self.plotbl] rr = (xx + yy + 1j * (xy - yx)) / 2 spec = np.abs(rr) elif ipol == 5: # The selected polarization is LL (not implemented because we only have amplitudes...) xx = self.msdata['data'][0, :, self.plotbl] yy = self.msdata['data'][1, :, self.plotbl] xy = self.msdata['data'][2, :, self.plotbl] yx = self.msdata['data'][3, :, self.plotbl] ll = (xx + yy - 1j * (xy - yx)) / 2 spec = np.abs(ll) try: minval = float(self.minentry.text()) * 10000. except: self.statusBar.showMessage('Could not interpret minimum sfu value', 2000) self.minentry.setText('{:10.2f}'.format(np.nanmin(spec) / 10000.)) minval = float(self.minentry.text()) * 10000. try: maxval = float(self.maxentry.text()) * 10000. except: self.statusBar.showMessage('Could not interpret maximum sfu value', 2000) self.maxentry.setText('{:10.2f}'.format(np.nanmax(spec) / 10000.)) maxval = float(self.maxentry.text()) * 10000. for i in range(len(self.dspec_ax.images)): # Clear any existing images without changing the axis limits self.dspec_ax.images[i].remove() fghz = self.msdata['freq'] / 1e9 times = self.msdata['time'] nf, nt = spec.shape if not self.ignore_gaps.isChecked(): # Insert nan rows and columns in gaps df = np.median(fghz[1:] - fghz[:-1]) fbreak, = np.where(fghz[1:] - fghz[:-1] > 2 * df) for n, fb in enumerate(fbreak): loc = fb + n + 1 fghz = np.concatenate( (fghz[:loc], np.array([fghz[loc - 1] + df]), fghz[loc:])) spec = np.concatenate((spec[:loc], np.zeros( (1, nt)) + np.nan, spec[loc:]), 0) nf, nt = spec.shape dt = np.median(times[1:] - times[:-1]) tbreak, = np.where(times[1:] - times[:-1] > 2 * dt) for n, tb in enumerate(tbreak): loc = tb + n + 1 times = np.concatenate( (times[:loc], np.array([times[loc - 1] + dt]), times[loc:])) spec = np.concatenate((spec[:, :loc], np.zeros( (nf, 1)) + np.nan, spec[:, loc:]), 1) nf, nt = spec.shape pd = Time(times / 86400., format='mjd').plot_date im = self.dspec_ax.pcolormesh(pd, fghz, np.clip(spec, minval, maxval)) self.dspec_ax.xaxis_date() self.dspec_ax.xaxis.set_major_formatter(DateFormatter("%H:%M")) self.dspec_ax.set_ylabel('Frequency [GHz]') self.dspec_ax.set_xlabel('Time [UT]') # self.dspec_ax.imshow(np.clip(spec, minval, maxval), interpolation='nearest', aspect='auto', origin='lower') self.dspec_ax.set_title(self.ptitle) self.speccanvas.draw() def drawsel(self): ''' Uses timerange, spw, and stokes selection to draw a box on the plot. ''' #if len(self.dspec_ax.get_images()) == 0: # # No plot exists, so exit # self.statusBar.showMessage("No plot exists yet. Please select a baseline to plot first.", 2000) # return pd1, pd2 = trange2pd(self.trangeEdit.text()) if pd1 is None: self.statusBar.showMessage(pd2, 2000) return f1, f2 = spw2frange(self.spwEdit.text(), self.msdata) if f1 is None: self.statusBar.showMessage(f2, 2000) return # Remove any existing lines before plotting a new one for line in self.dspec_ax.get_lines(): line.remove() self.dspec_ax.plot([pd1, pd2, pd2, pd1, pd1], [f1, f1, f2, f2, f1], color='white') self.speccanvas.draw() def xferUL(self): ''' Uses the plot limits to define the timerange and spw selections. ''' trange = pd2trange(self.dspec_ax.get_xlim()) self.trangeEdit.setText(trange) f1, f2 = self.dspec_ax.get_ylim() idx1, = np.where(self.msdata['freq'] >= f1 * 1.e9) idx2, = np.where(self.msdata['freq'] <= f2 * 1.e9) spw = str(self.msdata['spwlist'][idx1[0]]) + '~' + str( self.msdata['spwlist'][idx2[-1]]) self.spwEdit.setText(spw) def xferDR(self): ''' Uses timerange, spw, and stokes selection to define the plot limits. ''' pd = trange2pd(self.trangeEdit.text()) if pd[0] is None: self.statusBar.showMessage(pd[1], 2000) return frange = spw2frange(self.spwEdit.text(), self.msdata) if frange[0] is None: self.statusBar.showMessage(frange[1], 2000) return self.dspec_ax.set_xlim(pd) self.dspec_ax.set_ylim(frange) self.speccanvas.draw() def read_msdata(self, fillnan=None): ''' Read amplitudes for ALL data in the measurement set, returning a dictionary of the data, list of times, and list of frequencies. Fill flagged data with fillnan value if not None. ''' ms = self.ms #if type(ms) != casatools.ms.ms: # self.statusBar.showMessage('Not connected to a valid ms!',2000) # return self.statusBar.addWidget(self.progressBar) self.progressBar.setFormat('Reading file...%p% done.') self.progressBar.setValue(0) self.progressBar.show() ms.selectinit(datadescid=0, reset=True) spwinfo = ms.getspectralwindowinfo() nspw = len(spwinfo.keys()) spec = [] freq = [] #time = [] spwlist = [] for descid in range(len(spwinfo.keys())): ms.selectinit(datadescid=0, reset=True) ms.selectinit(datadescid=descid) data = ms.getdata(['data', 'time', 'axis_info'], ifraxis=True) spec_ = data['data'] freq_ = data['axis_info']['freq_axis']['chan_freq'] #time_ = data['time'] spwlist += [descid] * len(freq_) if fillnan is not None: flag_ = ms.getdata(['flag', 'time', 'axis_info'], ifraxis=True)['flag'] if type(fillnan) in [int, float]: spec_[flag_] = float(fillnan) else: spec_[flag_] = 0.0 spec.append(spec_) freq.append(freq_) #time.append(time_) self.progressBar.setValue(100 * (descid + 1) / nspw) spec = np.concatenate(spec, axis=1) freq = np.concatenate(freq, axis=0) nf = freq.shape[0] freq.shape = (nf, ) times = data['time'] ms.selectinit(datadescid=0, reset=True) self.statusBar.removeWidget(self.progressBar) self.dspec_ax.cla( ) # Clear plot axis in preparation for a new dynamic spectrum self.msdata = { 'data': spec, 'freq': freq, 'time': times, 'name': ms.name, 'spwlist': np.array(spwlist) }