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()
예제 #2
0
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)
예제 #3
0
파일: main.py 프로젝트: JohnGGG/PyPad
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])
예제 #4
0
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))
예제 #5
0
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)
예제 #6
0
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)
예제 #7
0
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())
예제 #8
0
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)
        }