class MainWidget(QWidget):
    def __init__(self, parent: QWidget, model: Model) -> None:
        super().__init__(parent)

        logger.add(self.log)

        settings = QSettings()
        self.mainlayout = QVBoxLayout()
        self.mainlayout.setContentsMargins(5, 5, 5, 5)
        self.setLayout(self.mainlayout)

        # summary

        summarylayout = FlowLayout()
        summarylayout.setContentsMargins(0, 0, 0, 0)
        self.summary = QWidget()
        self.summary.setLayout(summarylayout)
        self.mainlayout.addWidget(self.summary)
        self.summary.setVisible(
            settings.value('showSummary', 'True') == 'True')

        detailslayout = QHBoxLayout()
        detailslayout.setContentsMargins(1, 0, 0, 0)
        detailslayout.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        detailslayout.setSpacing(15)
        details = QWidget()
        details.setLayout(detailslayout)
        summarylayout.addWidget(details)

        self.modstotal = QLabel()
        detailslayout.addWidget(self.modstotal)
        self.modsenabled = QLabel()
        detailslayout.addWidget(self.modsenabled)
        self.overridden = QLabel()
        detailslayout.addWidget(self.overridden)
        self.conflicts = QLabel()
        detailslayout.addWidget(self.conflicts)

        buttonslayout = QHBoxLayout()
        buttonslayout.setContentsMargins(0, 0, 0, 0)
        buttonslayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        buttons = QWidget()
        buttons.setLayout(buttonslayout)
        summarylayout.addWidget(buttons)

        self.startscriptmerger = QPushButton('Start Script Merger')
        self.startscriptmerger.setContentsMargins(0, 0, 0, 0)
        self.startscriptmerger.setMinimumWidth(140)
        self.startscriptmerger.setIcon(
            QIcon(str(getRuntimePath('resources/icons/script.ico'))))
        self.startscriptmerger.clicked.connect(lambda: [
            openExecutable(Path(str(settings.value('scriptMergerPath'))), True)
        ])
        self.startscriptmerger.setEnabled(
            verifyScriptMergerPath(
                Path(str(settings.value('scriptMergerPath')))) is not None)
        buttonslayout.addWidget(self.startscriptmerger)

        self.startgame = QPushButton('Start Game')
        self.startgame.setContentsMargins(0, 0, 0, 0)
        self.startgame.setMinimumWidth(100)
        self.startgame.setIcon(
            QIcon(str(getRuntimePath('resources/icons/w3b.ico'))))
        buttonslayout.addWidget(self.startgame)

        # splitter

        self.splitter = QSplitter(Qt.Vertical)

        self.stack = QStackedWidget()
        self.splitter.addWidget(self.stack)

        # mod list widget

        self.modlistwidget = QWidget()
        self.modlistlayout = QVBoxLayout()
        self.modlistlayout.setContentsMargins(0, 0, 0, 0)
        self.modlistwidget.setLayout(self.modlistlayout)
        self.stack.addWidget(self.modlistwidget)

        # search bar

        self.searchbar = QLineEdit()
        self.searchbar.setPlaceholderText('Search...')
        self.modlistlayout.addWidget(self.searchbar)

        # mod list

        self.modlist = ModList(self, model)
        self.modlistlayout.addWidget(self.modlist)

        self.searchbar.textChanged.connect(lambda e: self.modlist.setFilter(e))

        # welcome message

        welcomelayout = QVBoxLayout()
        welcomelayout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        welcomewidget = QWidget()
        welcomewidget.setLayout(welcomelayout)
        welcomewidget.dragEnterEvent = self.modlist.dragEnterEvent  # type: ignore
        welcomewidget.dragMoveEvent = self.modlist.dragMoveEvent  # type: ignore
        welcomewidget.dragLeaveEvent = self.modlist.dragLeaveEvent  # type: ignore
        welcomewidget.dropEvent = self.modlist.dropEvent  # type: ignore
        welcomewidget.setAcceptDrops(True)

        icon = QIcon(str(getRuntimePath('resources/icons/open-folder.ico')))
        iconpixmap = icon.pixmap(32, 32)
        icon = QLabel()
        icon.setPixmap(iconpixmap)
        icon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        icon.setContentsMargins(4, 4, 4, 4)
        welcomelayout.addWidget(icon)

        welcome = QLabel('''<p><font>
            No mod installed yet.
            Drag a mod into this area to get started!
            </font></p>''')
        welcome.setAttribute(Qt.WA_TransparentForMouseEvents)
        welcome.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        welcomelayout.addWidget(welcome)

        self.stack.addWidget(welcomewidget)

        # output log

        self.output = QTextEdit(self)
        self.output.setTextInteractionFlags(Qt.NoTextInteraction)
        self.output.setReadOnly(True)
        self.output.setContextMenuPolicy(Qt.NoContextMenu)
        self.output.setPlaceholderText('Program output...')
        self.splitter.addWidget(self.output)

        # TODO: enhancement: show indicator if scripts have to be merged

        self.splitter.setStretchFactor(0, 1)
        self.splitter.setStretchFactor(1, 0)
        self.mainlayout.addWidget(self.splitter)

        # TODO: incomplete: make start game button functional

        if len(model):
            self.stack.setCurrentIndex(0)
            self.splitter.setSizes([self.splitter.size().height(), 50])
        else:
            self.stack.setCurrentIndex(1)
            self.splitter.setSizes([self.splitter.size().height(), 0])
        model.updateCallbacks.append(self.modelUpdateEvent)

        asyncio.create_task(model.loadInstalled())

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if event.key() == Qt.Key_Escape:
            self.modlist.setFocus()
            self.searchbar.setText('')
        elif event.matches(QKeySequence.Find):
            self.searchbar.setFocus()
        elif event.matches(QKeySequence.Paste):
            self.pasteEvent()
        # TODO: enhancement: add start game / start script merger shortcuts
        else:
            super().keyPressEvent(event)

    def pasteEvent(self) -> None:
        clipboard = QApplication.clipboard().text().splitlines()
        if len(clipboard) == 1 and isValidNexusModsUrl(clipboard[0]):
            self.parentWidget().showDownloadModDialog()
        else:
            urls = [
                url for url in QApplication.clipboard().text().splitlines()
                if len(str(url.strip()))
            ]
            if all(
                    isValidModDownloadUrl(url) or isValidFileUrl(url)
                    for url in urls):
                asyncio.create_task(self.modlist.checkInstallFromURLs(urls))

    def modelUpdateEvent(self, model: Model) -> None:
        total = len(model)
        enabled = len([mod for mod in model if model[mod].enabled])
        overridden = sum(
            len(file) for file in model.conflicts.bundled.values())
        conflicts = sum(len(file) for file in model.conflicts.scripts.values())
        self.modstotal.setText(
            f'<font color="#73b500" size="4">{total}</font> \
                <font color="#888" text-align="center">Installed Mod{"" if total == 1 else "s"}</font>'
        )
        self.modsenabled.setText(
            f'<font color="#73b500" size="4">{enabled}</font> \
                <font color="#888">Enabled Mod{"" if enabled == 1 else "s"}</font>'
        )
        self.overridden.setText(
            f'<font color="{"#b08968" if overridden > 0 else "#84C318"}" size="4">{overridden}</font> \
                <font color="#888">Overridden File{"" if overridden == 1 else "s"}</font> '
        )
        self.conflicts.setText(
            f'<font color="{"#E55934" if conflicts > 0 else "#aad576"}" size="4">{conflicts}</font> \
                <font color="#888">Unresolved Conflict{"" if conflicts == 1 else "s"}</font> '
        )

        if len(model) > 0:
            if self.stack.currentIndex() != 0:
                self.stack.setCurrentIndex(0)
                self.repaint()
        else:
            if self.stack.currentIndex() != 1:
                self.stack.setCurrentIndex(1)
                self.repaint()

    def unhideOutput(self) -> None:
        if self.splitter.sizes()[1] < 10:
            self.splitter.setSizes([self.splitter.size().height(), 50])

    def unhideModList(self) -> None:
        if self.splitter.sizes()[0] < 10:
            self.splitter.setSizes([50, self.splitter.size().height()])

    def log(self, message: Any) -> None:
        # format log messages to user readable output
        settings = QSettings()

        record = message.record
        message = record['message']
        extra = record['extra']
        level = record['level'].name.lower()

        name = str(extra['name']
                   ) if 'name' in extra and extra['name'] is not None else ''
        path = str(extra['path']
                   ) if 'path' in extra and extra['path'] is not None else ''
        dots = bool(
            extra['dots']
        ) if 'dots' in extra and extra['dots'] is not None else False
        newline = bool(
            extra['newline']
        ) if 'newline' in extra and extra['newline'] is not None else False
        output = bool(
            extra['output']
        ) if 'output' in extra and extra['output'] is not None else bool(
            message)
        modlist = bool(
            extra['modlist']
        ) if 'modlist' in extra and extra['modlist'] is not None else False

        if level in ['debug'
                     ] and settings.value('debugOutput', 'False') != 'True':
            if newline:
                self.output.append(f'')
            return

        n = '<br>' if newline else ''
        d = '...' if dots else ''
        if len(name) and len(path):
            path = f' ({path})'

        if output:
            message = html.escape(message, quote=True)

            if level in ['success', 'error', 'warning']:
                message = f'<strong>{message}</strong>'
            if level in ['success']:
                message = f'<font color="#04c45e">{message}</font>'
            if level in ['error', 'critical']:
                message = f'<font color="#ee3b3b">{message}</font>'
            if level in ['warning']:
                message = f'<font color="#ff6500">{message}</font>'
            if level in ['debug', 'trace']:
                message = f'<font color="#aaa">{message}</font>'
                path = f'<font color="#aaa">{path}</font>' if path else ''
                d = f'<font color="#aaa">{d}</font>' if d else ''

            time = record['time'].astimezone(
                tz=None).strftime('%Y-%m-%d %H:%M:%S')
            message = f'<font color="#aaa">{time}</font> {message}'
            self.output.append(
                f'{n}{message.strip()}{" " if name or path else ""}{name}{path}{d}'
            )
        else:
            self.output.append(f'')

        self.output.verticalScrollBar().setValue(
            self.output.verticalScrollBar().maximum())
        self.output.repaint()

        if modlist:
            self.unhideModList()
        if settings.value('unhideOutput', 'True') == 'True' and output:
            self.unhideOutput()
Beispiel #2
0
class BuilderWidget(QWidget):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)

        self.verticalLayout = QVBoxLayout(self)

        self.compileButton = QPushButton('Compile', self)
        self.compileButton.setMinimumSize(QSize(200, 50))
        self.verticalLayout.addWidget(self.compileButton, 0, Qt.AlignHCenter)

        self.tidyButton = QPushButton('Tidy', self)
        self.verticalLayout.addWidget(self.tidyButton, 0, Qt.AlignHCenter)

        self.splitter = QSplitter(self)
        self.splitter.setOrientation(Qt.Vertical)

        self.widget = QWidget(self.splitter)
        self.widget.setObjectName(u"widget")
        self.verticalLayout2 = QVBoxLayout(self.widget)
        self.stdoutLabel = QLabel(self.widget)
        self.stdoutLabel.setText('stdout:')

        self.verticalLayout2.addWidget(self.stdoutLabel)

        self.stdoutText = QTextEdit(self.widget)
        self.stdoutText.setEnabled(False)
        self.stdoutText.setReadOnly(True)
        self.stdoutText.setPlainText("Output will appear here")

        self.verticalLayout2.addWidget(self.stdoutText)

        self.splitter.addWidget(self.widget)

        self.widget1 = QWidget(self.splitter)

        self.verticalLayout3 = QVBoxLayout(self.widget1)
        self.stderrLabel = QLabel(self.widget1)
        self.stderrLabel.setText('stderr:')

        self.verticalLayout3.addWidget(self.stderrLabel)

        self.stderrText = QTextEdit(self.widget1)
        self.stderrText.setEnabled(False)
        self.stderrText.setReadOnly(True)
        self.stderrText.setPlainText('Errors will appear here')

        self.verticalLayout3.addWidget(self.stderrText)

        self.splitter.addWidget(self.widget1)

        self.verticalLayout.addWidget(self.splitter)

        # Logic

        # Use QProcess to start a program and get its outputs https://stackoverflow.com/a/22110924
        self.process = QProcess(self)

        self.process.readyReadStandardOutput.connect(self.readStdout)
        self.process.readyReadStandardError.connect(self.readStderr)
        self.process.started.connect(self.processStarted)
        self.process.finished.connect(self.processFinished)
        self.process.errorOccurred.connect(self.errorOccurred)
        self.compileButton.clicked.connect(self.doCompile)
        self.tidyButton.clicked.connect(self.doTidy)

    def doCompile(self):
        self.cleanupUI()
        self.process.setWorkingDirectory(settings.get_repo_location())
        self.process.startCommand(settings.get_build_command())

    def doTidy(self):
        self.cleanupUI()
        self.process.setWorkingDirectory(settings.get_repo_location())
        self.process.startCommand(settings.get_tidy_command())

    def cleanupUI(self):
        self.stdoutText.setEnabled(True)
        self.stderrText.setEnabled(True)
        self.stdoutText.setPlainText('')
        self.stderrText.setPlainText('')

    def readStdout(self):
        lines = self.process.readAllStandardOutput().data().decode(
        )[:-1].split('\n')
        for line in lines:
            if line == 'tmc.gba: FAILED':
                line = 'tmc.gba: <b style="color:red">FAILED</b>'
            elif line == 'tmc.gba: OK':
                line = 'tmc.gba: <b style="color:lime">OK</b>'
            self.stdoutText.append(line)

    def readStderr(self):
        lines = self.process.readAllStandardError().data().decode()[:-1].split(
            '\n')
        for line in lines:
            if 'error' in line.lower():
                line = f'<span style="color:red">{line}</span>'
            elif 'warning' in line.lower():
                line = f'<span style="color:orange">{line}</span>'

            self.stderrText.append(line)

    def processStarted(self):
        self.compileButton.setEnabled(False)
        self.tidyButton.setEnabled(False)

    def processFinished(self):
        self.compileButton.setEnabled(True)
        self.tidyButton.setEnabled(True)

    def errorOccurred(self):
        self.stderrText.insertPlainText(self.process.errorString())
Beispiel #3
0
class MainWindow(QMainWindow):
    def __init__(self, window_width=None, window_height=None):
        super(MainWindow, self).__init__()
        if window_width != None and window_height != None:
            self.window_width = window_width
            self.window_height = window_height
            self.resize(window_width, window_height)

        self.stack_of_widget = QStackedWidget()
        self.central_widget = QWidget()
        self.main_layout = QVBoxLayout()

        self.username = ""

        # ui widgets
        self.message_visable_field = QTextEdit()
        self.message_input_field = QTextEdit()
        self.send_message = QPushButton("Send my message!")

        self.login_screen = LoginScreen()
        self.login_screen.auth_button.clicked.connect(self.login_button_click)

        # show all widgets
        self.build_ui()

    def build_ui(self):
        self.message_visable_field.setFont(QFont("Roboto", 12))
        self.message_visable_field.setReadOnly(True)

        self.message_input_field.setFont(QFont("Roboto", 11))
        self.message_input_field.setPlaceholderText(
            "Input your message here...")
        self.message_input_field.setMinimumHeight(50)
        self.message_input_field.setMaximumHeight(50)

        self.send_message.setFont(QFont("Roboto", 11))
        self.send_message.clicked.connect(self.send_message_button_clicked)

        self.main_layout.addWidget(self.message_visable_field)
        self.main_layout.addWidget(self.message_input_field)
        self.main_layout.addWidget(self.send_message)

        self.central_widget.setLayout(self.main_layout)
        self.stack_of_widget.addWidget(self.login_screen)
        self.setMaximumSize(self.minimumSize())
        self.stack_of_widget.addWidget(self.central_widget)
        self.setCentralWidget(self.stack_of_widget)

    def send_message_button_clicked(self):
        """
        send a client message to the server, and append to the list message.
        """
        message = self.message_input_field.toPlainText()
        if message != '':
            # send message to the server
            r_type = RequestInfo(type_request="MessageRequest",
                                 request_ts=f"{datetime.now()}")
            r_data = MessageRequest(from_=self.username,
                                    to="all",
                                    message=message)
            request = Request(request=[r_type], data=[r_data])
            client_worker.send(request.json())
            self.message_visable_field.append('You: ' + message)
        self.message_input_field.clear()

    def login_button_click(self):
        self.username = self.login_screen.login_field.text()
        r_type = RequestInfo(type_request="AuthRequest",
                             request_ts=f"{datetime.now()}")
        r_data = AuthRequest(
            login=f"{self.username}",
            password=f"{self.login_screen.password_field.text()}")
        request = Request(request=[r_type], data=[r_data])
        client_worker.send(request.json())

    @Slot(str)
    def recieved_message_handler(self, message):
        msg = json.loads(message)
        print(msg)
        response = Request(**msg)
        if response.request[0].type_request == "AuthResponse":
            data = AuthResponse(**response.data[0])
            if data.access is True:
                self.show_main_screen()

        elif response.request[0].type_request == "MessageRequest":
            data = MessageRequest(**response.data[0])
            if data.to == "all":
                self.message_visable_field.append(
                    f"[{data.from_}]: {data.message}")

    def show_main_screen(self):
        self.stack_of_widget.setCurrentWidget(self.central_widget)
        self.setMinimumSize(self.window_width, self.window_height)
        self.resize(self.minimumSize())

    def closeEvent(self, event):
        client_worker.close()
        network_thread.exit()
        network_thread.wait(500)
        if network_thread.isRunning() is True:
            network_thread.terminate()
class RenameFilesGUI(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setMinimumSize(600, 250)
        self.setWindowTitle("Change File Names GUI")
        self.directory = ""
        self.cb_value = ""
        self.setupWidgets()
        self.show()

    def setupWidgets(self):
        """
        Set up the widgets and layouts for interface.
        """
        dir_label = QLabel("Choose Directory:")
        self.dir_line_edit = QLineEdit()

        dir_button = QPushButton('...')
        dir_button.setToolTip("Select file directory.")
        dir_button.clicked.connect(self.setDirectory)

        self.change_name_edit = QLineEdit()
        self.change_name_edit.setToolTip(
            "Files will be appended with numerical values.For example: filename <b> 01 </b >.jpg")
        self.change_name_edit.setPlaceholderText("Change file names to...")

        rename_button = QPushButton("Rename Files")
        rename_button.setToolTip("Begin renaming files in directory.")
        rename_button.clicked.connect(self.renameFiles)

        file_exts = [".jpg", ".jpeg", ".png", ".gif", ".txt"]

        # Create combo box for selecting file extensions.
        ext_cb = QComboBox()
        self.cb_value = file_exts[0]
        ext_cb.setToolTip("Only files with this extension will be changed.")
        ext_cb.addItems(file_exts)
        ext_cb.currentTextChanged.connect(self.updateCbValue)

        # Text edit is for displaying the file names as they are updated.
        self.display_files_edit = QTextEdit()
        self.display_files_edit.setReadOnly(True)
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)

        # Set layout and widgets.
        grid = QGridLayout()
        grid.addWidget(dir_label, 0, 0)
        grid.addWidget(self.dir_line_edit, 1, 0, 1, 2)
        grid.addWidget(dir_button, 1, 2)
        grid.addWidget(self.change_name_edit, 2, 0)
        grid.addWidget(ext_cb, 2, 1)
        grid.addWidget(rename_button, 2, 2)
        grid.addWidget(self.display_files_edit, 3, 0, 1, 3)
        grid.addWidget(self.progress_bar, 4, 0, 1, 3)
        self.setLayout(grid)

    def setDirectory(self):
        """
        Choose the directory.
        """
        file_dialog = QFileDialog(self)
        file_dialog.setFileMode(QFileDialog.Directory)
        self.directory = file_dialog.getExistingDirectory(self, "Open Directory", "", QFileDialog.ShowDirsOnly)
        if self.directory:
            self.dir_line_edit.setText(self.directory)
            # Set the max value of progress bar equal to max number of  files in the directory.
            num_of_files = len([name for name in os.listdir(self.directory)])
            self.progress_bar.setRange(0, num_of_files)

    def updateCbValue(self, text):
        """
        Change the combo box value. Values represent the different file extensions.
        """
        self.cb_value = text

    def renameFiles(self):
        """
        Create instance of worker thread to handle the file renaming process.
        """
        prefix_text = self.change_name_edit.text()
        if self.directory != "" and prefix_text != "":
            self.worker = Worker(self.directory, self.cb_value, prefix_text)
            self.worker.updateValueSignal.connect(self.updateProgressBar)
            self.worker.updateTextEditSignal.connect(self.updateTextEdit)
            self.worker.start()
        else:
            pass

    def updateProgressBar(self, value):
        self.progress_bar.setValue(value)

    def updateTextEdit(self, old_text, new_text):
        self.display_files_edit.append("[INFO] {} changed to{}.".format(old_text, new_text))