コード例 #1
0
def tribler_api(api_port, tmpdir_factory):
    # Run real Core and record responses
    core_env = QProcessEnvironment.systemEnvironment()
    core_env.insert("CORE_BASE_PATH",
                    str(RUN_TRIBLER_PY.parent / "tribler-core"))
    core_env.insert("CORE_PROCESS", "1")
    core_env.insert("CORE_API_PORT", f"{api_port}")
    core_env.insert("CORE_API_KEY", "")
    core_env.insert("TRIBLER_CORE_TEST_MODE", "1")

    temp_state_dir = tmpdir_factory.mktemp('tribler_state_dir')
    core_env.insert("TSTATEDIR", str(temp_state_dir))

    core_process = QProcess()

    def on_core_read_ready():
        raw_output = bytes(core_process.readAll())
        decoded_output = raw_output.decode(errors="replace")
        print(decoded_output.strip())  # noqa: T001

    core_process.setProcessEnvironment(core_env)
    core_process.setReadChannel(QProcess.StandardOutput)
    core_process.setProcessChannelMode(QProcess.MergedChannels)
    connect(core_process.readyRead, on_core_read_ready)
    core_process.start("python3", [str(RUN_TRIBLER_PY.absolute())])
    yield core_process
    core_process.kill()
    core_process.waitForFinished()
コード例 #2
0
ファイル: datasource.py プロジェクト: luelista/pre_workbench
class AbstractTsharkDataSource(DataSource):
    def startFetch(self):
        self.plist = ByteBufferList()
        self.process = QProcess()
        self.process.finished.connect(self.onProcessFinished)
        self.process.readyReadStandardError.connect(self.onReadyReadStderr)
        self.process.readyReadStandardOutput.connect(self.onReadyReadStdout)

        self.process.start(findTshark(), self.getArgs())
        self.target = PdmlToPacketListParser(self.plist)
        return self.plist

    def onReadyReadStderr(self):
        s = "STD-ERR FROM Tshark:" + self.process.readAllStandardError().data(
        ).decode("utf-8", "replace")
        print(s)
        self.on_log.emit(s)

    def onReadyReadStdout(self):
        self.plist.beginUpdate()
        self.target.feed(self.process.readAllStandardOutput())
        self.plist.endUpdate()

    def onProcessFinished(self, exitCode, exitStatus):
        self.on_finished.emit()

    def cancelFetch(self):
        self.process.terminate()
        self.process.waitForFinished(500)
        self.process.kill()
        pass
コード例 #3
0
ファイル: converter.py プロジェクト: codeshard/videomorph
class Converter:
    """Converter class to provide conversion functionality."""

    def __init__(self, media_list, conversion_lib=CONV_LIB.ffmpeg):
        """Class initializer."""
        self.conversion_lib = conversion_lib
        self.media_list = media_list
        self.process = QProcess()

    def start_encoding(self, cmd):
        """Start the encoding process."""
        self.process.start(which(self.conversion_lib), cmd)

    def stop_encoding(self):
        """Terminate encoding process."""
        self.process.terminate()
        if self.is_running:
            self.process.kill()

    @property
    def is_running(self):
        """Return the individual file encoding process state."""
        return self.process.state() == QProcess.Running

    @property
    def encoding_done(self):
        """Return True if media list is done."""
        return self.media_list.running_index + 1 >= self.media_list.length
コード例 #4
0
class MainView(QMainWindow):
    def __init__(self, flags=None, *args, **kwargs):
        super().__init__(flags, *args, **kwargs)
        loadUi(os.path.join(os.path.dirname(__file__), "server.ui"), self)
        self.output_path = os.path.join(os.path.dirname(__file__), "log")
        self._buffer = ""
        self._server_process: QProcess = None

        self.startStopButton: QPushButton = self.startStopButton
        self.logTextBrowser: QTextBrowser = self.logTextBrowser
        self.formWidget: QWidget = self.formWidget
        self.portLabel: QLabel = self.portLabel
        self.portLineEdit: QLineEdit = self.portLineEdit
        self.addressLabel: QLabel = self.addressLabel
        self.addressLineEdit: QLineEdit = self.addressLineEdit
        self.outputDirectoryButton: QPushButton = self.outputDirectoryButton

        self.outputDirectoryButton.setText(self.output_path)
        self.logTextBrowser.verticalScrollBar().rangeChanged.connect(
            lambda x: self.logTextBrowser.verticalScrollBar().setValue(
                self.logTextBrowser.verticalScrollBar().maximum()))
        self.outputDirectoryButton.clicked.connect(self.set_output_path)
        self.startStopButton.clicked.connect(self.start_server)
        self.show()

    def start_server(self):
        if not self._server_process:
            self._server_process = QProcess(self)
            self._server_process.readyRead.connect(self.data_ready)
            addr = self.addressLineEdit.text()
            port = self.portLineEdit.text()
            self._server_process.start(sys.executable, [
                "-m", "chat.server.chat_server", "--addr", addr, "--port",
                port, "--output", self.output_path
            ])
            self.startStopButton.setText("Stop server")
        else:
            self._server_process.kill()
            self._server_process = None
            self.startStopButton.setText("Start server")

    def set_output_path(self):
        diag = QFileDialog()
        diag.setFileMode(QFileDialog.Directory)
        self.output_path = diag.getExistingDirectory(
            self, "Folder do zapisu wyników", self.output_path)
        self.outputDirectoryButton.setText(self.output_path)

    def data_ready(self):
        prev = self.logTextBrowser.toPlainText()
        try:
            new = bytes(self._server_process.readAll()).decode("UTF-8")
            self.logTextBrowser.setText(prev + new)
        except UnicodeDecodeError:
            pass

    def closeEvent(self, QCloseEvent):
        if self._server_process:
            self._server_process.kill()
            self._server_process = None
コード例 #5
0
class PROCESS(QObject):
    queue_finished = pyqtSignal(int)
    prog_finished = pyqtSignal(tuple)
    stoped = pyqtSignal(int, int)  # 1: start, 0: finished, 2: error | queue
    state = pyqtSignal(int, str)

    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        self.process = QProcess()
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.finished.connect(self.emit_finished)
        self.process.stateChanged.connect(self.emit_state)
        self.process_type = 0  # 0 process, 1 runnable
        self.threadpool = QThreadPool()
        self.worker = Worker()
        self.worker.signals.state.connect(self.emit_state)
        self.queue = None

    def emit_state(self, state):
        self.state.emit(state, self.prog_name)

    def emit_finished(self, exitcode, exitstatus):
        self.prog_finished.emit((self.prog_name, exitcode, exitstatus))

    def set_queue(self, queue):
        self.queue = queue

    def start_process(self):
        try:
            obj = self.queue.get(False)
            self.prog_name = list(obj.keys())[0]
            if callable(list(obj.values())[0][0]):
                self.process_type = 1
                funct = list(obj.values())[0][0]
                args = list(obj.values())[0][1]
                self.worker.insert_function(funct, args, self.prog_name)
                self.threadpool.start(self.worker)
            else:
                self.process_type = 0
                self.process.start(
                    list(obj.values())[0][0],
                    list(obj.values())[0][1])
        except Empty:
            self.queue_finished.emit(self.queue.name)

    def force_finished(self):
        # for process (programs)
        self.stoped.emit(1, self.queue.name)
        if self.process_type == 0:
            self.process.terminate()
            if not self.process.waitForFinished(1000):
                self.process.kill()
        else:
            if self.threadpool.activeThreadCount():
                self.threadpool.clear()
                self.threadpool.waitForDone()
        with self.queue.mutex:
            self.queue.queue.clear()
        self.stoped.emit(0, self.queue.name)
コード例 #6
0
class Tunnel(QWidget):
    def __init__(self, name, data):
        super(Tunnel, self).__init__()
        uic.loadUi("tunnel.ui", self)

        self.tunnelconfig = TunnelConfig(self, data)
        self.tunnelconfig.setWindowTitle(name)
        self.tunnelconfig.setModal(True)
        self.name.setText(name)

        self.tunnelconfig.icon = F"./icons/{name}.png"

        if not os.path.exists(self.tunnelconfig.icon):
            self.tunnelconfig.icon = "./icons/robi.png"

        self.icon.setPixmap(QPixmap(self.tunnelconfig.icon))
        self.action_tunnel.clicked.connect(self.do_tunnel)
        self.action_settings.clicked.connect(self.tunnelconfig.show)
        self.action_open.clicked.connect(self.do_open_browser)
        self.action_tunnel.setStyleSheet(LANG.QSS_START)

        self.process = None

    def do_open_browser(self):
        browser_open = self.tunnelconfig.browser_open.text()
        if browser_open:
            urlobj = urlparse(browser_open)
            local_port = self.tunnelconfig.local_port.value()
            new_url = urlobj._replace(
                netloc=F"{urlobj.hostname}:{local_port}").geturl()
            QDesktopServices.openUrl(QUrl(new_url))

    def do_tunnel(self):
        if self.process:
            self.stop_tunnel()
        else:
            self.start_tunnel()

    def start_tunnel(self):
        params = self.tunnelconfig.ssh_command.text().split(" ")

        self.process = QProcess()
        self.process.start(params[0], params[1:])

        self.action_tunnel.setStyleSheet(LANG.QSS_STOP)
        self.action_tunnel.setIcon(QIcon.fromTheme(LANG.ICON_STOP))

        self.do_open_browser()

    def stop_tunnel(self):
        try:
            self.process.kill()
            self.process = None
        except:
            pass

        self.action_tunnel.setIcon(QIcon.fromTheme(LANG.ICON_START))
        self.action_tunnel.setStyleSheet(LANG.QSS_START)
コード例 #7
0
class ProcessThread(QThread):
    _ProcssOutput = pyqtSignal(object)

    def __init__(self, cmd, directory_exec=None):
        QThread.__init__(self)
        self.directory_exec = directory_exec
        self.cmd = cmd

    @pyqtSlot()
    def getNameThread(self):
        return "[New Thread {} ({})]".format(self.procThread.pid(), self.objectName())

    def readProcessOutput(self):
        try:
            self.data = str(self.procThread.readAllStandardOutput(), encoding="ascii")
            self._ProcssOutput.emit(self.data)
        except Exception:
            pass

    def getpid(self):
        """ return the pid of current process in background"""
        return self.procThread.pid()

    def getID(self):
        """ return the name of process in background"""
        return self.objectName()

    def start(self):
        self.procThread = QProcess(self)
        self.procThread.setProcessChannelMode(QProcess.MergedChannels)
        if self.directory_exec:
            self.procThread.setWorkingDirectory(self.directory_exec)
        self.procThread.start(
            list(self.cmd.keys())[0], self.cmd[list(self.cmd.keys())[0]]
        )
        self.procThread.readyReadStandardOutput.connect(self.readProcessOutput)
        print(
            display_messages(
                "starting {} pid: [{}]".format(
                    self.objectName(), self.procThread.pid()
                ),
                sucess=True,
            )
        )

    def stop(self):
        print(
            display_messages(
                "thread {} successfully stopped".format(self.objectName()), info=True
            )
        )
        if hasattr(self, "procThread"):
            self.procThread.terminate()
            self.procThread.waitForFinished()
            self.procThread.kill()
コード例 #8
0
class OutputPane(QTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.process = None
        self.setAcceptRichText(False)
        self.setReadOnly(True)
        self.setLineWrapMode(QTextEdit.NoWrap)
        self.setObjectName('outputpane')

    def append(self, txt):
        tc = self.textCursor()
        tc.movePosition(QTextCursor.End)
        self.setTextCursor(tc)
        self.insertPlainText(txt)
        self.ensureCursorVisible()

    def clear(self):
        self.setText('')

    def get_subprocess_env(self):
        """Get the environment variables for running the subprocess."""
        return {'PYTHONIOENCODING': ENCODING}

    def run(self, *args, cwd=None):
        env = QProcessEnvironment().systemEnvironment()
        for k, v in self.get_subprocess_env().items():
            env.insert(k, v)

        self.process = QProcess(self)
        self.process.setProcessEnvironment(env)
        if cwd:
            self.process.setWorkingDirectory(cwd)
        # self.process.stateChanged.connect(self.stateChanged)
        self.process.readyReadStandardOutput.connect(self.on_stdout_read)
        self.process.readyReadStandardError.connect(self.on_stderr_read)
        self.process.finished.connect(self.on_process_end)

        self.clear()
        self.process.start(args[0], args[1:], QIODevice.ReadWrite)

    def on_stdout_read(self):
        while self.process and self.process.canReadLine():
            text = self.process.readLine().data().decode(ENCODING)
            self.append(text)

    def on_stderr_read(self):
        text = self.process.readAllStandardError().data().decode(ENCODING)
        self.append(text)

    def kill(self):
        if self.process:
            self.process.kill()

    def on_process_end(self):
        self.process = None
コード例 #9
0
ファイル: network.py プロジェクト: zhe1234zou/wifipumpkin3
class ThreadDNSspoofNF(QObject):
    DnsReq = pyqtSignal(object)

    def __init__(self, domains, interface, redirect, APmode=True, parent=None):
        super(ThreadDNSspoofNF, self).__init__(parent)
        self.domains = domains
        self.interface = interface
        self.redirect = redirect
        self.APmode = APmode
        self.desc = "DNS spoof Module::NetFilter"

    @pyqtSlot()
    def readProcessOutput(self):
        self.data = str(self.procThreadDNS.readAllStandardOutput())
        self.DnsReq.emit(self.data)

    def start(self):
        self.setIptables(self.APmode, option="A")
        self.procThreadDNS = QProcess(self)
        self.procThreadDNS.setProcessChannelMode(QProcess.MergedChannels)
        QObject.connect(
            self.procThreadDNS,
            pyqtSignal("readyReadStandardOutput()"),
            self,
            pyqtSlot("readProcessOutput()"),
        )
        self.procThreadDNS.start(
            "python",
            [
                "core/packets/dnsspoofNF.py",
                "-r",
                self.redirect,
                "-d",
                ",".join(self.domains),
            ],
        )

    def setIptables(self, APMode=True, option=str()):
        if APMode:
            system(
                "iptables -{} INPUT -i {} -p udp --dport 53 -s {} -j ACCEPT".
                format(option, self.interface, self.redirect))
            system("iptables -{} INPUT -i {} -p udp --dport 53 -j DROP".format(
                option, self.interface))
            system(
                "iptables -t nat -{} PREROUTING -p udp --dport 53 -j NFQUEUE".
                format(option))

    def stop(self):
        self.setIptables(self.APmode, option="D")
        if hasattr(self, "procThreadDNS"):
            self.procThreadDNS.terminate()
            self.procThreadDNS.waitForFinished()
            self.procThreadDNS.kill()
コード例 #10
0
class _Converter:
    """_Converter class to provide conversion functionality."""

    def __init__(self, library_path):
        """Class initializer."""
        self._library_path = library_path
        self._process = QProcess()

    def setup_converter(self, reader, finisher, process_channel):
        """Set up the QProcess object."""
        self._process.setProcessChannelMode(process_channel)
        self._process.readyRead.connect(reader)
        self._process.finished.connect(finisher)

    def start_converter(self, cmd):
        """Start the encoding process."""
        self._process.start(self._library_path, cmd)

    def stop_converter(self):
        """Terminate the encoding process."""
        self._process.terminate()
        if self.converter_is_running:
            self._process.kill()

    def converter_finished_disconnect(self, connected):
        """Disconnect the QProcess.finished method."""
        self._process.finished.disconnect(connected)

    def close_converter(self):
        """Call QProcess.close method."""
        self._process.close()

    def kill_converter(self):
        """Call QProcess.kill method."""
        self._process.kill()

    def converter_state(self):
        """Call QProcess.state method."""
        return self._process.state()

    def converter_exit_status(self):
        """Call QProcess.exit_status method."""
        return self._process.exitStatus()

    def read_converter_output(self):
        """Call QProcess.readAll method."""
        return str(self._process.readAll())

    @property
    def converter_is_running(self):
        """Return QProcess state."""
        return self._process.state() == QProcess.Running
コード例 #11
0
ファイル: test_qtutils.py プロジェクト: vrama21/qutebrowser
    def test_qprocess(self, py_proc):
        """Test PyQIODevice with a QProcess which is non-sequential.

        This also verifies seek() and tell() behave as expected.
        """
        proc = QProcess()
        proc.start(*py_proc('print("Hello World")'))
        dev = qtutils.PyQIODevice(proc)
        assert not dev.closed
        with pytest.raises(OSError, match='Random access not allowed!'):
            dev.seek(0)
        with pytest.raises(OSError, match='Random access not allowed!'):
            dev.tell()
        proc.waitForFinished(1000)
        proc.kill()
        assert bytes(dev.read()).rstrip() == b'Hello World'
コード例 #12
0
    def test_qprocess(self, py_proc):
        """Test PyQIODevice with a QProcess which is non-sequential.

        This also verifies seek() and tell() behave as expected.
        """
        proc = QProcess()
        proc.start(*py_proc('print("Hello World")'))
        dev = qtutils.PyQIODevice(proc)
        assert not dev.closed
        with pytest.raises(OSError, match='Random access not allowed!'):
            dev.seek(0)
        with pytest.raises(OSError, match='Random access not allowed!'):
            dev.tell()
        proc.waitForFinished(1000)
        proc.kill()
        assert bytes(dev.read()).rstrip() == b'Hello World'
コード例 #13
0
ファイル: test_qtutils.py プロジェクト: robomica/qutebrowser
    def test_qprocess(self):
        """Test PyQIODevice with a QProcess which is non-sequential.

        This also verifies seek() and tell() behave as expected.
        """
        proc = QProcess()
        proc.start(sys.executable, ['-c', 'print("Hello World")'])
        dev = qtutils.PyQIODevice(proc)
        assert not dev.closed
        with pytest.raises(OSError) as excinfo:
            dev.seek(0)
        assert str(excinfo.value) == 'Random access not allowed!'
        with pytest.raises(OSError) as excinfo:
            dev.tell()
        assert str(excinfo.value) == 'Random access not allowed!'
        proc.waitForFinished(1000)
        proc.kill()
        assert bytes(dev.read()).rstrip() == b'Hello World'
コード例 #14
0
    def test_qprocess(self):
        """Test PyQIODevice with a QProcess which is non-sequential.

        This also verifies seek() and tell() behave as expected.
        """
        proc = QProcess()
        proc.start(sys.executable, ['-c', 'print("Hello World")'])
        dev = qtutils.PyQIODevice(proc)
        assert not dev.closed
        with pytest.raises(OSError) as excinfo:
            dev.seek(0)
        assert str(excinfo.value) == 'Random access not allowed!'
        with pytest.raises(OSError) as excinfo:
            dev.tell()
        assert str(excinfo.value) == 'Random access not allowed!'
        proc.waitForFinished(1000)
        proc.kill()
        assert bytes(dev.read()).rstrip() == b'Hello World'
コード例 #15
0
ファイル: ProcessKeyboard.py プロジェクト: yurisnm/pyqt5
class Worker(QObject):
    sendOutput = pyqtSignal(str)

    def __init__(self):
        super(Worker, self).__init__()
        self.process = QProcess()
        self.setupProcess()

    def __del__(self):
        self.process.terminate()
        if not self.process.waitForFinished(10000):
            self.process.kill()

    def setupProcess(self):
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.readyReadStandardOutput.connect(self.readStdOutput)

        self._envgcc = QProcessEnvironment.systemEnvironment()
        self._envgcc.insert("QT5DIR", "/home/epson/Qt/5.8/gcc_64")
        self._envgcc.insert("QT_QPA_PLATFORM_PLUGIN_PATH",
                            "/home/epson/Qt/5.8/gcc_64/plugins/platforms")
        self._envgcc.insert("QT_PLUGIN_PATH",
                            "/home/epson/Qt/5.8/gcc_64/plugins")
        self._envgcc.insert("QML_IMPORT_PATH", "/home/epson/Qt/5.8/gcc_64/qml")
        self._envgcc.insert("QML2_IMPORT_PATH",
                            "/home/epson/Qt/5.8/gcc_64/qml")
        self._envgcc.insert(
            "QT_VIRTUALKEYBOARD_LAYOUT_PATH",
            "/home/epson/Qt/5.8/Src/qtvirtualkeyboard/src/virtualkeyboard/content"
        )
        self._envgcc.insert("QT_VIRTUALKEYBOARD_STYLE", "custom")
        self._envgcc.insert("QT_IM_MODULE", "qtvirtualkeyboard")
        self.process.setProcessEnvironment(self._envgcc)
        self.process.start(
            "/home/epson/INTERACT/interact-ii/examples/ProcessExample.py")

    @pyqtSlot()
    def readStdOutput(self):
        output = str(self.process.readAllStandardOutput())
        # Do some extra processing of the output here if required
        # ...
        self.sendOutput.emit(output)
コード例 #16
0
ファイル: Conda.py プロジェクト: skeptycal/eric6-20.3
    def runProcess(self, args):
        """
        Public method to execute the conda with the given arguments.
        
        The conda executable is called with the given arguments and
        waited for its end.
        
        @param args list of command line arguments
        @type list of str
        @return tuple containing a flag indicating success and the output
            of the process
        @rtype tuple of (bool, str)
        """
        exe = Preferences.getConda("CondaExecutable")
        if not exe:
            exe = "conda"

        process = QProcess()
        process.start(exe, args)
        procStarted = process.waitForStarted(15000)
        if procStarted:
            finished = process.waitForFinished(30000)
            if finished:
                if process.exitCode() == 0:
                    output = str(process.readAllStandardOutput(),
                                 Preferences.getSystem("IOEncoding"),
                                 'replace').strip()
                    return True, output
                else:
                    return (
                        False,
                        self.tr("conda exited with an error ({0}).").format(
                            process.exitCode()))
            else:
                process.terminate()
                process.waitForFinished(2000)
                process.kill()
                process.waitForFinished(3000)
                return False, self.tr("conda did not finish within"
                                      " 30 seconds.")

        return False, self.tr("conda could not be started.")
コード例 #17
0
    def runProcess(self, args, interpreter):
        """
        Public method to execute the current pip with the given arguments.
        
        The selected pip executable is called with the given arguments and
        waited for its end.
        
        @param args list of command line arguments
        @type list of str
        @param interpreter path of the Python interpreter to be used
        @type str
        @return tuple containing a flag indicating success and the output
            of the process
        @rtype tuple of (bool, str)
        """
        ioEncoding = Preferences.getSystem("IOEncoding")

        process = QProcess()
        process.start(interpreter, args)
        procStarted = process.waitForStarted()
        if procStarted:
            finished = process.waitForFinished(30000)
            if finished:
                if process.exitCode() == 0:
                    output = str(process.readAllStandardOutput(), ioEncoding,
                                 'replace')
                    return True, output
                else:
                    return (
                        False,
                        self.tr("python exited with an error ({0}).").format(
                            process.exitCode()))
            else:
                process.terminate()
                process.waitForFinished(2000)
                process.kill()
                process.waitForFinished(3000)
                return False, self.tr("python did not finish within"
                                      " 30 seconds.")

        return False, self.tr("python could not be started.")
コード例 #18
0
ファイル: threads.py プロジェクト: yanrbts/wifipumpkin3
class WorkerProcess(QThread):
    sendOutput = pyqtSignal(str)

    def __init__(self, command):
        super(WorkerProcess, self).__init__()
        self.process = QProcess(self)
        self.command = command

    def __del__(self):
        self.process.terminate()
        if not self.process.waitForFinished(10000):
            self.process.kill()

    def start(self):
        self.setupProcess()

    def setupProcess(self):
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.start(
            list(self.command.keys())[0],
            self.command[list(self.command.keys())[0]])
コード例 #19
0
class ConsoleProcess():
    #subclass that attaches a process and pipes the output to textedit widget console widget
    def __init__(self, console=None, errorHandler=None, finishHandler=None):
        self.process=QProcess()
        self.console=console
        self.cidFile=""
        self.state='stopped'
        if console:
            self.process.readyReadStandardOutput.connect(lambda: self.writeConsole(self.process, console,self.process.readAllStandardOutput,Qt.white))
            self.process.readyReadStandardError.connect(lambda: self.writeConsole(self.process, console,self.process.readAllStandardError,Qt.red))
        if finishHandler:
            self.process.finished.connect(finishHandler)
            
    def writeConsole(self,process,console,read,color):
        console.setTextColor(color)
        console.append(read().data().decode('utf-8',errors="ignore"))
        #console.append('</span>')
    
    def writeMessage(self,message,color=Qt.green):
        #for bwb messages
        self.console.setTextColor(color)
        self.console.append(message)
        
    def stop(self,message=None):
        self.state='stopped'
        #we need to write our own code to 
        with open (self.cidFile,'r') as f:
            cid=f.read()
        stopCmd='docker stop {} '.format(cid)
        sys.stderr.write('Stop command: {}'.format(stopCmd))
        os.system(stopCmd)
        self.process.kill()
        self.cleanup()
        if message:
            self.writeMessage(message)
            
    def cleanup(self):
        os.unlink(self.cidFile)
コード例 #20
0
ファイル: main.py プロジェクト: boojamya/total_duration
    def run_ffprobe(self, is_image_BOOL):
        program = str(Path('./ffprobe'))
        if is_image_BOOL == False:
            args = [
                '-v', 'error', '-select_streams', 'v:0', '-show_entries',
                'format=duration,size:stream=width,height,r_frame_rate',
                self.filePath
            ]
        else:
            args = [
                '-v', 'error', '-select_streams', 'v:0', '-show_entries',
                'format=size:stream=width,height', self.filePath
            ]

        process = QProcess()
        process.setProcessChannelMode(QProcess.MergedChannels)
        process.start(program, args)
        if process.waitForFinished(-1):
            process.kill()
        ffprobe_output = bytearray(
            process.readAllStandardOutput()).decode('UTF-8')

        self.parse_ffprobe_out(ffprobe_output, is_image_BOOL)
コード例 #21
0
class WorkerProcess(QThread):
    sendOutput = pyqtSignal(str)

    def __init__(self, command):
        super(WorkerProcess, self).__init__()
        self.process = QProcess(self)
        self.command = command

    def __del__(self):
        try:
            self.process.terminate()
            self.process.kill()
        except Exception:
            pass

    def start(self):
        self.setupProcess()

    def setupProcess(self):
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.start(
            list(self.command.keys())[0], self.command[list(self.command.keys())[0]]
        )
コード例 #22
0
ファイル: Pip.py プロジェクト: metamarcdw/.dotfiles
    def runProcess(self, args, cmd=""):
        """
        Public method to execute the current pip with the given arguments.
        
        The selected pip executable is called with the given arguments and
        waited for its end.
        
        @param args list of command line arguments (list of string)
        @param cmd pip command to be used (string)
        @return tuple containing a flag indicating success and the output
            of the process (string)
        """
        if not cmd:
            cmd = self.__plugin.getPreferences("CurrentPipExecutable")
        ioEncoding = Preferences.getSystem("IOEncoding")

        process = QProcess()
        process.start(cmd, args)
        procStarted = process.waitForStarted()
        if procStarted:
            finished = process.waitForFinished(30000)
            if finished:
                if process.exitCode() == 0:
                    output = str(process.readAllStandardOutput(), ioEncoding,
                                 'replace')
                    return True, output
                else:
                    return False, self.tr("pip exited with an error ({0}).")\
                        .format(process.exitCode())
            else:
                process.terminate()
                process.waitForFinished(2000)
                process.kill()
                process.waitForFinished(3000)
                return False, self.tr("pip did not finish within 30 seconds.")

        return False, self.tr("pip could not be started.")
コード例 #23
0
ファイル: run_widget.py プロジェクト: ninja-ide/ninja-ide
class Program(QObject):

    def __init__(self, **kwargs):
        QObject.__init__(self)
        self.filename = kwargs.get("filename")
        self.text_code = kwargs.get("code")
        self.__python_exec = kwargs.get("python_exec")
        self.pre_script = kwargs.get("pre_script")
        self.post_script = kwargs.get("post_script")
        self.__params = kwargs.get("params")
        self.__elapsed = QElapsedTimer()

        self.outputw = None

        self.__current_process = None

        self.main_process = QProcess(self)
        self.main_process.started.connect(self._process_started)
        self.main_process.finished.connect(self._process_finished)
        self.main_process.finished.connect(self.__post_execution)
        self.main_process.readyReadStandardOutput.connect(self._refresh_output)
        self.main_process.readyReadStandardError.connect(self._refresh_error)

        self.pre_process = QProcess(self)
        self.pre_process.started.connect(self._process_started)
        self.pre_process.finished.connect(self._process_finished)
        self.pre_process.finished.connect(self.__main_execution)
        self.pre_process.readyReadStandardOutput.connect(self._refresh_output)
        self.pre_process.readyReadStandardError.connect(self._refresh_error)

        self.post_process = QProcess(self)
        self.post_process.started.connect(self._process_started)
        self.post_process.finished.connect(self._process_finished)
        self.post_process.readyReadStandardOutput.connect(self._refresh_output)
        self.post_process.readyReadStandardError.connect(self._refresh_error)

    def start(self):
        self.__pre_execution()
        self.outputw.setFocus()

    def __pre_execution(self):
        """Execute a script before executing the project"""
        self.__current_process = self.pre_process
        file_pre_exec = QFile(self.pre_script)
        if file_pre_exec.exists():
            ext = file_manager.get_file_extension(self.pre_script)
            args = []
            if ext == "py":
                program = self.python_exec
                # -u: Force python to unbuffer stding ad stdout
                args.append("-u")
                args.append(self.pre_script)
            elif ext == "sh":
                program = "bash"
                args.append(self.pre_script)
            else:
                program = self.pre_script
            self.pre_process.setProgram(program)
            self.pre_process.setArguments(args)
            self.pre_process.start()
        else:
            self.__main_execution()

    def __main_execution(self):
        self.__elapsed.start()
        self.__current_process = self.main_process
        if not self.only_text:
            # In case a text is executed and not a file or project
            file_directory = file_manager.get_folder(self.filename)
            self.main_process.setWorkingDirectory(file_directory)
        self.main_process.setProgram(self.python_exec)
        self.main_process.setArguments(self.arguments)
        environment = QProcessEnvironment()
        system_environment = self.main_process.systemEnvironment()
        for env in system_environment:
            key, value = env.split("=", 1)
            environment.insert(key, value)
        self.main_process.setProcessEnvironment(environment)
        self.main_process.start()

    def __post_execution(self):
        """Execute a script after executing the project."""
        self.__current_process = self.post_process
        file_pre_exec = QFile(self.post_script)
        if file_pre_exec.exists():
            ext = file_manager.get_file_extension(self.post_script)
            args = []
            if ext == "py":
                program = self.python_exec
                # -u: Force python to unbuffer stding ad stdout
                args.append("-u")
                args.append(self.post_script)
            elif ext == "sh":
                program = "bash"
                args.append(self.post_script)
            else:
                program = self.post_script
            self.post_process.setProgram(program)
            self.post_process.setArguments(args)
            self.post_process.start()

    @property
    def process_name(self):
        proc = self.__current_process
        return proc.program() + " " + " ".join(proc.arguments())

    def update(self, **kwargs):
        self.text_code = kwargs.get("code")
        self.__python_exec = kwargs.get("python_exec")
        self.pre_script = kwargs.get("pre_script")
        self.post_script = kwargs.get("post_script")
        self.__params = kwargs.get("params")

    def set_output_widget(self, ow):
        self.outputw = ow
        self.outputw.inputRequested.connect(self._write_input)

    def _write_input(self, data):
        self.main_process.write(data.encode())
        self.main_process.write(b"\n")

    def is_running(self):
        running = False
        if self.main_process.state() == QProcess.Running:
            running = True
        return running

    def _process_started(self):
        time_str = QTime.currentTime().toString("hh:mm:ss")
        text = time_str + " Running: " + self.process_name
        self.outputw.append_text(text)
        self.outputw.setReadOnly(False)

    def _process_finished(self, code, status):
        frmt = OutputWidget.Format.NORMAL
        if status == QProcess.NormalExit == code:
            text = translations.TR_PROCESS_EXITED_NORMALLY % code
        else:
            text = translations.TR_PROCESS_INTERRUPTED
            frmt = OutputWidget.Format.ERROR
        self.outputw.append_text(text, frmt)
        if self.__current_process is self.main_process:
            tformat = QTime(0, 0, 0, 0).addMSecs(
                self.__elapsed.elapsed() + 500)
            time = tformat.toString("h:mm:ss")
            if time.startswith("0:"):
                # Don't display zero hours
                time = time[2:]
            self.outputw.append_text(translations.TR_ELAPSED_TIME.format(time))
        self.outputw.setReadOnly(True)

    def _refresh_output(self):
        data = self.__current_process.readAllStandardOutput().data().decode()
        for line in data.splitlines():
            self.outputw.append_text(
                line, text_format=OutputWidget.Format.NORMAL)

    def _refresh_error(self):
        data = self.__current_process.readAllStandardError().data().decode()
        for line_text in data.splitlines():
            frmt = OutputWidget.Format.ERROR
            if self.outputw.patLink.match(line_text):
                frmt = OutputWidget.Format.ERROR_UNDERLINE
            self.outputw.append_text(line_text, frmt)

    def display_name(self):
        name = "New document"
        if not self.only_text:
            name = file_manager.get_basename(self.filename)
        return name

    @property
    def only_text(self):
        return self.filename is None

    @property
    def python_exec(self):
        py_exec = self.__python_exec
        if not py_exec:
            py_exec = settings.PYTHON_EXEC
        return py_exec

    @property
    def arguments(self):
        args = []
        if self.text_code:
            args.append("-c")
            args.append(self.text_code)
        else:
            # Force python to unbuffer stding and stdout
            args.append("-u")
            args += settings.EXECUTION_OPTIONS.split()
            args.append(self.filename)
        return args

    def kill(self):
        self.main_process.kill()
コード例 #24
0
class HgGpgSignaturesDialog(QDialog, Ui_HgGpgSignaturesDialog):
    """
    Class implementing a dialog showing signed changesets.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent reference to the parent widget (QWidget)
        """
        super(HgGpgSignaturesDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.process = QProcess()
        self.vcs = vcs
        self.__hgClient = vcs.getClient()
        
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.show()
        QCoreApplication.processEvents()
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        e.accept()
    
    def start(self, path):
        """
        Public slot to start the list command.
        
        @param path name of directory (string)
        """
        self.errorGroup.hide()
        
        self.intercept = False
        self.activateWindow()
        
        self.__path = path
        dname, fname = self.vcs.splitPath(path)
        
        # find the root of the repo
        repodir = dname
        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
            repodir = os.path.dirname(repodir)
            if os.path.splitdrive(repodir)[1] == os.sep:
                return
        
        args = self.vcs.initCommand("sigs")
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines():
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break
            self.__finish()
        else:
            self.process.kill()
            self.process.setWorkingDirectory(repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
            else:
                self.inputGroup.setEnabled(True)
                self.inputGroup.show()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        self.process = None
        
        if self.signaturesList.topLevelItemCount() == 0:
            # no patches present
            self.__generateItem("", "", self.tr("no signatures found"))
        self.__resizeColumns()
        self.__resort()
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.signaturesList.sortItems(
            self.signaturesList.sortColumn(),
            self.signaturesList.header().sortIndicatorOrder())
    
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.signaturesList.header().resizeSections(
            QHeaderView.ResizeToContents)
        self.signaturesList.header().setStretchLastSection(True)
    
    def __generateItem(self, revision, changeset, signature):
        """
        Private method to generate a patch item in the list of patches.
        
        @param revision revision number (string)
        @param changeset changeset of the bookmark (string)
        @param signature signature of the changeset (string)
        """
        if revision == "" and changeset == "":
            QTreeWidgetItem(self.signaturesList, [signature])
        else:
            revString = "{0:>7}:{1}".format(revision, changeset)
            topItems = self.signaturesList.findItems(
                revString, Qt.MatchExactly)
            if len(topItems) == 0:
                # first signature for this changeset
                topItm = QTreeWidgetItem(self.signaturesList, [
                    "{0:>7}:{1}".format(revision, changeset)])
                topItm.setExpanded(True)
                font = topItm.font(0)
                font.setBold(True)
                topItm.setFont(0, font)
            else:
                topItm = topItems[0]
            QTreeWidgetItem(topItm, [signature])
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(), self.vcs.getEncoding(),
                    'replace').strip()
            self.__processOutputLine(s)
    
    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        li = line.split()
        if li[-1][0] in "1234567890":
            # last element is a rev:changeset
            rev, changeset = li[-1].split(":", 1)
            del li[-1]
            signature = " ".join(li)
            self.__generateItem(rev, changeset, signature)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    @pyqtSlot()
    def on_signaturesList_itemSelectionChanged(self):
        """
        Private slot handling changes of the selection.
        """
        selectedItems = self.signaturesList.selectedItems()
        if len(selectedItems) == 1 and \
           self.signaturesList.indexOfTopLevelItem(selectedItems[0]) != -1:
            self.verifyButton.setEnabled(True)
        else:
            self.verifyButton.setEnabled(False)
    
    @pyqtSlot()
    def on_verifyButton_clicked(self):
        """
        Private slot to verify the signatures of the selected revision.
        """
        rev = self.signaturesList.selectedItems()[0].text(0)\
            .split(":")[0].strip()
        self.vcs.getExtensionObject("gpg")\
            .hgGpgVerifySignatures(self.__path, rev)
    
    @pyqtSlot(str)
    def on_categoryCombo_activated(self, txt):
        """
        Private slot called, when a new filter category is selected.
        
        @param txt text of the selected category (string)
        """
        self.__filterSignatures()
    
    @pyqtSlot(str)
    def on_rxEdit_textChanged(self, txt):
        """
        Private slot called, when a filter expression is entered.
        
        @param txt filter expression (string)
        """
        self.__filterSignatures()
    
    def __filterSignatures(self):
        """
        Private method to filter the log entries.
        """
        searchRxText = self.rxEdit.text()
        filterTop = self.categoryCombo.currentText() == self.tr("Revision")
        if filterTop and searchRxText.startswith("^"):
            searchRx = QRegExp(
                "^\s*{0}".format(searchRxText[1:]), Qt.CaseInsensitive)
        else:
            searchRx = QRegExp(searchRxText, Qt.CaseInsensitive)
        for topIndex in range(self.signaturesList.topLevelItemCount()):
            topLevelItem = self.signaturesList.topLevelItem(topIndex)
            if filterTop:
                topLevelItem.setHidden(
                    searchRx.indexIn(topLevelItem.text(0)) == -1)
            else:
                visibleChildren = topLevelItem.childCount()
                for childIndex in range(topLevelItem.childCount()):
                    childItem = topLevelItem.child(childIndex)
                    if searchRx.indexIn(childItem.text(0)) == -1:
                        childItem.setHidden(True)
                        visibleChildren -= 1
                    else:
                        childItem.setHidden(False)
                topLevelItem.setHidden(visibleChildren == 0)
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgGpgSignaturesDialog, self).keyPressEvent(evt)
コード例 #25
0
ファイル: HgStatusDialog.py プロジェクト: testmana2/test
class HgStatusDialog(QWidget, Ui_HgStatusDialog):
    """
    Class implementing a dialog to show the output of the hg status command
    process.
    """

    def __init__(self, vcs, mq=False, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param mq flag indicating to show a queue repo status (boolean)
        @param parent parent widget (QWidget)
        """
        super(HgStatusDialog, self).__init__(parent)
        self.setupUi(self)

        self.__toBeCommittedColumn = 0
        self.__statusColumn = 1
        self.__pathColumn = 2
        self.__lastColumn = self.statusList.columnCount()

        self.refreshButton = self.buttonBox.addButton(self.tr("Refresh"), QDialogButtonBox.ActionRole)
        self.refreshButton.setToolTip(self.tr("Press to refresh the status display"))
        self.refreshButton.setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)

        self.diff = None
        self.vcs = vcs
        self.vcs.committed.connect(self.__committed)
        self.__hgClient = self.vcs.getClient()
        self.__mq = mq
        if self.__hgClient:
            self.process = None
        else:
            self.process = QProcess()
            self.process.finished.connect(self.__procFinished)
            self.process.readyReadStandardOutput.connect(self.__readStdout)
            self.process.readyReadStandardError.connect(self.__readStderr)

        self.diffSplitter.setSizes([350, 250])
        self.__diffSplitterState = None

        self.statusList.headerItem().setText(self.__lastColumn, "")
        self.statusList.header().setSortIndicator(self.__pathColumn, Qt.AscendingOrder)

        font = Preferences.getEditorOtherFonts("MonospacedFont")
        self.diffEdit.setFontFamily(font.family())
        self.diffEdit.setFontPointSize(font.pointSize())

        self.diffHighlighter = HgDiffHighlighter(self.diffEdit.document())
        self.__diffGenerator = HgDiffGenerator(vcs, self)
        self.__diffGenerator.finished.connect(self.__generatorFinished)

        self.__selectedName = ""

        if mq:
            self.buttonsLine.setVisible(False)
            self.addButton.setVisible(False)
            self.diffButton.setVisible(False)
            self.sbsDiffButton.setVisible(False)
            self.revertButton.setVisible(False)
            self.forgetButton.setVisible(False)
            self.restoreButton.setVisible(False)

            self.diffEdit.setVisible(False)

        self.menuactions = []
        self.lfActions = []
        self.menu = QMenu()
        if not mq:
            self.__commitAct = self.menu.addAction(self.tr("Commit changes to repository..."), self.__commit)
            self.menuactions.append(self.__commitAct)
            self.menuactions.append(self.menu.addAction(self.tr("Select all for commit"), self.__commitSelectAll))
            self.menuactions.append(self.menu.addAction(self.tr("Deselect all from commit"), self.__commitDeselectAll))
            self.menu.addSeparator()
            self.__addAct = self.menu.addAction(self.tr("Add to repository"), self.__add)
            self.menuactions.append(self.__addAct)
            if self.vcs.version >= (2, 0):
                self.lfActions.append(self.menu.addAction(self.tr("Add as Large File"), lambda: self.__lfAdd("large")))
                self.lfActions.append(
                    self.menu.addAction(self.tr("Add as Normal File"), lambda: self.__lfAdd("normal"))
                )
            self.__diffAct = self.menu.addAction(self.tr("Show differences"), self.__diff)
            self.menuactions.append(self.__diffAct)
            self.__sbsDiffAct = self.menu.addAction(self.tr("Show differences side-by-side"), self.__sbsDiff)
            self.menuactions.append(self.__sbsDiffAct)
            self.__revertAct = self.menu.addAction(self.tr("Revert changes"), self.__revert)
            self.menuactions.append(self.__revertAct)
            self.__forgetAct = self.menu.addAction(self.tr("Forget missing"), self.__forget)
            self.menuactions.append(self.__forgetAct)
            self.__restoreAct = self.menu.addAction(self.tr("Restore missing"), self.__restoreMissing)
            self.menuactions.append(self.__restoreAct)
            self.menu.addSeparator()
            self.menuactions.append(self.menu.addAction(self.tr("Adjust column sizes"), self.__resizeColumns))
            for act in self.menuactions:
                act.setEnabled(False)
            for act in self.lfActions:
                act.setEnabled(False)

            self.statusList.setContextMenuPolicy(Qt.CustomContextMenu)
            self.statusList.customContextMenuRequested.connect(self.__showContextMenu)

        if not mq and self.vcs.version >= (2, 0):
            self.__lfAddActions = []
            self.__addButtonMenu = QMenu()
            self.__addButtonMenu.addAction(self.tr("Add"), self.__add)
            self.__lfAddActions.append(
                self.__addButtonMenu.addAction(self.tr("Add as Large File"), lambda: self.__lfAdd("large"))
            )
            self.__lfAddActions.append(
                self.__addButtonMenu.addAction(self.tr("Add as Normal File"), lambda: self.__lfAdd("normal"))
            )
            self.addButton.setMenu(self.__addButtonMenu)
            self.__addButtonMenu.aboutToShow.connect(self.__showAddMenu)

        self.modifiedIndicators = [self.tr("added"), self.tr("modified"), self.tr("removed")]

        self.unversionedIndicators = [self.tr("not tracked")]

        self.missingIndicators = [self.tr("missing")]

        self.status = {
            "A": self.tr("added"),
            "C": self.tr("normal"),
            "I": self.tr("ignored"),
            "M": self.tr("modified"),
            "R": self.tr("removed"),
            "?": self.tr("not tracked"),
            "!": self.tr("missing"),
        }

    def show(self):
        """
        Public slot to show the dialog.
        """
        super(HgStatusDialog, self).show()

        if not self.__mq and self.__diffSplitterState:
            self.diffSplitter.restoreState(self.__diffSplitterState)

    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.statusList.sortItems(self.statusList.sortColumn(), self.statusList.header().sortIndicatorOrder())

    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.statusList.header().resizeSections(QHeaderView.ResizeToContents)
        self.statusList.header().setStretchLastSection(True)

    def __generateItem(self, status, path):
        """
        Private method to generate a status item in the status list.
        
        @param status status indicator (string)
        @param path path of the file or directory (string)
        """
        statusText = self.status[status]
        itm = QTreeWidgetItem(self.statusList, ["", statusText, path])

        itm.setTextAlignment(1, Qt.AlignHCenter)
        itm.setTextAlignment(2, Qt.AlignLeft)

        if status in "AMR":
            itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable)
            itm.setCheckState(self.__toBeCommittedColumn, Qt.Checked)
        else:
            itm.setFlags(itm.flags() & ~Qt.ItemIsUserCheckable)

        if statusText not in self.__statusFilters:
            self.__statusFilters.append(statusText)

    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)

        if not self.__mq:
            self.__diffSplitterState = self.diffSplitter.saveState()

        e.accept()

    def start(self, fn):
        """
        Public slot to start the hg status command.
        
        @param fn filename(s)/directoryname(s) to show the status of
            (string or list of strings)
        """
        self.errorGroup.hide()
        self.intercept = False
        self.args = fn

        for act in self.menuactions:
            act.setEnabled(False)
        for act in self.lfActions:
            act.setEnabled(False)

        self.addButton.setEnabled(False)
        self.commitButton.setEnabled(False)
        self.diffButton.setEnabled(False)
        self.sbsDiffButton.setEnabled(False)
        self.revertButton.setEnabled(False)
        self.forgetButton.setEnabled(False)
        self.restoreButton.setEnabled(False)

        self.statusFilterCombo.clear()
        self.__statusFilters = []
        self.statusList.clear()

        if self.__mq:
            self.setWindowTitle(self.tr("Mercurial Queue Repository Status"))
        else:
            self.setWindowTitle(self.tr("Mercurial Status"))

        args = self.vcs.initCommand("status")
        if self.__mq:
            args.append("--mq")
            if isinstance(fn, list):
                self.dname, fnames = self.vcs.splitPathList(fn)
            else:
                self.dname, fname = self.vcs.splitPath(fn)
        else:
            if self.vcs.hasSubrepositories():
                args.append("--subrepos")

            if isinstance(fn, list):
                self.dname, fnames = self.vcs.splitPathList(fn)
                self.vcs.addArguments(args, fn)
            else:
                self.dname, fname = self.vcs.splitPath(fn)
                args.append(fn)

        # find the root of the repo
        repodir = self.dname
        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
            repodir = os.path.dirname(repodir)
            if os.path.splitdrive(repodir)[1] == os.sep:
                return

        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            self.refreshButton.setEnabled(False)

            out, err = self.__hgClient.runcommand(args)
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines():
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break
            self.__finish()
        else:
            if self.process:
                self.process.kill()

            self.process.setWorkingDirectory(repodir)

            self.process.start("hg", args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr("Process Generation Error"),
                    self.tr("The process {0} could not be started. " "Ensure, that it is in the search path.").format(
                        "hg"
                    ),
                )
            else:
                self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
                self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
                self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)

                self.inputGroup.setEnabled(True)
                self.inputGroup.show()
                self.refreshButton.setEnabled(False)

    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)

        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        self.refreshButton.setEnabled(True)

        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(Qt.OtherFocusReason)

        self.__statusFilters.sort()
        self.__statusFilters.insert(0, "<{0}>".format(self.tr("all")))
        self.statusFilterCombo.addItems(self.__statusFilters)

        for act in self.menuactions:
            act.setEnabled(True)

        self.__resort()
        self.__resizeColumns()

        self.__updateButtons()
        self.__updateCommitButton()

        self.__refreshDiff()

    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
        elif button == self.refreshButton:
            self.on_refreshButton_clicked()

    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()

    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        if self.process is not None:
            self.process.setReadChannel(QProcess.StandardOutput)

            while self.process.canReadLine():
                line = str(self.process.readLine(), self.vcs.getEncoding(), "replace")
                self.__processOutputLine(line)

    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        if line[0] in "ACIMR?!" and line[1] == " ":
            status, path = line.strip().split(" ", 1)
            self.__generateItem(status, path)

    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(), self.vcs.getEncoding(), "replace")
            self.__showError(s)

    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()

    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)

    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep

        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()

        self.process.write(input)

        self.passwordCheckBox.setChecked(False)
        self.input.clear()

    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()

    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgStatusDialog, self).keyPressEvent(evt)

    @pyqtSlot()
    def on_refreshButton_clicked(self):
        """
        Private slot to refresh the status display.
        """
        selectedItems = self.statusList.selectedItems()
        if len(selectedItems) == 1:
            self.__selectedName = selectedItems[0].text(self.__pathColumn)
        else:
            self.__selectedName = ""

        self.start(self.args)

    def __updateButtons(self):
        """
        Private method to update the VCS buttons status.
        """
        modified = len(self.__getModifiedItems())
        unversioned = len(self.__getUnversionedItems())
        missing = len(self.__getMissingItems())

        self.addButton.setEnabled(unversioned)
        self.diffButton.setEnabled(modified)
        self.sbsDiffButton.setEnabled(modified == 1)
        self.revertButton.setEnabled(modified)
        self.forgetButton.setEnabled(missing)
        self.restoreButton.setEnabled(missing)

    def __updateCommitButton(self):
        """
        Private method to update the Commit button status.
        """
        commitable = len(self.__getCommitableItems())
        self.commitButton.setEnabled(commitable)

    @pyqtSlot(str)
    def on_statusFilterCombo_activated(self, txt):
        """
        Private slot to react to the selection of a status filter.
        
        @param txt selected status filter (string)
        """
        if txt == "<{0}>".format(self.tr("all")):
            for topIndex in range(self.statusList.topLevelItemCount()):
                topItem = self.statusList.topLevelItem(topIndex)
                topItem.setHidden(False)
        else:
            for topIndex in range(self.statusList.topLevelItemCount()):
                topItem = self.statusList.topLevelItem(topIndex)
                topItem.setHidden(topItem.text(self.__statusColumn) != txt)

    @pyqtSlot(QTreeWidgetItem, int)
    def on_statusList_itemChanged(self, item, column):
        """
        Private slot to act upon item changes.
        
        @param item reference to the changed item (QTreeWidgetItem)
        @param column index of column that changed (integer)
        """
        if column == self.__toBeCommittedColumn:
            self.__updateCommitButton()

    @pyqtSlot()
    def on_statusList_itemSelectionChanged(self):
        """
        Private slot to act upon changes of selected items.
        """
        self.__updateButtons()
        self.__generateDiffs()

    @pyqtSlot()
    def on_commitButton_clicked(self):
        """
        Private slot to handle the press of the Commit button.
        """
        self.__commit()

    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to handle the press of the Add button.
        """
        self.__add()

    @pyqtSlot()
    def on_diffButton_clicked(self):
        """
        Private slot to handle the press of the Differences button.
        """
        self.__diff()

    @pyqtSlot()
    def on_sbsDiffButton_clicked(self):
        """
        Private slot to handle the press of the Side-by-Side Diff button.
        """
        self.__sbsDiff()

    @pyqtSlot()
    def on_revertButton_clicked(self):
        """
        Private slot to handle the press of the Revert button.
        """
        self.__revert()

    @pyqtSlot()
    def on_forgetButton_clicked(self):
        """
        Private slot to handle the press of the Forget button.
        """
        self.__forget()

    @pyqtSlot()
    def on_restoreButton_clicked(self):
        """
        Private slot to handle the press of the Restore button.
        """
        self.__restoreMissing()

    ###########################################################################
    ## Context menu handling methods
    ###########################################################################

    def __showContextMenu(self, coord):
        """
        Private slot to show the context menu of the status list.
        
        @param coord the position of the mouse pointer (QPoint)
        """
        modified = len(self.__getModifiedItems())
        unversioned = len(self.__getUnversionedItems())
        missing = len(self.__getMissingItems())
        commitable = len(self.__getCommitableItems())

        self.__addAct.setEnabled(unversioned)
        self.__diffAct.setEnabled(modified)
        self.__sbsDiffAct.setEnabled(modified == 1)
        self.__revertAct.setEnabled(modified)
        self.__forgetAct.setEnabled(missing)
        self.__restoreAct.setEnabled(missing)
        self.__commitAct.setEnabled(commitable)

        if self.vcs.isExtensionActive("largefiles"):
            enable = len(self.__getUnversionedItems()) > 0
        else:
            enable = False
        for act in self.lfActions:
            act.setEnabled(enable)
        self.menu.popup(self.statusList.mapToGlobal(coord))

    def __showAddMenu(self):
        """
        Private slot to prepare the Add button menu before it is shown.
        """
        enable = self.vcs.isExtensionActive("largefiles")
        for act in self.__lfAddActions:
            act.setEnabled(enable)

    def __commit(self):
        """
        Private slot to handle the Commit context menu entry.
        """
        if self.__mq:
            self.vcs.vcsCommit(self.dname, "", mq=True)
        else:
            names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getCommitableItems()]
            if not names:
                E5MessageBox.information(
                    self, self.tr("Commit"), self.tr("""There are no entries selected to be""" """ committed.""")
                )
                return

            if Preferences.getVCS("AutoSaveFiles"):
                vm = e5App().getObject("ViewManager")
                for name in names:
                    vm.saveEditor(name)
            self.vcs.vcsCommit(names, "")

    def __committed(self):
        """
        Private slot called after the commit has finished.
        """
        if self.isVisible():
            self.on_refreshButton_clicked()
            self.vcs.checkVCSStatus()

    def __commitSelectAll(self):
        """
        Private slot to select all entries for commit.
        """
        self.__commitSelect(True)

    def __commitDeselectAll(self):
        """
        Private slot to deselect all entries from commit.
        """
        self.__commitSelect(False)

    def __add(self):
        """
        Private slot to handle the Add context menu entry.
        """
        names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getUnversionedItems()]
        if not names:
            E5MessageBox.information(
                self, self.tr("Add"), self.tr("""There are no unversioned entries""" """ available/selected.""")
            )
            return

        self.vcs.vcsAdd(names)
        self.on_refreshButton_clicked()

        project = e5App().getObject("Project")
        for name in names:
            project.getModel().updateVCSStatus(name)
        self.vcs.checkVCSStatus()

    def __lfAdd(self, mode):
        """
        Private slot to add a file to the repository.
        
        @param mode add mode (string one of 'normal' or 'large')
        """
        names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getUnversionedItems()]
        if not names:
            E5MessageBox.information(
                self, self.tr("Add"), self.tr("""There are no unversioned entries""" """ available/selected.""")
            )
            return

        self.vcs.getExtensionObject("largefiles").hgAdd(names, mode)
        self.on_refreshButton_clicked()

        project = e5App().getObject("Project")
        for name in names:
            project.getModel().updateVCSStatus(name)
        self.vcs.checkVCSStatus()

    def __forget(self):
        """
        Private slot to handle the Remove context menu entry.
        """
        names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getMissingItems()]
        if not names:
            E5MessageBox.information(
                self, self.tr("Remove"), self.tr("""There are no missing entries""" """ available/selected.""")
            )
            return

        self.vcs.hgForget(names)
        self.on_refreshButton_clicked()

    def __revert(self):
        """
        Private slot to handle the Revert context menu entry.
        """
        names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems()]
        if not names:
            E5MessageBox.information(
                self, self.tr("Revert"), self.tr("""There are no uncommitted changes""" """ available/selected.""")
            )
            return

        self.vcs.hgRevert(names)
        self.raise_()
        self.activateWindow()
        self.on_refreshButton_clicked()

        project = e5App().getObject("Project")
        for name in names:
            project.getModel().updateVCSStatus(name)
        self.vcs.checkVCSStatus()

    def __restoreMissing(self):
        """
        Private slot to handle the Restore Missing context menu entry.
        """
        names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getMissingItems()]
        if not names:
            E5MessageBox.information(
                self, self.tr("Revert"), self.tr("""There are no missing entries""" """ available/selected.""")
            )
            return

        self.vcs.hgRevert(names)
        self.on_refreshButton_clicked()
        self.vcs.checkVCSStatus()

    def __diff(self):
        """
        Private slot to handle the Diff context menu entry.
        """
        names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems()]
        if not names:
            E5MessageBox.information(
                self, self.tr("Differences"), self.tr("""There are no uncommitted changes""" """ available/selected.""")
            )
            return

        if self.diff is None:
            from .HgDiffDialog import HgDiffDialog

            self.diff = HgDiffDialog(self.vcs)
        self.diff.show()
        self.diff.start(names, refreshable=True)

    def __sbsDiff(self):
        """
        Private slot to handle the Diff context menu entry.
        """
        names = [os.path.join(self.dname, itm.text(self.__pathColumn)) for itm in self.__getModifiedItems()]
        if not names:
            E5MessageBox.information(
                self,
                self.tr("Side-by-Side Diff"),
                self.tr("""There are no uncommitted changes""" """ available/selected."""),
            )
            return
        elif len(names) > 1:
            E5MessageBox.information(
                self,
                self.tr("Side-by-Side Diff"),
                self.tr("""Only one file with uncommitted changes""" """ must be selected."""),
            )
            return

        self.vcs.hgSbsDiff(names[0])

    def __getCommitableItems(self):
        """
        Private method to retrieve all entries the user wants to commit.
        
        @return list of all items, the user has checked
        """
        commitableItems = []
        for index in range(self.statusList.topLevelItemCount()):
            itm = self.statusList.topLevelItem(index)
            if itm.checkState(self.__toBeCommittedColumn) == Qt.Checked:
                commitableItems.append(itm)
        return commitableItems

    def __getModifiedItems(self):
        """
        Private method to retrieve all entries, that have a modified status.
        
        @return list of all items with a modified status
        """
        modifiedItems = []
        for itm in self.statusList.selectedItems():
            if itm.text(self.__statusColumn) in self.modifiedIndicators:
                modifiedItems.append(itm)
        return modifiedItems

    def __getUnversionedItems(self):
        """
        Private method to retrieve all entries, that have an unversioned
        status.
        
        @return list of all items with an unversioned status
        """
        unversionedItems = []
        for itm in self.statusList.selectedItems():
            if itm.text(self.__statusColumn) in self.unversionedIndicators:
                unversionedItems.append(itm)
        return unversionedItems

    def __getMissingItems(self):
        """
        Private method to retrieve all entries, that have a missing status.
        
        @return list of all items with a missing status
        """
        missingItems = []
        for itm in self.statusList.selectedItems():
            if itm.text(self.__statusColumn) in self.missingIndicators:
                missingItems.append(itm)
        return missingItems

    def __commitSelect(self, selected):
        """
        Private slot to select or deselect all entries.
        
        @param selected commit selection state to be set (boolean)
        """
        for index in range(self.statusList.topLevelItemCount()):
            itm = self.statusList.topLevelItem(index)
            if itm.flags() & Qt.ItemIsUserCheckable:
                if selected:
                    itm.setCheckState(self.__toBeCommittedColumn, Qt.Checked)
                else:
                    itm.setCheckState(self.__toBeCommittedColumn, Qt.Unchecked)

    ###########################################################################
    ## Diff handling methods below
    ###########################################################################

    def __generateDiffs(self):
        """
        Private slot to generate diff outputs for the selected item.
        """
        self.diffEdit.clear()

        if not self.__mq:
            selectedItems = self.statusList.selectedItems()
            if len(selectedItems) == 1:
                fn = os.path.join(self.dname, selectedItems[0].text(self.__pathColumn))
                self.__diffGenerator.start(fn)

    def __generatorFinished(self):
        """
        Private slot connected to the finished signal of the diff generator.
        """
        diff = self.__diffGenerator.getResult()[0]

        if diff:
            for line in diff[:]:
                if line.startswith("@@ "):
                    break
                else:
                    diff.pop(0)
            self.diffEdit.setPlainText("".join(diff))

        tc = self.diffEdit.textCursor()
        tc.movePosition(QTextCursor.Start)
        self.diffEdit.setTextCursor(tc)
        self.diffEdit.ensureCursorVisible()

    def __refreshDiff(self):
        """
        Private method to refresh the diff output after a refresh.
        """
        if self.__selectedName and not self.__mq:
            for index in range(self.statusList.topLevelItemCount()):
                itm = self.statusList.topLevelItem(index)
                if itm.text(self.__pathColumn) == self.__selectedName:
                    itm.setSelected(True)
                    break

        self.__selectedName = ""
コード例 #26
0
ファイル: HgLogDialog.py プロジェクト: pycom/EricShort
class HgLogDialog(QWidget, Ui_HgLogDialog):
    """
    Class implementing a dialog to show the output of the hg log command
    process.
    
    The dialog is nonmodal. Clicking a link in the upper text pane shows
    a diff of the revisions.
    """
    def __init__(self, vcs, mode="log", bundle=None, isFile=False,
                 parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param mode mode of the dialog (string; one of log, incoming, outgoing)
        @param bundle name of a bundle file (string)
        @param isFile flag indicating log for a file is to be shown (boolean)
        @param parent parent widget (QWidget)
        """
        super(HgLogDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.process = QProcess()
        self.vcs = vcs
        if mode in ("log", "incoming", "outgoing"):
            self.mode = mode
        else:
            self.mode = "log"
        self.bundle = bundle
        self.__hgClient = self.vcs.getClient()
        
        self.contents.setHtml(
            self.tr('<b>Processing your request, please wait...</b>'))
        
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.contents.anchorClicked.connect(self.__sourceChanged)
        
        self.revisions = []  # stack of remembered revisions
        self.revString = self.tr('Revision')
        self.projectMode = False
        
        self.logEntries = []        # list of log entries
        self.lastLogEntry = {}
        self.fileCopies = {}
        self.endInitialText = False
        self.initialText = []
        
        self.diff = None
        
        self.sbsCheckBox.setEnabled(isFile)
        self.sbsCheckBox.setVisible(isFile)
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        e.accept()
    
    def start(self, fn, noEntries=0, revisions=None):
        """
        Public slot to start the hg log command.
        
        @param fn filename to show the log for (string)
        @param noEntries number of entries to show (integer)
        @param revisions revisions to show log for (list of strings)
        """
        self.errorGroup.hide()
        QApplication.processEvents()
        
        self.intercept = False
        self.filename = fn
        self.dname, self.fname = self.vcs.splitPath(fn)
        
        # find the root of the repo
        self.repodir = self.dname
        while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)):
            self.repodir = os.path.dirname(self.repodir)
            if os.path.splitdrive(self.repodir)[1] == os.sep:
                return
        
        self.projectMode = (self.fname == "." and self.dname == self.repodir)
        
        self.activateWindow()
        self.raise_()
        
        preargs = []
        args = self.vcs.initCommand(self.mode)
        if noEntries and self.mode == "log":
            args.append('--limit')
            args.append(str(noEntries))
        if self.mode in ("incoming", "outgoing"):
            args.append("--newest-first")
            if self.vcs.hasSubrepositories():
                args.append("--subrepos")
        if self.mode == "log":
            args.append('--copies')
        if self.vcs.version >= (3, 0):
            args.append('--template')
            args.append(os.path.join(os.path.dirname(__file__),
                                     "templates",
                                     "logDialogBookmarkPhase.tmpl"))
        else:
            args.append('--style')
            if self.vcs.version >= (2, 1):
                args.append(os.path.join(os.path.dirname(__file__),
                                         "styles",
                                         "logDialogBookmarkPhase.style"))
            else:
                args.append(os.path.join(os.path.dirname(__file__),
                                         "styles",
                                         "logDialogBookmark.style"))
        if self.mode == "incoming":
            if self.bundle:
                args.append(self.bundle)
            elif not self.vcs.hasSubrepositories():
                project = e5App().getObject("Project")
                self.vcs.bundleFile = os.path.join(
                    project.getProjectManagementDir(), "hg-bundle.hg")
                if os.path.exists(self.vcs.bundleFile):
                    os.remove(self.vcs.bundleFile)
                preargs = args[:]
                preargs.append("--quiet")
                preargs.append('--bundle')
                preargs.append(self.vcs.bundleFile)
                args.append(self.vcs.bundleFile)
        if revisions:
            for rev in revisions:
                args.append("--rev")
                args.append(rev)
        if not self.projectMode:
            args.append(self.filename)
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            if preargs:
                out, err = self.__hgClient.runcommand(preargs)
            else:
                err = ""
            if err:
                self.__showError(err)
            elif self.mode != "incoming" or \
                (self.vcs.bundleFile and
                 os.path.exists(self.vcs.bundleFile)) or \
                    self.bundle:
                out, err = self.__hgClient.runcommand(args)
                if err:
                    self.__showError(err)
                if out and self.isVisible():
                    for line in out.splitlines(True):
                        self.__processOutputLine(line)
                        if self.__hgClient.wasCanceled():
                            break
            self.__finish()
        else:
            self.process.kill()
            
            self.process.setWorkingDirectory(self.repodir)
            
            if preargs:
                process = QProcess()
                process.setWorkingDirectory(self.repodir)
                process.start('hg', args)
                procStarted = process.waitForStarted(5000)
                if procStarted:
                    process.waitForFinished(30000)
            
            if self.mode != "incoming" or \
                (self.vcs.bundleFile and
                 os.path.exists(self.vcs.bundleFile)) or \
                    self.bundle:
                self.process.start('hg', args)
                procStarted = self.process.waitForStarted(5000)
                if not procStarted:
                    self.inputGroup.setEnabled(False)
                    self.inputGroup.hide()
                    E5MessageBox.critical(
                        self,
                        self.tr('Process Generation Error'),
                        self.tr(
                            'The process {0} could not be started. '
                            'Ensure, that it is in the search path.'
                        ).format('hg'))
            else:
                self.__finish()
    
    def __getParents(self, rev):
        """
        Private method to get the parents of the currently viewed
        file/directory.
        
        @param rev revision number to get parents for (string)
        @return list of parent revisions (list of strings)
        """
        errMsg = ""
        parents = []
        
        if int(rev) > 0:
            args = self.vcs.initCommand("parents")
            if self.mode == "incoming":
                if self.bundle:
                    args.append("--repository")
                    args.append(self.bundle)
                elif self.vcs.bundleFile and \
                        os.path.exists(self.vcs.bundleFile):
                    args.append("--repository")
                    args.append(self.vcs.bundleFile)
            args.append("--template")
            args.append("{rev}:{node|short}\n")
            args.append("-r")
            args.append(rev)
            if not self.projectMode:
                args.append(self.filename)
            
            output = ""
            if self.__hgClient:
                output, errMsg = self.__hgClient.runcommand(args)
            else:
                process = QProcess()
                process.setWorkingDirectory(self.repodir)
                process.start('hg', args)
                procStarted = process.waitForStarted(5000)
                if procStarted:
                    finished = process.waitForFinished(30000)
                    if finished and process.exitCode() == 0:
                        output = str(process.readAllStandardOutput(),
                                     self.vcs.getEncoding(), 'replace')
                    else:
                        if not finished:
                            errMsg = self.tr(
                                "The hg process did not finish within 30s.")
                else:
                    errMsg = self.tr("Could not start the hg executable.")
            
            if errMsg:
                E5MessageBox.critical(
                    self,
                    self.tr("Mercurial Error"),
                    errMsg)
            
            if output:
                parents = [p for p in output.strip().splitlines()]
        
        return parents
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.contents.clear()
        
        if not self.logEntries:
            self.errors.append(self.tr("No log available for '{0}'")
                               .format(self.filename))
            self.errorGroup.show()
            return
        
        html = ""
        
        if self.initialText:
            for line in self.initialText:
                html += Utilities.html_encode(line.strip())
                html += '<br />\n'
            html += '{0}<br/>\n'.format(80 * "=")
            
        for entry in self.logEntries:
            fileCopies = {}
            if entry["file_copies"]:
                for fentry in entry["file_copies"].split(", "):
                    newName, oldName = fentry[:-1].split(" (")
                    fileCopies[newName] = oldName
            
            rev, hexRev = entry["change"].split(":")
            dstr = '<p><b>{0} {1}</b>'.format(self.revString, entry["change"])
            if entry["parents"]:
                parents = entry["parents"].split()
            else:
                parents = self.__getParents(rev)
            for parent in parents:
                url = QUrl()
                url.setScheme("file")
                url.setPath(self.filename)
                if qVersion() >= "5.0.0":
                    query = parent.split(":")[0] + '_' + rev
                    url.setQuery(query)
                else:
                    query = QByteArray()
                    query.append(parent.split(":")[0]).append('_').append(rev)
                    url.setEncodedQuery(query)
                dstr += ' [<a href="{0}" name="{1}" id="{1}">{2}</a>]'.format(
                    url.toString(), query,
                    self.tr('diff to {0}').format(parent),
                )
            dstr += '<br />\n'
            html += dstr
            
            if "phase" in entry:
                html += self.tr("Phase: {0}<br />\n")\
                    .format(entry["phase"])
            
            html += self.tr("Branch: {0}<br />\n")\
                .format(entry["branches"])
            
            html += self.tr("Tags: {0}<br />\n").format(entry["tags"])
            
            if "bookmarks" in entry:
                html += self.tr("Bookmarks: {0}<br />\n")\
                    .format(entry["bookmarks"])
            
            html += self.tr("Parents: {0}<br />\n")\
                .format(entry["parents"])
            
            html += self.tr('<i>Author: {0}</i><br />\n')\
                .format(Utilities.html_encode(entry["user"]))
            
            date, time = entry["date"].split()[:2]
            html += self.tr('<i>Date: {0}, {1}</i><br />\n')\
                .format(date, time)
            
            for line in entry["description"]:
                html += Utilities.html_encode(line.strip())
                html += '<br />\n'
            
            if entry["file_adds"]:
                html += '<br />\n'
                for f in entry["file_adds"].strip().split(", "):
                    if f in fileCopies:
                        html += self.tr(
                            'Added {0} (copied from {1})<br />\n')\
                            .format(Utilities.html_encode(f),
                                    Utilities.html_encode(fileCopies[f]))
                    else:
                        html += self.tr('Added {0}<br />\n')\
                            .format(Utilities.html_encode(f))
            
            if entry["files_mods"]:
                html += '<br />\n'
                for f in entry["files_mods"].strip().split(", "):
                    html += self.tr('Modified {0}<br />\n')\
                        .format(Utilities.html_encode(f))
            
            if entry["file_dels"]:
                html += '<br />\n'
                for f in entry["file_dels"].strip().split(", "):
                    html += self.tr('Deleted {0}<br />\n')\
                        .format(Utilities.html_encode(f))
            
            html += '</p>{0}<br/>\n'.format(60 * "=")
        
        self.contents.setHtml(html)
        tc = self.contents.textCursor()
        tc.movePosition(QTextCursor.Start)
        self.contents.setTextCursor(tc)
        self.contents.ensureCursorVisible()
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process and inserts it into a buffer.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(), self.vcs.getEncoding(), 'replace')
            self.__processOutputLine(s)
    
    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        if line == "@@@\n":
            self.logEntries.append(self.lastLogEntry)
            self.lastLogEntry = {}
            self.fileCopies = {}
        else:
            try:
                key, value = line.split("|", 1)
            except ValueError:
                key = ""
                value = line
            if key == "change":
                self.endInitialText = True
            if key in ("change", "tags", "parents", "user", "date",
                       "file_copies", "file_adds", "files_mods", "file_dels",
                       "bookmarks", "phase"):
                self.lastLogEntry[key] = value.strip()
            elif key == "branches":
                if value.strip():
                    self.lastLogEntry[key] = value.strip()
                else:
                    self.lastLogEntry[key] = "default"
            elif key == "description":
                self.lastLogEntry[key] = [value.strip()]
            else:
                if self.endInitialText:
                    self.lastLogEntry["description"].append(value.strip())
                else:
                    self.initialText.append(value)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    def __sourceChanged(self, url):
        """
        Private slot to handle the sourceChanged signal of the contents pane.
        
        @param url the url that was clicked (QUrl)
        """
        filename = url.path()
        if Utilities.isWindowsPlatform():
            if filename.startswith("/"):
                filename = filename[1:]
        if qVersion() >= "5.0.0":
            ver = url.query()
        else:
            ver = bytes(url.encodedQuery()).decode()
        v1, v2 = ver.split('_')
        if v1 == "" or v2 == "":
            return
        self.contents.scrollToAnchor(ver)
        
        if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked():
            self.vcs.hgSbsDiff(filename, revisions=(v1, v2))
        else:
            if self.diff is None:
                from .HgDiffDialog import HgDiffDialog
                self.diff = HgDiffDialog(self.vcs)
            self.diff.show()
            self.diff.start(filename, [v1, v2], self.bundle)
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the hg process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgLogDialog, self).keyPressEvent(evt)
コード例 #27
0
class PythonProcessPane(QTextEdit):
    """
    Handles / displays a Python process's stdin/out with working command
    history and simple buffer editing.
    """

    on_append_text = pyqtSignal(bytes)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFont(Font().load())
        self.setAcceptRichText(False)
        self.setReadOnly(False)
        self.setUndoRedoEnabled(False)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.context_menu)
        self.running = False  # Flag to show the child process is running.
        self.setObjectName('PythonRunner')
        self.process = None  # Will eventually reference the running process.
        self.input_history = []  # history of inputs entered in this session.
        self.start_of_current_line = 0  # start position of the input line.
        self.history_position = 0  # current position when navigation history.

    def start_process(self,
                      script_name,
                      working_directory,
                      interactive=True,
                      debugger=False,
                      command_args=None,
                      envars=None,
                      runner=None):
        """
        Start the child Python process.

        Will run the referenced Python script_name within the context of the
        working directory.

        If interactive is True (the default) the Python process will run in
        interactive mode (dropping the user into the REPL when the script
        completes).

        If debugger is True (the default is False) then the script will run
        within a debug runner session.

        If there is a list of command_args (the default is None), then these
        will be passed as further arguments into the script to be run.

        If there is a list of environment variables, these will be part of the
        context of the new child process.

        If runner is give, this is used as the command to start the Python
        process.
        """
        self.script = os.path.abspath(os.path.normcase(script_name))
        logger.info('Running script: {}'.format(self.script))
        if interactive:
            logger.info('Running with interactive mode.')
        if command_args is None:
            command_args = []
        logger.info('Command args: {}'.format(command_args))
        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        # Force buffers to flush immediately.
        env = QProcessEnvironment.systemEnvironment()
        env.insert('PYTHONUNBUFFERED', '1')
        if envars:
            logger.info('Running with environment variables: '
                        '{}'.format(envars))
            for name, value in envars:
                env.insert(name, value)
        logger.info('Working directory: {}'.format(working_directory))
        self.process.setWorkingDirectory(working_directory)
        self.process.setProcessEnvironment(env)
        self.process.readyRead.connect(self.read_from_stdout)
        self.process.finished.connect(self.finished)
        logger.info('Python path: {}'.format(sys.path))
        if debugger:
            # Start the mu-debug runner for the script.
            args = [
                self.script,
            ] + command_args
            self.process.start('mu-debug', args)
        else:
            if runner:
                # Use the passed in Python "runner" to run the script.
                python_exec = runner
            else:
                # Use the current system Python to run the script.
                python_exec = sys.executable
            if interactive:
                # Start the script in interactive Python mode.
                args = [
                    '-i',
                    self.script,
                ] + command_args
            else:
                # Just run the command with no additional flags.
                args = [
                    self.script,
                ] + command_args
            self.process.start(python_exec, args)
            self.running = True

    def finished(self, code, status):
        """
        Handle when the child process finishes.
        """
        self.running = False
        cursor = self.textCursor()
        cursor.movePosition(cursor.End)
        cursor.insertText('\n\n---------- FINISHED ----------\n')
        msg = 'exit code: {} status: {}'.format(code, status)
        cursor.insertText(msg)
        cursor.movePosition(QTextCursor.End)
        self.setTextCursor(cursor)
        self.setReadOnly(True)

    def context_menu(self):
        """
        Creates custom context menu with just copy and paste.
        """
        menu = QMenu(self)
        if platform.system() == 'Darwin':
            copy_keys = QKeySequence(Qt.CTRL + Qt.Key_C)
            paste_keys = QKeySequence(Qt.CTRL + Qt.Key_V)
        else:
            copy_keys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_C)
            paste_keys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_V)
        menu.addAction("Copy", self.copy, copy_keys)
        menu.addAction("Paste", self.paste, paste_keys)
        menu.exec_(QCursor.pos())

    def paste(self):
        """
        Grabs clipboard contents then writes to the REPL.
        """
        clipboard = QApplication.clipboard()
        if clipboard and clipboard.text():
            # normalize for Windows line-ends.
            text = '\n'.join(clipboard.text().splitlines())
            if text:
                self.parse_paste(text)

    def parse_paste(self, text):
        """
        Recursively takes characters from text to be parsed as input. We do
        this so the event loop has time to respond to output from the process
        to which the characters are sent (for example, when a newline is sent).

        Yes, this is a quick and dirty hack, but ensures the pasted input is
        also evaluated in an interactive manner rather than as a single-shot
        splurge of data. Essentially, it's simulating someone typing in the
        characters of the pasted text *really fast* but in such a way that the
        event loop cycles.
        """
        character = text[0]  # the current character to process.
        remainder = text[1:]  # remaining characters to process in the future.
        if character in string.printable:
            if character == '\n' or character == '\r':
                self.parse_input(Qt.Key_Enter, character, None)
            else:
                self.parse_input(None, character, None)
        if remainder:
            # Schedule a recursive call of parse_paste with the remaining text
            # to process. This allows the event loop to cycle and handle any
            # output from the child process as a result of the text pasted so
            # far (especially useful for handling responses from newlines).
            QTimer.singleShot(2, lambda text=remainder: self.parse_paste(text))

    def keyPressEvent(self, data):
        """
        Called when the user types something in the REPL.
        """
        key = data.key()
        text = data.text()
        modifiers = data.modifiers()
        self.parse_input(key, text, modifiers)

    def parse_input(self, key, text, modifiers):
        """
        Correctly encodes user input and sends it to the connected process.

        The key is a Qt.Key_Something value, text is the textual representation
        of the input, and modifiers are the control keys (shift, CTRL, META,
        etc) also used.
        """
        msg = b''  # Eventually to be inserted into the pane at the cursor.
        if key == Qt.Key_Enter or key == Qt.Key_Return:
            msg = b'\n'
        elif (platform.system() == 'Darwin' and
                modifiers == Qt.MetaModifier) or \
             (platform.system() != 'Darwin' and
                modifiers == Qt.ControlModifier):
            # Handle CTRL-C and CTRL-D
            if self.process and self.running:
                pid = self.process.processId()
                # NOTE: Windows related constraints don't allow us to send a
                # CTRL-C, rather, the process will just terminate.
                if key == Qt.Key_C:
                    os.kill(pid, signal.SIGINT)
                if key == Qt.Key_D:
                    self.process.kill()
            return
        elif key == Qt.Key_Up:
            self.history_back()
        elif key == Qt.Key_Down:
            self.history_forward()
        elif key == Qt.Key_Right:
            cursor = self.textCursor()
            cursor.movePosition(QTextCursor.Right)
            self.setTextCursor(cursor)
        elif key == Qt.Key_Left:
            cursor = self.textCursor()
            if cursor.position() > self.start_of_current_line:
                cursor.movePosition(QTextCursor.Left)
                self.setTextCursor(cursor)
        elif key == Qt.Key_Home:
            cursor = self.textCursor()
            cursor.movePosition(QTextCursor.End)
            buffer_len = len(self.toPlainText()) - self.start_of_current_line
            for i in range(buffer_len):
                cursor.movePosition(QTextCursor.Left)
            self.setTextCursor(cursor)
        elif key == Qt.Key_End:
            cursor = self.textCursor()
            cursor.movePosition(QTextCursor.End)
            self.setTextCursor(cursor)
        elif (modifiers == Qt.ControlModifier | Qt.ShiftModifier) or \
                (platform.system() == 'Darwin' and
                    modifiers == Qt.ControlModifier):
            # Command-key on Mac, Ctrl-Shift on Win/Lin
            if key == Qt.Key_C:
                self.copy()
            elif key == Qt.Key_V:
                self.paste()
        elif text in string.printable:
            # If the key is for a printable character then add it to the
            # active buffer and display it.
            msg = bytes(text, 'utf8')
        if key == Qt.Key_Backspace:
            self.backspace()
        if key == Qt.Key_Delete:
            self.delete()
        if not self.isReadOnly() and msg:
            self.insert(msg)
        if key == Qt.Key_Enter or key == Qt.Key_Return:
            content = self.toPlainText()
            line = content[self.start_of_current_line:].encode('utf-8')
            self.write_to_stdin(line)
            if line.strip():
                self.input_history.append(line.replace(b'\n', b''))
            self.history_position = 0
            self.start_of_current_line = self.textCursor().position()

    def history_back(self):
        """
        Replace the current input line with the next item BACK from the
        current history position.
        """
        if self.input_history:
            self.history_position -= 1
            history_pos = len(self.input_history) + self.history_position
            if history_pos < 0:
                self.history_position += 1
                history_pos = 0
            history_item = self.input_history[history_pos]
            self.replace_input_line(history_item)

    def history_forward(self):
        """
        Replace the current input line with the next item FORWARD from the
        current history position.
        """
        if self.input_history:
            self.history_position += 1
            history_pos = len(self.input_history) + self.history_position
            if history_pos >= len(self.input_history):
                # At the most recent command.
                self.history_position = 0
                self.clear_input_line()
                return
            history_item = self.input_history[history_pos]
            self.replace_input_line(history_item)

    def read_from_stdout(self):
        """
        Process incoming data from the process's stdout.
        """
        data = self.process.readAll().data()
        if data:
            self.append(data)
            self.on_append_text.emit(data)
            cursor = self.textCursor()
            self.start_of_current_line = cursor.position()

    def write_to_stdin(self, data):
        """
        Writes data from the Qt application to the child process's stdin.
        """
        if self.process:
            self.process.write(data)

    def append(self, msg):
        """
        Append text to the text area.
        """
        cursor = self.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(msg.decode('utf-8'))
        cursor.movePosition(QTextCursor.End)
        self.setTextCursor(cursor)

    def insert(self, msg):
        """
        Insert text to the text area at the current cursor position.
        """
        cursor = self.textCursor()
        if cursor.position() < self.start_of_current_line:
            cursor.movePosition(QTextCursor.End)
        cursor.insertText(msg.decode('utf-8'))
        self.setTextCursor(cursor)

    def backspace(self):
        """
        Removes a character from the current buffer -- to the left of cursor.
        """
        cursor = self.textCursor()
        if cursor.position() > self.start_of_current_line:
            cursor = self.textCursor()
            cursor.deletePreviousChar()
            self.setTextCursor(cursor)

    def delete(self):
        """
        Removes a character from the current buffer -- to the right of cursor.
        """
        cursor = self.textCursor()
        if cursor.position() >= self.start_of_current_line:
            cursor.deleteChar()
            self.setTextCursor(cursor)

    def clear_input_line(self):
        """
        Remove all the characters currently in the input buffer line.
        """
        cursor = self.textCursor()
        cursor.movePosition(QTextCursor.End)
        buffer_len = len(self.toPlainText()) - self.start_of_current_line
        for i in range(buffer_len):
            cursor.deletePreviousChar()
        self.setTextCursor(cursor)

    def replace_input_line(self, text):
        """
        Replace the current input line with the passed in text.
        """
        self.clear_input_line()
        self.append(text)

    def zoomIn(self, delta=2):
        """
        Zoom in (increase) the size of the font by delta amount difference in
        point size upto 34 points.
        """
        old_size = self.font().pointSize()
        new_size = old_size + delta
        if new_size <= 34:
            super().zoomIn(delta)

    def zoomOut(self, delta=2):
        """
        Zoom out (decrease) the size of the font by delta amount difference in
        point size down to 4 points.
        """
        old_size = self.font().pointSize()
        new_size = old_size - delta
        if new_size >= 4:
            super().zoomOut(delta)

    def set_theme(self, theme):
        """
        Sets the theme / look for the REPL pane.
        """
        if theme == 'day':
            self.setStyleSheet(DAY_STYLE)
        elif theme == 'night':
            self.setStyleSheet(NIGHT_STYLE)
        else:
            self.setStyleSheet(CONTRAST_STYLE)
コード例 #28
0
class JobRunner(QObject, MooseWidget):
    """
    Actually runs the process. It will read the output and
    translate any terminal color codes into html.
    It will also attempt to parse the output to check to
    see if we are at a new time step and emit the
    timestep_updated signal.
    Signals:
        started: Emitted when we start running.
        finished: Emitted when we are finished. Arguments are exit code and status message.
        outputAdded: Emitted when there is new output.
        timeStepUpdated: A new time step has started
        error: Emitted when an error is encountered. Arguments are QProcess code and error description
    """
    started = pyqtSignal()
    finished = pyqtSignal(int, str)
    outputAdded = pyqtSignal(str)
    timeStepUpdated = pyqtSignal(int)
    error = pyqtSignal(int, str)

    def __init__(self, **kwds):
        super(JobRunner, self).__init__(**kwds)

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.readyReadStandardOutput.connect(self._readOutput)
        self.process.finished.connect(self._jobFinished)
        self.process.started.connect(self.started)
        self.process.error.connect(self._error)
        self._error_map = { QProcess.FailedToStart: "Failed to start",
                QProcess.Crashed: "Crashed",
                QProcess.Timedout: "Timedout",
                QProcess.WriteError: "Write error",
                QProcess.ReadError: "Read error",
                QProcess.UnknownError: "Unknown error",
                }

        self.killed = False
        self.setup()

    def run(self, cmd, args):
        """
        Start the command.

        Arguments:
            cmd: The command to run
            args: A list of string arguments
        """
        self.killed = False
        self._sendMessage("Running command: %s %s" % (cmd, ' '.join(args)))
        self._sendMessage("Working directory: %s" % os.getcwd())
        self.process.start(cmd, args)
        self.process.waitForStarted()

    def _sendMessage(self, msg):
        mooseutils.mooseMessage(msg, color="MAGENTA")
        self.outputAdded.emit('<span style="color:magenta;">%s</span>' % msg)

    @pyqtSlot(QProcess.ProcessError)
    def _error(self, err):
        """
        Slot called when the QProcess encounters an error.
        Inputs:
            err: One of the QProcess.ProcessError enums
        """
        if not self.killed:
            msg = self._error_map.get(err, "Unknown error")
            self.error.emit(int(err), msg)
            mooseutils.mooseMessage(msg, color="RED")
            self.outputAdded.emit(msg)

    @pyqtSlot(int, QProcess.ExitStatus)
    def _jobFinished(self, code, status):
        """
        Slot called when the QProcess is finished.
        Inputs:
            code: Exit code of the process.
            status: QProcess.ExitStatus
        """
        exit_status = "Finished"
        if status != QProcess.NormalExit:
            if self.killed:
                exit_status = "Killed by user"
            else:
                exit_status = "Crashed"
        self.finished.emit(code, exit_status)
        self._sendMessage("%s: Exit code: %s" % (exit_status, code))

    def kill(self):
        """
        Kills the QProcess
        """
        self.killed = True
        mooseutils.mooseMessage("Killing")
        self.process.kill()
        self.process.waitForFinished()

    @pyqtSlot()
    def _readOutput(self):
        """
        Slot called when the QProcess produces output.
        """
        lines = []
        while self.process.canReadLine():
            tmp = self.process.readLine().data().decode("utf-8").rstrip()
            lines.append(TerminalUtils.terminalOutputToHtml(tmp))
            match = re.search(r'Time\sStep\s*([0-9]{1,})', tmp)
            if match:
                ts = int(match.group(1))
                self.timeStepUpdated.emit(ts)

        output = '<pre style="display: inline; margin: 0;">%s</pre>' % '\n'.join(lines)
        self.outputAdded.emit(output)

    def isRunning(self):
        return self.process.state() == QProcess.Running
コード例 #29
0
class HgDiffDialog(QWidget, Ui_HgDiffDialog):
    """
    Class implementing a dialog to show the output of the hg diff command
    process.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(HgDiffDialog, self).__init__(parent)
        self.setupUi(self)

        self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)

        self.process = QProcess()
        self.vcs = vcs
        self.__hgClient = self.vcs.getClient()

        font = Preferences.getEditorOtherFonts("MonospacedFont")
        self.contents.setFontFamily(font.family())
        self.contents.setFontPointSize(font.pointSize())

        self.cNormalFormat = self.contents.currentCharFormat()
        self.cAddedFormat = self.contents.currentCharFormat()
        self.cAddedFormat.setBackground(QBrush(QColor(190, 237, 190)))
        self.cRemovedFormat = self.contents.currentCharFormat()
        self.cRemovedFormat.setBackground(QBrush(QColor(237, 190, 190)))
        self.cLineNoFormat = self.contents.currentCharFormat()
        self.cLineNoFormat.setBackground(QBrush(QColor(255, 220, 168)))

        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)

    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)

        e.accept()

    def __getVersionArg(self, version):
        """
        Private method to get a hg revision argument for the given revision.
        
        @param version revision (integer or string)
        @return version argument (string)
        """
        if version == "WORKING":
            return None
        else:
            return str(version)

    def start(self, fn, versions=None, bundle=None, qdiff=False):
        """
        Public slot to start the hg diff command.
        
        @param fn filename to be diffed (string)
        @param versions list of versions to be diffed (list of up to 2 strings
            or None)
        @param bundle name of a bundle file (string)
        @param qdiff flag indicating qdiff command shall be used (boolean)
        """
        self.errorGroup.hide()
        self.inputGroup.show()
        self.intercept = False
        self.filename = fn

        self.contents.clear()
        self.paras = 0

        self.filesCombo.clear()

        if qdiff:
            args = self.vcs.initCommand("qdiff")
            self.setWindowTitle(self.tr("Patch Contents"))
        else:
            args = self.vcs.initCommand("diff")

            if self.vcs.hasSubrepositories():
                args.append("--subrepos")

            if bundle:
                args.append('--repository')
                args.append(bundle)
            elif self.vcs.bundleFile and os.path.exists(self.vcs.bundleFile):
                args.append('--repository')
                args.append(self.vcs.bundleFile)

            if versions is not None:
                self.raise_()
                self.activateWindow()

                rev1 = self.__getVersionArg(versions[0])
                rev2 = None
                if len(versions) == 2:
                    rev2 = self.__getVersionArg(versions[1])

                if rev1 is not None or rev2 is not None:
                    args.append('-r')
                    if rev1 is not None and rev2 is not None:
                        args.append('{0}:{1}'.format(rev1, rev2))
                    elif rev2 is None:
                        args.append(rev1)
                    elif rev1 is None:
                        args.append(':{0}'.format(rev2))

        if isinstance(fn, list):
            dname, fnames = self.vcs.splitPathList(fn)
            self.vcs.addArguments(args, fn)
        else:
            dname, fname = self.vcs.splitPath(fn)
            args.append(fn)

        self.__oldFile = ""
        self.__oldFileLine = -1
        self.__fileSeparators = []

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()

            out, err = self.__hgClient.runcommand(args)

            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines(True):
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break

            self.__finish()
        else:
            # find the root of the repo
            repodir = dname
            while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
                repodir = os.path.dirname(repodir)
                if os.path.splitdrive(repodir)[1] == os.sep:
                    return

            self.process.kill()

            self.process.setWorkingDirectory(repodir)

            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                QApplication.restoreOverrideCursor()
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self, self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.').format('hg'))

    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()

    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        QApplication.restoreOverrideCursor()
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()

        if self.paras == 0:
            self.contents.setCurrentCharFormat(self.cNormalFormat)
            self.contents.setPlainText(self.tr('There is no difference.'))

        self.buttonBox.button(QDialogButtonBox.Save).setEnabled(self.paras > 0)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)

        tc = self.contents.textCursor()
        tc.movePosition(QTextCursor.Start)
        self.contents.setTextCursor(tc)
        self.contents.ensureCursorVisible()

        self.filesCombo.addItem(self.tr("<Start>"), 0)
        self.filesCombo.addItem(self.tr("<End>"), -1)
        for oldFile, newFile, pos in sorted(self.__fileSeparators):
            if oldFile != newFile:
                self.filesCombo.addItem("{0}\n{1}".format(oldFile, newFile),
                                        pos)
            else:
                self.filesCombo.addItem(oldFile, pos)

    def __appendText(self, txt, format):
        """
        Private method to append text to the end of the contents pane.
        
        @param txt text to insert (string)
        @param format text format to be used (QTextCharFormat)
        """
        tc = self.contents.textCursor()
        tc.movePosition(QTextCursor.End)
        self.contents.setTextCursor(tc)
        self.contents.setCurrentCharFormat(format)
        self.contents.insertPlainText(txt)

    def __extractFileName(self, line):
        """
        Private method to extract the file name out of a file separator line.
        
        @param line line to be processed (string)
        @return extracted file name (string)
        """
        f = line.split(None, 1)[1]
        f = f.rsplit(None, 6)[0]
        f = f.split("/", 1)[1]
        return f

    def __processFileLine(self, line):
        """
        Private slot to process a line giving the old/new file.
        
        @param line line to be processed (string)
        """
        if line.startswith('---'):
            self.__oldFileLine = self.paras
            self.__oldFile = self.__extractFileName(line)
        else:
            self.__fileSeparators.append(
                (self.__oldFile, self.__extractFileName(line),
                 self.__oldFileLine))

    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        if line.startswith("--- ") or \
           line.startswith("+++ "):
            self.__processFileLine(line)

        if line.startswith('+'):
            format = self.cAddedFormat
        elif line.startswith('-'):
            format = self.cRemovedFormat
        elif line.startswith('@@'):
            format = self.cLineNoFormat
        else:
            format = self.cNormalFormat
        self.__appendText(line, format)
        self.paras += 1

    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)

        while self.process.canReadLine():
            line = str(self.process.readLine(), self.vcs.getEncoding(),
                       'replace')
            self.__processOutputLine(line)

    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)

    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()

    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Save):
            self.on_saveButton_clicked()

    @pyqtSlot(int)
    def on_filesCombo_activated(self, index):
        """
        Private slot to handle the selection of a file.
        
        @param index activated row (integer)
        """
        para = self.filesCombo.itemData(index)

        if para == 0:
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.Start)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
        elif para == -1:
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.End)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
        else:
            # step 1: move cursor to end
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.End)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()

            # step 2: move cursor to desired line
            tc = self.contents.textCursor()
            delta = tc.blockNumber() - para
            tc.movePosition(QTextCursor.PreviousBlock, QTextCursor.MoveAnchor,
                            delta)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()

    @pyqtSlot()
    def on_saveButton_clicked(self):
        """
        Private slot to handle the Save button press.
        
        It saves the diff shown in the dialog to a file in the local
        filesystem.
        """
        if isinstance(self.filename, list):
            if len(self.filename) > 1:
                fname = self.vcs.splitPathList(self.filename)[0]
            else:
                dname, fname = self.vcs.splitPath(self.filename[0])
                if fname != '.':
                    fname = "{0}.diff".format(self.filename[0])
                else:
                    fname = dname
        else:
            fname = self.vcs.splitPath(self.filename)[0]

        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            self, self.tr("Save Diff"), fname, self.tr("Patch Files (*.diff)"),
            None, E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))

        if not fname:
            return  # user aborted

        ext = QFileInfo(fname).suffix()
        if not ext:
            ex = selectedFilter.split("(*")[1].split(")")[0]
            if ex:
                fname += ex
        if QFileInfo(fname).exists():
            res = E5MessageBox.yesNo(
                self,
                self.tr("Save Diff"),
                self.tr("<p>The patch file <b>{0}</b> already exists."
                        " Overwrite it?</p>").format(fname),
                icon=E5MessageBox.Warning)
            if not res:
                return
        fname = Utilities.toNativeSeparators(fname)

        eol = e5App().getObject("Project").getEolString()
        try:
            f = open(fname, "w", encoding="utf-8", newline="")
            f.write(eol.join(self.contents.toPlainText().splitlines()))
            f.close()
        except IOError as why:
            E5MessageBox.critical(
                self, self.tr('Save Diff'),
                self.tr('<p>The patch file <b>{0}</b> could not be saved.'
                        '<br>Reason: {1}</p>').format(fname, str(why)))

    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)

    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep

        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()

        self.process.write(input)

        self.passwordCheckBox.setChecked(False)
        self.input.clear()

    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()

    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgDiffDialog, self).keyPressEvent(evt)
コード例 #30
0
class MainWindow(QMainWindow):

    """Voice Changer main window."""

    def __init__(self, parent=None):
        super(MainWindow, self).__init__()
        self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.")
        self.setWindowTitle(__doc__)
        self.setMinimumSize(240, 240)
        self.setMaximumSize(480, 480)
        self.resize(self.minimumSize())
        self.setWindowIcon(QIcon.fromTheme("audio-input-microphone"))
        self.tray = QSystemTrayIcon(self)
        self.center()
        QShortcut("Ctrl+q", self, activated=lambda: self.close())
        self.menuBar().addMenu("&File").addAction("Quit", lambda: exit())
        self.menuBar().addMenu("Sound").addAction(
            "STOP !", lambda: call('killall rec', shell=True))
        windowMenu = self.menuBar().addMenu("&Window")
        windowMenu.addAction("Hide", lambda: self.hide())
        windowMenu.addAction("Minimize", lambda: self.showMinimized())
        windowMenu.addAction("Maximize", lambda: self.showMaximized())
        windowMenu.addAction("Restore", lambda: self.showNormal())
        windowMenu.addAction("FullScreen", lambda: self.showFullScreen())
        windowMenu.addAction("Center", lambda: self.center())
        windowMenu.addAction("Top-Left", lambda: self.move(0, 0))
        windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position())
        # widgets
        group0 = QGroupBox("Voice Deformation")
        self.setCentralWidget(group0)
        self.process = QProcess(self)
        self.process.error.connect(
            lambda: self.statusBar().showMessage("Info: Process Killed", 5000))
        self.control = QDial()
        self.control.setRange(-10, 20)
        self.control.setSingleStep(5)
        self.control.setValue(0)
        self.control.setCursor(QCursor(Qt.OpenHandCursor))
        self.control.sliderPressed.connect(
            lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor)))
        self.control.sliderReleased.connect(
            lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor)))
        self.control.valueChanged.connect(
            lambda: self.control.setToolTip(f"<b>{self.control.value()}"))
        self.control.valueChanged.connect(
            lambda: self.statusBar().showMessage(
                f"Voice deformation: {self.control.value()}", 5000))
        self.control.valueChanged.connect(self.run)
        self.control.valueChanged.connect(lambda: self.process.kill())
        # Graphic effect
        self.glow = QGraphicsDropShadowEffect(self)
        self.glow.setOffset(0)
        self.glow.setBlurRadius(99)
        self.glow.setColor(QColor(99, 255, 255))
        self.control.setGraphicsEffect(self.glow)
        self.glow.setEnabled(False)
        # Timer to start
        self.slider_timer = QTimer(self)
        self.slider_timer.setSingleShot(True)
        self.slider_timer.timeout.connect(self.on_slider_timer_timeout)
        # an icon and set focus
        QLabel(self.control).setPixmap(
            QIcon.fromTheme("audio-input-microphone").pixmap(32))
        self.control.setFocus()
        QVBoxLayout(group0).addWidget(self.control)
        self.menu = QMenu(__doc__)
        self.menu.addAction(__doc__).setDisabled(True)
        self.menu.setIcon(self.windowIcon())
        self.menu.addSeparator()
        self.menu.addAction(
            "Show / Hide",
            lambda: self.hide() if self.isVisible() else self.showNormal())
        self.menu.addAction("STOP !", lambda: call('killall rec', shell=True))
        self.menu.addSeparator()
        self.menu.addAction("Quit", lambda: exit())
        self.tray.setContextMenu(self.menu)
        self.make_trayicon()

    def run(self):
        """Run/Stop the QTimer."""
        if self.slider_timer.isActive():
            self.slider_timer.stop()
        self.glow.setEnabled(True)
        call('killall rec ; killall play', shell=True)
        self.slider_timer.start(3000)

    def on_slider_timer_timeout(self):
        """Run subprocess to deform voice."""
        self.glow.setEnabled(False)
        value = int(self.control.value()) * 100
        command = f'play -q -V0 "|rec -q -V0 -n -d -R riaa bend pitch {value} "'
        print(f"Voice Deformation Value: {value}")
        print(f"Voice Deformation Command: {command}")
        self.process.start(command)
        if self.isVisible():
            self.statusBar().showMessage("Minimizing to System TrayIcon", 3000)
            print("Minimizing Main Window to System TrayIcon now...")
            sleep(3)
            self.hide()

    def center(self):
        """Center Window on the Current Screen,with Multi-Monitor support."""
        window_geometry = self.frameGeometry()
        mousepointer_position = QApplication.desktop().cursor().pos()
        screen = QApplication.desktop().screenNumber(mousepointer_position)
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        window_geometry.moveCenter(centerPoint)
        self.move(window_geometry.topLeft())

    def move_to_mouse_position(self):
        """Center the Window on the Current Mouse position."""
        window_geometry = self.frameGeometry()
        window_geometry.moveCenter(QApplication.desktop().cursor().pos())
        self.move(window_geometry.topLeft())

    def make_trayicon(self):
        """Make a Tray Icon."""
        if self.windowIcon() and __doc__:
            self.tray.setIcon(self.windowIcon())
            self.tray.setToolTip(__doc__)
            self.tray.activated.connect(
                lambda: self.hide() if self.isVisible()
                else self.showNormal())
            return self.tray.show()
コード例 #31
0
ファイル: VideoPlayer.py プロジェクト: mgoral/subconvert
class VideoPlayer(QObject):
    positionChanged = pyqtSignal(int)
    playbackChanged = pyqtSignal(bool)
    playerStopped = pyqtSignal()
    videoDataChanged = pyqtSignal(bool) # True if all data is available

    def __init__(self, window, parent = None):
        super().__init__(parent)
        self._executable = "mplayer"
        self._window = window
        self._filePath = None
        self._proc = QProcess(self)
        self._isPlaying = False

        self._data = VideoData()
        self._params = VideoParameters()

        # connect some signals
        self._proc.readyReadStandardOutput.connect(self._readStdout)
        self._proc.readyReadStandardError.connect(self._readStdErr)
        self._compileRegex()

    def _compileRegex(self):
        # bunch of used regex patterns. Note that sometimes it's not necessary to use regex to
        # parse MPlayer output
        self._commandLinePattern = re.compile(r"""
            V:\ *?(?P<time>\d+\.\d)     # fallback time (always available). Note spaces after V:
            .*                          # anything between time and frame number
            \d+/\s*?(?P<frame>\d+)\     # frame number (not always available). Note that space is
                                        # the last character
            """, re.X)
        self._idVideoBitratePattern = re.compile(r"^ID_VIDEO_BITRATE=(?P<bitrate>\d+)")
        self._idWidthPattern = re.compile(r"^ID_VIDEO_WIDTH=(?P<width>\d+)")
        self._idHeightPattern= re.compile(r"^ID_VIDEO_HEIGHT=(?P<height>\d+)")
        self._idFpsPattern= re.compile(r"^ID_VIDEO_FPS=(?P<fps>[0-9.]+)")
        self._idLengthPattern= re.compile(r"ID_LENGTH=(?P<length>[0-9.]+)")

    def __del__(self):
        log.debug(_("Closing VideoPlayer"))
        self._kill()

    @property
    def isPlaying(self):
        return self._isPlaying

    @property
    def videoData(self):
        """Fetches meta data for loaded file"""
        return self._data

    def loadFile(self, filePath):
        """Loads a file"""
        self._filePath = filePath

        if self._proc.state() != QProcess.Running:
            self._kill()
            self._run(self._filePath)
        else:
            self._execute("pausing_keep_force pt_step 1")
            self._execute("get_property pause")
            self._execute("loadfile \"%s\"" % self._filePath)

        self._data.reset()
        self.videoDataChanged.emit(False)
        self._changePlayingState(True)

    def play(self):
        """Starts a playback"""
        if self._proc.state() == QProcess.Running:
            if self.isPlaying is False:
                self._execute("pause")
                self._changePlayingState(True)
        elif self._filePath is not None:
            self._kill()
            self._run(self._filePath)
            self._changePlayingState(True)

    def jump(self, timestamp):
        """Jumps to a given position in a movie"""
        self._execute("pausing_keep seek %.1f 2" % timestamp)

    def pause(self):
        """Pauses playback"""
        if self.isPlaying is True:
            self._execute("pause")
            self._changePlayingState(False)

    def stop(self):
        """Stops playback"""
        if self.isPlaying is True:
            self._execute("stop")
            self._changePlayingState(False)

    def seek(self, val):
        """Seeks +/- given seconds.miliseconds"""
        self._execute("pausing_keep seek %.1f 0" % val)

    def frameStep(self):
        self._execute("frame_step")

    def mute(self):
        """Mutes autio"""
        self._execute("mute")

    def setVolume(self, volume):
        """Changes volume"""
        val = float(val)
        cmd = "volume %s" % val
        self._execute(cmd)

    @property
    def path(self):
        return self._filePath

    def _run(self, filepath):
        arguments = [
            "-slave",
            "-noautosub",
            "-identify",
            "-nomouseinput",
            "-osdlevel", "0",
            "-input", "nodefault-bindings",
            "-noconfig", "all",
            "-wid", str(int(self._window.winId())),
            filepath
        ]

        self._proc.start(self._executable, arguments)
        if self._proc.waitForStarted() is False:
            raise VideoPlayerException("Cannot start MPlayer!")

    def _kill(self):
        log.debug(_("Killing MPlayer process"))
        self._proc.kill()
        if self._proc.waitForFinished() is False:
            log.debug(_("Cannot kill MPlayer process (if you killed Subconvert this is not "
                "necessary an error)!"))

    def _execute(self, cmd):
        if self._proc.state() == QProcess.Running:
            if cmd is not None and cmd != "":
                procCmd = "%s%s" % (str(cmd), "\n")
                self._proc.write(procCmd.encode('utf-8'))

    def _changePlayingState(self, state):
        if state != self._isPlaying:
            self._isPlaying = state
            self.playbackChanged.emit(state)

    def _readStdout(self):
        message = bytes(self._proc.readAllStandardOutput()).decode()
        log.debug("stderr: %s", message)
        self._parseMessage(message)

    def _readStdErr(self):
        message = bytes(self._proc.readAllStandardError()).decode()
        log.debug("stdout: %s", message)
        self._parseMessage(message)

    def _parseMessage(self, message):
        lines = message.splitlines()
        for line in lines:
            if line.startswith("A:") or line.startswith("V:"):
                self._parsePositionChange(line)
            elif line.startswith("ID_"):
                self._parseMovieInfo(line)

    def _parsePositionChange(self, message):
        if self.videoData.isAllDataSet():
            pos = self._commandLinePattern.search(message)
            if pos is not None:
                frame = int(pos.group("frame"))
                time = float(pos.group("time"))
                if frame == 0:
                    frame = int(time * self.videoData.fps)
                self.positionChanged.emit(frame)

    def _parseMovieInfo(self, message):
        if message.startswith("ID_VIDEO_BITRATE"):
            bitrate = self._idVideoBitratePattern.search(message).group("bitrate")
            self._data.bitrate = int(bitrate)
            self._dataChanged()
        elif message.startswith("ID_VIDEO_WIDTH"):
            width = self._idWidthPattern.search(message).group("width")
            self._data.width = int(width)
            self._dataChanged()
        elif message.startswith("ID_VIDEO_HEIGHT"):
            height = self._idHeightPattern.search(message).group("height")
            self._data.height = int(height)
            self._dataChanged()
        elif message.startswith("ID_VIDEO_FPS"):
            fps = self._idFpsPattern.search(message).group("fps")
            self._data.fps = float(fps)
            self._dataChanged()
        elif message.startswith("ID_LENGTH"):
            length = self._idLengthPattern.search(message).group("length")
            self._data.length = float(length)
            self._dataChanged()
        elif message.startswith("ID_PAUSED"):
            self._changePlayingState(False)
        elif message.startswith("ID_EXIT"):
            self._changePlayingState(False)
            self.playerStopped.emit()

    def _dataChanged(self):
        if self._data.isAllDataSet():
            self.videoDataChanged.emit(True)
コード例 #32
0
class HgTagBranchListDialog(QDialog, Ui_HgTagBranchListDialog):
    """
    Class implementing a dialog to show a list of tags or branches.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(HgTagBranchListDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.Window)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.vcs = vcs
        self.tagsList = None
        self.allTagsList = None
        self.__hgClient = vcs.getClient()
        
        self.tagList.headerItem().setText(self.tagList.columnCount(), "")
        self.tagList.header().setSortIndicator(3, Qt.AscendingOrder)
        
        if self.__hgClient:
            self.process = None
        else:
            self.process = QProcess()
            self.process.finished.connect(self.__procFinished)
            self.process.readyReadStandardOutput.connect(self.__readStdout)
            self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.show()
        QCoreApplication.processEvents()
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        e.accept()
    
    def start(self, path, tags, tagsList, allTagsList):
        """
        Public slot to start the tags command.
        
        @param path name of directory to be listed (string)
        @param tags flag indicating a list of tags is requested
            (False = branches, True = tags)
        @param tagsList reference to string list receiving the tags
            (list of strings)
        @param allTagsList reference to string list all tags (list of strings)
        """
        self.errorGroup.hide()
        
        self.intercept = False
        self.tagsMode = tags
        if not tags:
            self.setWindowTitle(self.tr("Mercurial Branches List"))
            self.tagList.headerItem().setText(2, self.tr("Status"))
        self.activateWindow()
        
        self.tagsList = tagsList
        self.allTagsList = allTagsList
        dname, fname = self.vcs.splitPath(path)
        
        # find the root of the repo
        repodir = dname
        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
            repodir = os.path.dirname(repodir)
            if os.path.splitdrive(repodir)[1] == os.sep:
                return
        
        if self.tagsMode:
            args = self.vcs.initCommand("tags")
            args.append('--verbose')
        else:
            args = self.vcs.initCommand("branches")
            args.append('--closed')
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines():
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break
            self.__finish()
        else:
            self.process.kill()
            self.process.setWorkingDirectory(repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
            else:
                self.inputGroup.setEnabled(True)
                self.inputGroup.show()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        self.__resizeColumns()
        self.__resort()
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.tagList.sortItems(
            self.tagList.sortColumn(),
            self.tagList.header().sortIndicatorOrder())
    
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.tagList.header().resizeSections(QHeaderView.ResizeToContents)
        self.tagList.header().setStretchLastSection(True)
    
    def __generateItem(self, revision, changeset, status, name):
        """
        Private method to generate a tag item in the tag list.
        
        @param revision revision of the tag/branch (string)
        @param changeset changeset of the tag/branch (string)
        @param status of the tag/branch (string)
        @param name name of the tag/branch (string)
        """
        itm = QTreeWidgetItem(self.tagList)
        itm.setData(0, Qt.DisplayRole, int(revision))
        itm.setData(1, Qt.DisplayRole, changeset)
        itm.setData(2, Qt.DisplayRole, status)
        itm.setData(3, Qt.DisplayRole, name)
        itm.setTextAlignment(0, Qt.AlignRight)
        itm.setTextAlignment(1, Qt.AlignRight)
        itm.setTextAlignment(2, Qt.AlignHCenter)
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(), self.vcs.getEncoding(),
                    'replace').strip()
            self.__processOutputLine(s)
    
    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        li = line.split()
        if li[-1][0] in "1234567890":
            # last element is a rev:changeset
            if self.tagsMode:
                status = ""
            else:
                status = self.tr("active")
            rev, changeset = li[-1].split(":", 1)
            del li[-1]
        else:
            if self.tagsMode:
                status = self.tr("yes")
            else:
                status = li[-1][1:-1]
            rev, changeset = li[-2].split(":", 1)
            del li[-2:]
        name = " ".join(li)
        self.__generateItem(rev, changeset, status, name)
        if name not in ["tip", "default"]:
            if self.tagsList is not None:
                self.tagsList.append(name)
            if self.allTagsList is not None:
                self.allTagsList.append(name)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgTagBranchListDialog, self).keyPressEvent(evt)
コード例 #33
0
ファイル: HgDiffDialog.py プロジェクト: Darriall/eric
class HgDiffDialog(QWidget, Ui_HgDiffDialog):
    """
    Class implementing a dialog to show the output of the hg diff command
    process.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(HgDiffDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.process = QProcess()
        self.vcs = vcs
        self.__hgClient = self.vcs.getClient()
        
        font = Preferences.getEditorOtherFonts("MonospacedFont")
        self.contents.setFontFamily(font.family())
        self.contents.setFontPointSize(font.pointSize())
        
        self.cNormalFormat = self.contents.currentCharFormat()
        self.cAddedFormat = self.contents.currentCharFormat()
        self.cAddedFormat.setBackground(QBrush(QColor(190, 237, 190)))
        self.cRemovedFormat = self.contents.currentCharFormat()
        self.cRemovedFormat.setBackground(QBrush(QColor(237, 190, 190)))
        self.cLineNoFormat = self.contents.currentCharFormat()
        self.cLineNoFormat.setBackground(QBrush(QColor(255, 220, 168)))
        
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        e.accept()
    
    def __getVersionArg(self, version):
        """
        Private method to get a hg revision argument for the given revision.
        
        @param version revision (integer or string)
        @return version argument (string)
        """
        if version == "WORKING":
            return None
        else:
            return str(version)
    
    def start(self, fn, versions=None, bundle=None, qdiff=False):
        """
        Public slot to start the hg diff command.
        
        @param fn filename to be diffed (string)
        @param versions list of versions to be diffed (list of up to 2 strings
            or None)
        @param bundle name of a bundle file (string)
        @param qdiff flag indicating qdiff command shall be used (boolean)
        """
        self.errorGroup.hide()
        self.inputGroup.show()
        self.intercept = False
        self.filename = fn
        
        self.contents.clear()
        self.paras = 0
        
        self.filesCombo.clear()
        
        if qdiff:
            args = self.vcs.initCommand("qdiff")
            self.setWindowTitle(self.tr("Patch Contents"))
        else:
            args = self.vcs.initCommand("diff")
            
            if self.vcs.hasSubrepositories():
                args.append("--subrepos")
            
            if bundle:
                args.append('--repository')
                args.append(bundle)
            elif self.vcs.bundleFile and os.path.exists(self.vcs.bundleFile):
                args.append('--repository')
                args.append(self.vcs.bundleFile)
            
            if versions is not None:
                self.raise_()
                self.activateWindow()
                
                rev1 = self.__getVersionArg(versions[0])
                rev2 = None
                if len(versions) == 2:
                    rev2 = self.__getVersionArg(versions[1])
                
                if rev1 is not None or rev2 is not None:
                    args.append('-r')
                    if rev1 is not None and rev2 is not None:
                        args.append('{0}:{1}'.format(rev1, rev2))
                    elif rev2 is None:
                        args.append(rev1)
                    elif rev1 is None:
                        args.append(':{0}'.format(rev2))
        
        if isinstance(fn, list):
            dname, fnames = self.vcs.splitPathList(fn)
            self.vcs.addArguments(args, fn)
        else:
            dname, fname = self.vcs.splitPath(fn)
            args.append(fn)
        
        self.__oldFile = ""
        self.__oldFileLine = -1
        self.__fileSeparators = []
        
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines(True):
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break
            
            self.__finish()
        else:
            # find the root of the repo
            repodir = dname
            while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
                repodir = os.path.dirname(repodir)
                if os.path.splitdrive(repodir)[1] == os.sep:
                    return
            
            self.process.kill()
            
            self.process.setWorkingDirectory(repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                QApplication.restoreOverrideCursor()
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        QApplication.restoreOverrideCursor()
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        if self.paras == 0:
            self.contents.insertPlainText(
                self.tr('There is no difference.'))
            return
        
        self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        tc = self.contents.textCursor()
        tc.movePosition(QTextCursor.Start)
        self.contents.setTextCursor(tc)
        self.contents.ensureCursorVisible()
        
        self.filesCombo.addItem(self.tr("<Start>"), 0)
        self.filesCombo.addItem(self.tr("<End>"), -1)
        for oldFile, newFile, pos in sorted(self.__fileSeparators):
            if oldFile != newFile:
                self.filesCombo.addItem(
                    "{0}\n{1}".format(oldFile, newFile), pos)
            else:
                self.filesCombo.addItem(oldFile, pos)
    
    def __appendText(self, txt, format):
        """
        Private method to append text to the end of the contents pane.
        
        @param txt text to insert (string)
        @param format text format to be used (QTextCharFormat)
        """
        tc = self.contents.textCursor()
        tc.movePosition(QTextCursor.End)
        self.contents.setTextCursor(tc)
        self.contents.setCurrentCharFormat(format)
        self.contents.insertPlainText(txt)
    
    def __extractFileName(self, line):
        """
        Private method to extract the file name out of a file separator line.
        
        @param line line to be processed (string)
        @return extracted file name (string)
        """
        f = line.split(None, 1)[1]
        f = f.rsplit(None, 6)[0]
        f = f.split("/", 1)[1]
        return f
    
    def __processFileLine(self, line):
        """
        Private slot to process a line giving the old/new file.
        
        @param line line to be processed (string)
        """
        if line.startswith('---'):
            self.__oldFileLine = self.paras
            self.__oldFile = self.__extractFileName(line)
        else:
            self.__fileSeparators.append(
                (self.__oldFile, self.__extractFileName(line),
                 self.__oldFileLine))
    
    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        if line.startswith("--- ") or \
           line.startswith("+++ "):
            self.__processFileLine(line)
        
        if line.startswith('+'):
            format = self.cAddedFormat
        elif line.startswith('-'):
            format = self.cRemovedFormat
        elif line.startswith('@@'):
            format = self.cLineNoFormat
        else:
            format = self.cNormalFormat
        self.__appendText(line, format)
        self.paras += 1
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            line = str(self.process.readLine(), self.vcs.getEncoding(),
                       'replace')
            self.__processOutputLine(line)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Save):
            self.on_saveButton_clicked()
    
    @pyqtSlot(int)
    def on_filesCombo_activated(self, index):
        """
        Private slot to handle the selection of a file.
        
        @param index activated row (integer)
        """
        para = self.filesCombo.itemData(index)
        
        if para == 0:
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.Start)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
        elif para == -1:
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.End)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
        else:
            # step 1: move cursor to end
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.End)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
            
            # step 2: move cursor to desired line
            tc = self.contents.textCursor()
            delta = tc.blockNumber() - para
            tc.movePosition(QTextCursor.PreviousBlock, QTextCursor.MoveAnchor,
                            delta)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
    
    @pyqtSlot()
    def on_saveButton_clicked(self):
        """
        Private slot to handle the Save button press.
        
        It saves the diff shown in the dialog to a file in the local
        filesystem.
        """
        if isinstance(self.filename, list):
            if len(self.filename) > 1:
                fname = self.vcs.splitPathList(self.filename)[0]
            else:
                dname, fname = self.vcs.splitPath(self.filename[0])
                if fname != '.':
                    fname = "{0}.diff".format(self.filename[0])
                else:
                    fname = dname
        else:
            fname = self.vcs.splitPath(self.filename)[0]
        
        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            self,
            self.tr("Save Diff"),
            fname,
            self.tr("Patch Files (*.diff)"),
            None,
            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
        
        if not fname:
            return  # user aborted
        
        ext = QFileInfo(fname).suffix()
        if not ext:
            ex = selectedFilter.split("(*")[1].split(")")[0]
            if ex:
                fname += ex
        if QFileInfo(fname).exists():
            res = E5MessageBox.yesNo(
                self,
                self.tr("Save Diff"),
                self.tr("<p>The patch file <b>{0}</b> already exists."
                        " Overwrite it?</p>").format(fname),
                icon=E5MessageBox.Warning)
            if not res:
                return
        fname = Utilities.toNativeSeparators(fname)
        
        eol = e5App().getObject("Project").getEolString()
        try:
            f = open(fname, "w", encoding="utf-8", newline="")
            f.write(eol.join(self.contents.toPlainText().splitlines()))
            f.close()
        except IOError as why:
            E5MessageBox.critical(
                self, self.tr('Save Diff'),
                self.tr(
                    '<p>The patch file <b>{0}</b> could not be saved.'
                    '<br>Reason: {1}</p>')
                .format(fname, str(why)))
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgDiffDialog, self).keyPressEvent(evt)
コード例 #34
0
    def _performMonitor(self):
        """
        Protected method implementing the monitoring action.
        
        This method populates the statusList member variable
        with a list of strings giving the status in the first column and the
        path relative to the project directory starting with the third column.
        The allowed status flags are:
        <ul>
            <li>"A" path was added but not yet comitted</li>
            <li>"M" path has local changes</li>
            <li>"O" path was removed</li>
            <li>"R" path was deleted and then re-added</li>
            <li>"U" path needs an update</li>
            <li>"Z" path contains a conflict</li>
            <li>" " path is back at normal</li>
        </ul>
        
        @return tuple of flag indicating successful operation (boolean) and
            a status message in case of non successful operation (string)
        """
        self.shouldUpdate = False

        if self.__client is None and not self.__useCommandLine:
            if self.vcs.version >= (2, 9, 9):
                # versions below that have a bug causing a second
                # instance to not recognize changes to the status
                from .HgClient import HgClient
                client = HgClient(self.projectDir, "utf-8", self.vcs)
                ok, err = client.startServer()
                if ok:
                    self.__client = client
                else:
                    self.__useCommandLine = True
            else:
                self.__useCommandLine = True

        # step 1: get overall status
        args = self.vcs.initCommand("status")
        args.append('--noninteractive')
        args.append('--all')

        output = ""
        error = ""
        if self.__client:
            output, error = self.__client.runcommand(args)
        else:
            process = QProcess()
            process.setWorkingDirectory(self.projectDir)
            process.start('hg', args)
            procStarted = process.waitForStarted(5000)
            if procStarted:
                finished = process.waitForFinished(300000)
                if finished and process.exitCode() == 0:
                    output = str(process.readAllStandardOutput(),
                                 self.vcs.getEncoding(), 'replace')
                else:
                    process.kill()
                    process.waitForFinished()
                    error = str(process.readAllStandardError(),
                                self.vcs.getEncoding(), 'replace')
            else:
                process.kill()
                process.waitForFinished()
                error = self.tr("Could not start the Mercurial process.")

        if error:
            return False, error

        states = {}
        for line in output.splitlines():
            if not line.startswith("  "):
                flag, name = line.split(" ", 1)
                if flag in "AMR":
                    if flag == "R":
                        status = "O"
                    else:
                        status = flag
                    states[name] = status

        # step 2: get conflicting changes
        args = self.vcs.initCommand("resolve")
        args.append('--list')

        output = ""
        error = ""
        if self.__client:
            output, error = self.__client.runcommand(args)
        else:
            process.setWorkingDirectory(self.projectDir)
            process.start('hg', args)
            procStarted = process.waitForStarted(5000)
            if procStarted:
                finished = process.waitForFinished(300000)
                if finished and process.exitCode() == 0:
                    output = str(process.readAllStandardOutput(),
                                 self.vcs.getEncoding(), 'replace')

        for line in output.splitlines():
            flag, name = line.split(" ", 1)
            if flag == "U":
                states[name] = "Z"  # conflict

        # step 3: collect the status to be reported back
        for name in states:
            try:
                if self.reportedStates[name] != states[name]:
                    self.statusList.append("{0} {1}".format(
                        states[name], name))
            except KeyError:
                self.statusList.append("{0} {1}".format(states[name], name))
        for name in self.reportedStates.keys():
            if name not in states:
                self.statusList.append("  {0}".format(name))
        self.reportedStates = states

        return True, \
            self.tr("Mercurial status checked successfully")
コード例 #35
0
 def _performMonitor(self):
     """
     Protected method implementing the monitoring action.
     
     This method populates the statusList member variable
     with a list of strings giving the status in the first column and the
     path relative to the project directory starting with the third column.
     The allowed status flags are:
     <ul>
         <li>"A" path was added but not yet comitted</li>
         <li>"M" path has local changes</li>
         <li>"O" path was removed</li>
         <li>"R" path was deleted and then re-added</li>
         <li>"U" path needs an update</li>
         <li>"Z" path contains a conflict</li>
         <li>" " path is back at normal</li>
     </ul>
     
     @return tuple of flag indicating successful operation (boolean) and
         a status message in case of non successful operation (string)
     """
     self.shouldUpdate = False
     
     process = QProcess()
     args = []
     args.append('status')
     if not Preferences.getVCS("MonitorLocalStatus"):
         args.append('--show-updates')
     args.append('--non-interactive')
     args.append('.')
     process.setWorkingDirectory(self.projectDir)
     process.start('svn', args)
     procStarted = process.waitForStarted(5000)
     if procStarted:
         finished = process.waitForFinished(300000)
         if finished and process.exitCode() == 0:
             output = str(process.readAllStandardOutput(),
                          self.__ioEncoding, 'replace')
             states = {}
             for line in output.splitlines():
                 if self.rx_status1.exactMatch(line):
                     flags = self.rx_status1.cap(1)
                     path = self.rx_status1.cap(3).strip()
                 elif self.rx_status2.exactMatch(line):
                     flags = self.rx_status2.cap(1)
                     path = self.rx_status2.cap(5).strip()
                 else:
                     continue
                 if flags[0] in "ACDMR" or \
                    (flags[0] == " " and flags[-1] == "*"):
                     if flags[-1] == "*":
                         status = "U"
                     else:
                         status = flags[0]
                     if status == "C":
                         status = "Z"    # give it highest priority
                     elif status == "D":
                         status = "O"
                     if status == "U":
                         self.shouldUpdate = True
                     name = path
                     states[name] = status
                     try:
                         if self.reportedStates[name] != status:
                             self.statusList.append(
                                 "{0} {1}".format(status, name))
                     except KeyError:
                         self.statusList.append(
                             "{0} {1}".format(status, name))
             for name in list(self.reportedStates.keys()):
                 if name not in states:
                     self.statusList.append("  {0}".format(name))
             self.reportedStates = states
             return True, self.tr(
                 "Subversion status checked successfully (using svn)")
         else:
             process.kill()
             process.waitForFinished()
             return False, \
                 str(process.readAllStandardError(),
                     Preferences.getSystem("IOEncoding"),
                     'replace')
     else:
         process.kill()
         process.waitForFinished()
         return False, self.tr(
             "Could not start the Subversion process.")
コード例 #36
0
ファイル: JobRunner.py プロジェクト: aeslaughter/moose
class JobRunner(QObject, MooseWidget):
    """
    Actually runs the process. It will read the output and
    translate any terminal color codes into html.
    It will also attempt to parse the output to check to
    see if we are at a new time step and emit the
    timestep_updated signal.
    Signals:
        started: Emitted when we start running.
        finished: Emitted when we are finished. Arguments are exit code and status message.
        outputAdded: Emitted when there is new output.
        timeStepUpdated: A new time step has started
        error: Emitted when an error is encountered. Arguments are QProcess code and error description
    """
    started = pyqtSignal()
    finished = pyqtSignal(int, str)
    outputAdded = pyqtSignal(str)
    timeStepUpdated = pyqtSignal(int)
    error = pyqtSignal(int, str)

    def __init__(self, **kwds):
        super(JobRunner, self).__init__(**kwds)

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.readyReadStandardOutput.connect(self._readOutput)
        self.process.finished.connect(self._jobFinished)
        self.process.started.connect(self.started)
        self.process.error.connect(self._error)
        self._error_map = { QProcess.FailedToStart: "Failed to start",
                QProcess.Crashed: "Crashed",
                QProcess.Timedout: "Timedout",
                QProcess.WriteError: "Write error",
                QProcess.ReadError: "Read error",
                QProcess.UnknownError: "Unknown error",
                }

        self.killed = False
        self.setup()

    def run(self, cmd, args):
        """
        Start the command.

        Arguments:
            cmd: The command to run
            args: A list of string arguments
        """
        self.killed = False
        self._sendMessage("Running command: %s %s" % (cmd, ' '.join(args)))
        self._sendMessage("Working directory: %s" % os.getcwd())
        self.process.start(cmd, args)
        self.process.waitForStarted()

    def _sendMessage(self, msg):
        mooseutils.mooseMessage(msg, color="MAGENTA")
        self.outputAdded.emit('<span style="color:magenta;">%s</span>' % msg)

    @pyqtSlot(QProcess.ProcessError)
    def _error(self, err):
        """
        Slot called when the QProcess encounters an error.
        Inputs:
            err: One of the QProcess.ProcessError enums
        """
        if not self.killed:
            msg = self._error_map.get(err, "Unknown error")
            self.error.emit(int(err), msg)
            mooseutils.mooseMessage(msg, color="RED")
            self.outputAdded.emit(msg)

    @pyqtSlot(int, QProcess.ExitStatus)
    def _jobFinished(self, code, status):
        """
        Slot called when the QProcess is finished.
        Inputs:
            code: Exit code of the process.
            status: QProcess.ExitStatus
        """
        exit_status = "Finished"
        if status != QProcess.NormalExit:
            if self.killed:
                exit_status = "Killed by user"
            else:
                exit_status = "Crashed"
        self.finished.emit(code, exit_status)
        self._sendMessage("%s: Exit code: %s" % (exit_status, code))

    def kill(self):
        """
        Kills the QProcess
        """
        self.killed = True
        mooseutils.mooseMessage("Killing")
        self.process.terminate()
        self.process.waitForFinished(1000)
        if self.isRunning():
            mooseutils.mooseMessage("Failed to terminate job cleanly. Doing a hard kill.")
            self.process.kill()
            self.process.waitForFinished()

    @pyqtSlot()
    def _readOutput(self):
        """
        Slot called when the QProcess produces output.
        """
        lines = []
        while self.process.canReadLine():
            tmp = self.process.readLine().data().decode("utf-8").rstrip()
            lines.append(TerminalUtils.terminalOutputToHtml(tmp))
            match = re.search(r'Time\sStep\s*([0-9]{1,})', tmp)
            if match:
                ts = int(match.group(1))
                self.timeStepUpdated.emit(ts)

        output = '<pre style="display: inline; margin: 0;">%s</pre>' % '\n'.join(lines)
        self.outputAdded.emit(output)

    def isRunning(self):
        return self.process.state() == QProcess.Running
コード例 #37
0
class HgConflictsListDialog(QWidget, Ui_HgConflictsListDialog):
    """
    Class implementing a dialog to show a list of files which had or still
    have conflicts.
    """
    StatusRole = Qt.UserRole + 1
    FilenameRole = Qt.UserRole + 2
    
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(HgConflictsListDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.__position = QPoint()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.conflictsList.headerItem().setText(
            self.conflictsList.columnCount(), "")
        self.conflictsList.header().setSortIndicator(0, Qt.AscendingOrder)
        
        self.refreshButton = self.buttonBox.addButton(
            self.tr("&Refresh"), QDialogButtonBox.ActionRole)
        self.refreshButton.setToolTip(
            self.tr("Press to refresh the list of conflicts"))
        self.refreshButton.setEnabled(False)
        
        self.vcs = vcs
        self.project = e5App().getObject("Project")
        
        self.__hgClient = vcs.getClient()
        if self.__hgClient:
            self.process = None
        else:
            self.process = QProcess()
            self.process.finished.connect(self.__procFinished)
            self.process.readyReadStandardOutput.connect(self.__readStdout)
            self.process.readyReadStandardError.connect(self.__readStderr)
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        self.__position = self.pos()
        
        e.accept()
    
    def show(self):
        """
        Public slot to show the dialog.
        """
        if not self.__position.isNull():
            self.move(self.__position)
        
        super(HgConflictsListDialog, self).show()
    
    def start(self, path):
        """
        Public slot to start the tags command.
        
        @param path name of directory to list conflicts for (string)
        """
        self.errorGroup.hide()
        QApplication.processEvents()
            
        self.intercept = False
        dname, fname = self.vcs.splitPath(path)
        
        # find the root of the repo
        self.__repodir = dname
        while not os.path.isdir(
                os.path.join(self.__repodir, self.vcs.adminDir)):
            self.__repodir = os.path.dirname(self.__repodir)
            if os.path.splitdrive(self.__repodir)[1] == os.sep:
                return
        
        self.activateWindow()
        self.raise_()
        
        self.conflictsList.clear()
        self.__started = True
        self.__getEntries()
    
    def __getEntries(self):
        """
        Private method to get the conflict entries.
        """
        args = self.vcs.initCommand("resolve")
        args.append('--list')
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines():
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break
            self.__finish()
        else:
            self.process.kill()
            self.process.setWorkingDirectory(self.__repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
            else:
                self.inputGroup.setEnabled(True)
                self.inputGroup.show()

    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        QApplication.restoreOverrideCursor()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        self.refreshButton.setEnabled(True)
        
        self.__resizeColumns()
        self.__resort()
        self.on_conflictsList_itemSelectionChanged()
    
    @pyqtSlot(QAbstractButton)
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
        elif button == self.refreshButton:
            self.on_refreshButton_clicked()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.conflictsList.sortItems(
            self.conflictsList.sortColumn(),
            self.conflictsList.header().sortIndicatorOrder())
    
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.conflictsList.header().resizeSections(
            QHeaderView.ResizeToContents)
        self.conflictsList.header().setStretchLastSection(True)
    
    def __generateItem(self, status, name):
        """
        Private method to generate a tag item in the tag list.
        
        @param status status of the file (string)
        @param name name of the file (string)
        """
        itm = QTreeWidgetItem(self.conflictsList)
        if status == "U":
            itm.setText(0, self.tr("Unresolved"))
        elif status == "R":
            itm.setText(0, self.tr("Resolved"))
        else:
            itm.setText(0, self.tr("Unknown Status"))
        itm.setText(1, name)
        
        itm.setData(0, self.StatusRole, status)
        itm.setData(0, self.FilenameRole, self.project.getAbsolutePath(name))
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(), self.vcs.getEncoding(),
                    'replace').strip()
            self.__processOutputLine(s)
    
    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        status, filename = line.strip().split(None, 1)
        self.__generateItem(status, filename)
    
    @pyqtSlot()
    def on_refreshButton_clicked(self):
        """
        Private slot to refresh the log.
        """
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.inputGroup.setEnabled(True)
        self.inputGroup.show()
        self.refreshButton.setEnabled(False)
        self.start(self.__repodir)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgConflictsListDialog, self).keyPressEvent(evt)
    
    @pyqtSlot(QTreeWidgetItem, int)
    def on_conflictsList_itemDoubleClicked(self, item, column):
        """
        Private slot to open the double clicked entry.
        
        @param item reference to the double clicked item (QTreeWidgetItem)
        @param column column that was double clicked (integer)
        """
        self.on_editButton_clicked()
    
    @pyqtSlot()
    def on_conflictsList_itemSelectionChanged(self):
        """
        Private slot to handle a change of selected conflict entries.
        """
        selectedCount = len(self.conflictsList.selectedItems())
        unresolved = resolved = 0
        for itm in self.conflictsList.selectedItems():
            status = itm.data(0, self.StatusRole)
            if status == "U":
                unresolved += 1
            elif status == "R":
                resolved += 1
        
        self.resolvedButton.setEnabled(unresolved > 0)
        self.unresolvedButton.setEnabled(resolved > 0)
        self.reMergeButton.setEnabled(unresolved > 0)
        self.editButton.setEnabled(
            selectedCount == 1 and
            Utilities.MimeTypes.isTextFile(
                self.conflictsList.selectedItems()[0].data(
                    0, self.FilenameRole)))
    
    @pyqtSlot()
    def on_resolvedButton_clicked(self):
        """
        Private slot to mark the selected entries as resolved.
        """
        names = [
            itm.data(0, self.FilenameRole)
            for itm in self.conflictsList.selectedItems()
            if itm.data(0, self.StatusRole) == "U"
        ]
        if names:
            self.vcs.hgResolved(names)
            self.on_refreshButton_clicked()
    
    @pyqtSlot()
    def on_unresolvedButton_clicked(self):
        """
        Private slot to mark the selected entries as unresolved.
        """
        names = [
            itm.data(0, self.FilenameRole)
            for itm in self.conflictsList.selectedItems()
            if itm.data(0, self.StatusRole) == "R"
        ]
        if names:
            self.vcs.hgResolved(names, unresolve=True)
            self.on_refreshButton_clicked()
    
    @pyqtSlot()
    def on_reMergeButton_clicked(self):
        """
        Private slot to re-merge the selected entries.
        """
        names = [
            itm.data(0, self.FilenameRole)
            for itm in self.conflictsList.selectedItems()
            if itm.data(0, self.StatusRole) == "U"
        ]
        if names:
            self.vcs.hgReMerge(names)
    
    @pyqtSlot()
    def on_editButton_clicked(self):
        """
        Private slot to open the selected file in an editor.
        """
        itm = self.conflictsList.selectedItems()[0]
        filename = itm.data(0, self.FilenameRole)
        if Utilities.MimeTypes.isTextFile(filename):
            e5App().getObject("ViewManager").getEditor(filename)
コード例 #38
0
ファイル: datasource.py プロジェクト: luelista/pre_workbench
class LivePcapCaptureDataSource(DataSource):
    @staticmethod
    def getConfigFields():
        return [("shell_cmd", "Shell command line", "text", {
            "default": "sudo tcpdump -w -"
        })]

    def startFetch(self):
        self.plist = ByteBufferList()
        self.packetFI = None
        self.ctx = structinfo.ParseContext()
        self.process = QProcess()
        self.process.finished.connect(self.onProcessFinished)
        self.process.readyReadStandardError.connect(self.onReadyReadStderr)
        self.process.readyReadStandardOutput.connect(self.onReadyReadStdout)

        self.process.start("/bin/sh", ["-c", self.params["shell_cmd"]])

        return self.plist

    def tryParseHeader(self):
        for headerFI, packetFI in PcapVariants:
            try:
                header = headerFI.read_from_buffer(self.ctx)
                self.packetFI = packetFI
                self.plist.metadata.update(header)
                return
            except structinfo.invalid as ex:
                self.on_log.emit(str(ex))
                pass
        raise structinfo.invalid(self.ctx, "no PcapVariant matched")

    def onReadyReadStderr(self):
        self.on_log.emit("STD-ERR:" +
                         self.process.readAllStandardError().data().decode(
                             "utf-8", "replace"))

    def onReadyReadStdout(self):
        self.ctx.feed_bytes(self.process.readAllStandardOutput())
        try:
            if self.packetFI == None:
                self.tryParseHeader()
            while True:
                packet = self.packetFI.read_from_buffer(self.ctx)
                self.plist.add(
                    ByteBuffer(packet['payload'], metadata=packet['header']))
        except structinfo.incomplete:
            return
        except structinfo.invalid as ex:
            self.on_log.emit("Invalid packet format - killing pcap")
            self.on_log.emit(str(ex))
            self.cancelFetch()

    def onProcessFinished(self, exitCode, exitStatus):
        self.on_finished.emit()

    def cancelFetch(self):
        self.process.terminate()
        self.process.waitForFinished(500)
        self.process.kill()
        pass
コード例 #39
0
ファイル: HgQueuesListDialog.py プロジェクト: pycom/EricShort
class HgQueuesListDialog(QDialog, Ui_HgQueuesListDialog):
    """
    Class implementing a dialog to show a list of applied and unapplied
    patches.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(HgQueuesListDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.Window)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.vcs = vcs
        self.__hgClient = vcs.getClient()
        
        self.patchesList.header().setSortIndicator(0, Qt.AscendingOrder)
        
        if self.__hgClient:
            self.process = None
        else:
            self.process = QProcess()
            self.process.finished.connect(self.__procFinished)
            self.process.readyReadStandardOutput.connect(self.__readStdout)
            self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.__statusDict = {
            "A": self.tr("applied"),
            "U": self.tr("not applied"),
            "G": self.tr("guarded"),
            "D": self.tr("missing"),
        }
        
        self.show()
        QCoreApplication.processEvents()
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        e.accept()
    
    def start(self, path):
        """
        Public slot to start the list command.
        
        @param path name of directory to be listed (string)
        """
        self.errorGroup.hide()
        
        self.intercept = False
        self.activateWindow()
        
        dname, fname = self.vcs.splitPath(path)
        
        # find the root of the repo
        repodir = dname
        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
            repodir = os.path.dirname(repodir)
            if os.path.splitdrive(repodir)[1] == os.sep:
                return
        
        self.__repodir = repodir
        self.__getSeries()
    
    def __getSeries(self, missing=False):
        """
        Private slot to get the list of applied, unapplied and guarded patches
        and patches missing in the series file.
        
        @param missing flag indicating to get the patches missing in the
            series file (boolean)
        """
        if missing:
            self.__mode = "missing"
        else:
            self.__mode = "qseries"
        
        args = self.vcs.initCommand("qseries")
        args.append('--summary')
        args.append('--verbose')
        if missing:
            args.append('--missing')
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines():
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        self.__mode = ""
                        break
            if self.__mode == "qseries":
                self.__getSeries(True)
            elif self.__mode == "missing":
                self.__getTop()
            else:
                self.__finish()
        else:
            self.process.kill()
            self.process.setWorkingDirectory(self.__repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
            else:
                self.inputGroup.setEnabled(True)
                self.inputGroup.show()
    
    def __getTop(self):
        """
        Private slot to get patch at the top of the stack.
        """
        self.__mode = "qtop"
        
        args = self.vcs.initCommand("qtop")
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines():
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break
            self.__finish()
        else:
            self.process.kill()
            self.process.setWorkingDirectory(self.__repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
            else:
                self.inputGroup.setEnabled(True)
                self.inputGroup.show()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        if self.patchesList.topLevelItemCount() == 0:
            # no patches present
            self.__generateItem(
                0, "", self.tr("no patches found"), "", True)
        self.__resizeColumns()
        self.__resort()
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__mode = ""
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        if self.__mode == "qseries":
            self.__getSeries(True)
        elif self.__mode == "missing":
            self.__getTop()
        else:
            self.__finish()
    
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.patchesList.sortItems(
            self.patchesList.sortColumn(),
            self.patchesList.header().sortIndicatorOrder())
    
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.patchesList.header().resizeSections(QHeaderView.ResizeToContents)
        self.patchesList.header().setStretchLastSection(True)
    
    def __generateItem(self, index, status, name, summary, error=False):
        """
        Private method to generate a patch item in the list of patches.
        
        @param index index of the patch (integer, -1 for missing)
        @param status status of the patch (string)
        @param name name of the patch (string)
        @param summary first line of the patch header (string)
        @param error flag indicating an error entry (boolean)
        """
        if error:
            itm = QTreeWidgetItem(self.patchesList, [
                "",
                name,
                "",
                summary
            ])
        else:
            if index == -1:
                index = ""
            try:
                statusStr = self.__statusDict[status]
            except KeyError:
                statusStr = self.tr("unknown")
            itm = QTreeWidgetItem(self.patchesList)
            itm.setData(0, Qt.DisplayRole, index)
            itm.setData(1, Qt.DisplayRole, name)
            itm.setData(2, Qt.DisplayRole, statusStr)
            itm.setData(3, Qt.DisplayRole, summary)
            if status == "A":
                # applied
                for column in range(itm.columnCount()):
                    itm.setForeground(column, Qt.blue)
            elif status == "D":
                # missing
                for column in range(itm.columnCount()):
                    itm.setForeground(column, Qt.red)
        
        itm.setTextAlignment(0, Qt.AlignRight)
        itm.setTextAlignment(2, Qt.AlignHCenter)
    
    def __markTopItem(self, name):
        """
        Private slot to mark the top patch entry.
        
        @param name name of the patch (string)
        """
        items = self.patchesList.findItems(name, Qt.MatchCaseSensitive, 1)
        if items:
            itm = items[0]
            for column in range(itm.columnCount()):
                font = itm.font(column)
                font.setBold(True)
                itm.setFont(column, font)
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(), self.vcs.getEncoding(),
                    'replace').strip()
            self.__processOutputLine(s)
    
    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        if self.__mode == "qtop":
            self.__markTopItem(line)
        else:
            li = line.split(": ", 1)
            if len(li) == 1:
                data, summary = li[0][:-1], ""
            else:
                data, summary = li[0], li[1]
            li = data.split(None, 2)
            if len(li) == 2:
                # missing entry
                index, status, name = -1, li[0], li[1]
            elif len(li) == 3:
                index, status, name = li[:3]
            else:
                return
            self.__generateItem(index, status, name, summary)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgQueuesListDialog, self).keyPressEvent(evt)
コード例 #40
0
class Process(QObject):
    """Abstraction over a running test subprocess process.

    Reads the log from its stdout and parses it.

    Attributes:
        _invalid: A list of lines which could not be parsed.
        _data: A list of parsed lines.
        _started: Whether the process was ever started.
        proc: The QProcess for the underlying process.
        exit_expected: Whether the process is expected to quit.
        request: The request object for the current test.

    Signals:
        ready: Emitted when the server finished starting up.
        new_data: Emitted when a new line was parsed.
    """

    ready = pyqtSignal()
    new_data = pyqtSignal(object)
    KEYS = ['data']

    def __init__(self, request, parent=None):
        super().__init__(parent)
        self.request = request
        self.captured_log = []
        self._started = False
        self._invalid = []
        self._data = []
        self.proc = QProcess()
        self.proc.setReadChannel(QProcess.StandardError)
        self.exit_expected = None  # Not started at all yet

    def _log(self, line):
        """Add the given line to the captured log output."""
        if self.request.config.getoption('--capture') == 'no':
            print(line)
        self.captured_log.append(line)

    def log_summary(self, text):
        """Log the given line as summary/title."""
        text = '\n{line} {text} {line}\n'.format(line='=' * 30, text=text)
        self._log(text)

    def _parse_line(self, line):
        """Parse the given line from the log.

        Return:
            A self.ParseResult member.
        """
        raise NotImplementedError

    def _executable_args(self):
        """Get the executable and necessary arguments as a tuple."""
        raise NotImplementedError

    def _default_args(self):
        """Get the default arguments to use if none were passed to start()."""
        raise NotImplementedError

    def _get_data(self):
        """Get the parsed data for this test.

        Also waits for 0.5s to make sure any new data is received.

        Subprocesses are expected to alias this to a public method with a
        better name.
        """
        self.proc.waitForReadyRead(500)
        self.read_log()
        return self._data

    def _wait_signal(self, signal, timeout=5000, raising=True):
        """Wait for a signal to be emitted.

        Should be used in a contextmanager.
        """
        blocker = pytestqt.plugin.SignalBlocker(timeout=timeout,
                                                raising=raising)
        blocker.connect(signal)
        return blocker

    @pyqtSlot()
    def read_log(self):
        """Read the log from the process' stdout."""
        if not hasattr(self, 'proc'):
            # I have no idea how this happens, but it does...
            return
        while self.proc.canReadLine():
            line = self.proc.readLine()
            line = bytes(line).decode('utf-8', errors='ignore').rstrip('\r\n')

            try:
                parsed = self._parse_line(line)
            except InvalidLine:
                self._invalid.append(line)
                self._log("INVALID: {}".format(line))
                continue

            if parsed is None:
                if self._invalid:
                    self._log("IGNORED: {}".format(line))
            else:
                self._data.append(parsed)
                self.new_data.emit(parsed)

    def start(self, args=None, *, env=None):
        """Start the process and wait until it started."""
        self._start(args, env=env)
        self._started = True
        verbose = self.request.config.getoption('--verbose')

        timeout = 60 if 'CI' in os.environ else 20
        for _ in range(timeout):
            with self._wait_signal(self.ready, timeout=1000,
                                   raising=False) as blocker:
                pass

            if not self.is_running():
                if self.exit_expected:
                    return
                # _start ensures it actually started, but it might quit shortly
                # afterwards
                raise ProcessExited(
                    '\n' + _render_log(self.captured_log, verbose=verbose))

            if blocker.signal_triggered:
                self._after_start()
                return

        raise WaitForTimeout("Timed out while waiting for process start.\n" +
                             _render_log(self.captured_log, verbose=verbose))

    def _start(self, args, env):
        """Actually start the process."""
        executable, exec_args = self._executable_args()
        if args is None:
            args = self._default_args()

        procenv = QProcessEnvironment.systemEnvironment()
        if env is not None:
            for k, v in env.items():
                procenv.insert(k, v)

        self.proc.readyRead.connect(self.read_log)
        self.proc.setProcessEnvironment(procenv)
        self.proc.start(executable, exec_args + args)
        ok = self.proc.waitForStarted()
        assert ok
        assert self.is_running()

    def _after_start(self):
        """Do things which should be done immediately after starting."""

    def before_test(self):
        """Restart process before a test if it exited before."""
        self._invalid = []
        if not self.is_running():
            self.start()

    def after_test(self):
        """Clean up data after each test.

        Also checks self._invalid so the test counts as failed if there were
        unexpected output lines earlier.
        """
        __tracebackhide__ = lambda e: e.errisinstance(ProcessExited)
        self.captured_log = []
        if self._invalid:
            # Wait for a bit so the full error has a chance to arrive
            time.sleep(1)
            # Exit the process to make sure we're in a defined state again
            self.terminate()
            self.clear_data()
            raise InvalidLine('\n' + '\n'.join(self._invalid))

        self.clear_data()
        if not self.is_running() and not self.exit_expected and self._started:
            raise ProcessExited
        self.exit_expected = False

    def clear_data(self):
        """Clear the collected data."""
        self._data.clear()

    def terminate(self):
        """Clean up and shut down the process."""
        if not self.is_running():
            return

        if quteutils.is_windows:
            self.proc.kill()
        else:
            self.proc.terminate()

        ok = self.proc.waitForFinished()
        if not ok:
            self.proc.kill()
            self.proc.waitForFinished()

    def is_running(self):
        """Check if the process is currently running."""
        return self.proc.state() == QProcess.Running

    def _match_data(self, value, expected):
        """Helper for wait_for to match a given value.

        The behavior of this method is slightly different depending on the
        types of the filtered values:

        - If expected is None, the filter always matches.
        - If the value is a string or bytes object and the expected value is
          too, the pattern is treated as a glob pattern (with only * active).
        - If the value is a string or bytes object and the expected value is a
          compiled regex, it is used for matching.
        - If the value is any other type, == is used.

        Return:
            A bool
        """
        regex_type = type(re.compile(''))
        if expected is None:
            return True
        elif isinstance(expected, regex_type):
            return expected.search(value)
        elif isinstance(value, (bytes, str)):
            return utils.pattern_match(pattern=expected, value=value)
        else:
            return value == expected

    def _wait_for_existing(self, override_waited_for, after, **kwargs):
        """Check if there are any line in the history for wait_for.

        Return: either the found line or None.
        """
        for line in self._data:
            matches = []

            for key, expected in kwargs.items():
                value = getattr(line, key)
                matches.append(self._match_data(value, expected))

            if after is None:
                too_early = False
            else:
                too_early = ((line.timestamp, line.msecs) <
                             (after.timestamp, after.msecs))

            if (all(matches) and (not line.waited_for or override_waited_for)
                    and not too_early):
                # If we waited for this line, chances are we don't mean the
                # same thing the next time we use wait_for and it matches
                # this line again.
                line.waited_for = True
                self._log("\n----> Already found {!r} in the log: {}".format(
                    kwargs.get('message', 'line'), line))
                return line
        return None

    def _wait_for_new(self, timeout, do_skip, **kwargs):
        """Wait for a log message which doesn't exist yet.

        Called via wait_for.
        """
        __tracebackhide__ = lambda e: e.errisinstance(WaitForTimeout)
        message = kwargs.get('message', None)
        if message is not None:
            elided = quteutils.elide(repr(message), 100)
            self._log("\n----> Waiting for {} in the log".format(elided))

        spy = QSignalSpy(self.new_data)
        elapsed_timer = QElapsedTimer()
        elapsed_timer.start()

        while True:
            # Skip if there are pending messages causing a skip
            self._maybe_skip()
            got_signal = spy.wait(timeout)
            if not got_signal or elapsed_timer.hasExpired(timeout):
                msg = "Timed out after {}ms waiting for {!r}.".format(
                    timeout, kwargs)
                if do_skip:
                    pytest.skip(msg)
                else:
                    raise WaitForTimeout(msg)

            match = self._wait_for_match(spy, kwargs)
            if match is not None:
                if message is not None:
                    self._log("----> found it")
                return match

        raise quteutils.Unreachable

    def _wait_for_match(self, spy, kwargs):
        """Try matching the kwargs with the given QSignalSpy."""
        for args in spy:
            assert len(args) == 1
            line = args[0]

            matches = []

            for key, expected in kwargs.items():
                value = getattr(line, key)
                matches.append(self._match_data(value, expected))

            if all(matches):
                # If we waited for this line, chances are we don't mean the
                # same thing the next time we use wait_for and it matches
                # this line again.
                line.waited_for = True
                return line
        return None

    def _maybe_skip(self):
        """Can be overridden by subclasses to skip on certain log lines.

        We can't run pytest.skip directly while parsing the log, as that would
        lead to a pytest.skip.Exception error in a virtual Qt method, which
        means pytest-qt fails the test.

        Instead, we check for skip messages periodically in
        QuteProc._maybe_skip, and call _maybe_skip after every parsed message
        in wait_for (where it's most likely that new messages arrive).
        """

    def wait_for(self,
                 timeout=None,
                 *,
                 override_waited_for=False,
                 do_skip=False,
                 divisor=1,
                 after=None,
                 **kwargs):
        """Wait until a given value is found in the data.

        Keyword arguments to this function get interpreted as attributes of the
        searched data. Every given argument is treated as a pattern which
        the attribute has to match against.

        Args:
            timeout: How long to wait for the message.
            override_waited_for: If set, gets triggered by previous messages
                                 again.
            do_skip: If set, call pytest.skip on a timeout.
            divisor: A factor to decrease the timeout by.
            after: If it's an existing line, ensure it's after the given one.

        Return:
            The matched line.
        """
        __tracebackhide__ = lambda e: e.errisinstance(WaitForTimeout)

        if timeout is None:
            if do_skip:
                timeout = 2000
            elif 'CI' in os.environ:
                timeout = 15000
            else:
                timeout = 5000

        timeout //= divisor

        if not kwargs:
            raise TypeError("No keyword arguments given!")
        for key in kwargs:
            assert key in self.KEYS

        existing = self._wait_for_existing(override_waited_for, after,
                                           **kwargs)
        if existing is not None:
            return existing
        else:
            return self._wait_for_new(timeout=timeout,
                                      do_skip=do_skip,
                                      **kwargs)

    def ensure_not_logged(self, delay=500, **kwargs):
        """Make sure the data matching the given arguments is not logged.

        If nothing is found in the log, we wait for delay ms to make sure
        nothing arrives.
        """
        __tracebackhide__ = lambda e: e.errisinstance(BlacklistedMessageError)
        try:
            line = self.wait_for(timeout=delay,
                                 override_waited_for=True,
                                 **kwargs)
        except WaitForTimeout:
            return
        else:
            raise BlacklistedMessageError(line)

    def wait_for_quit(self):
        """Wait until the process has quit."""
        self.exit_expected = True
        with self._wait_signal(self.proc.finished, timeout=15000):
            pass
コード例 #41
0
ファイル: HgAnnotateDialog.py プロジェクト: pycom/EricShort
class HgAnnotateDialog(QDialog, Ui_HgAnnotateDialog):
    """
    Class implementing a dialog to show the output of the hg annotate command.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(HgAnnotateDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.Window)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.vcs = vcs
        self.__hgClient = vcs.getClient()
        
        self.__annotateRe = re.compile(
            r"""(.+)\s+(\d+)\s+([0-9a-fA-F]+)\s+([0-9-]+)\s+(.+)""")
        
        self.annotateList.headerItem().setText(
            self.annotateList.columnCount(), "")
        font = Preferences.getEditorOtherFonts("MonospacedFont")
        self.annotateList.setFont(font)
        
        if self.__hgClient:
            self.process = None
        else:
            self.process = QProcess()
            self.process.finished.connect(self.__procFinished)
            self.process.readyReadStandardOutput.connect(self.__readStdout)
            self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.show()
        QCoreApplication.processEvents()
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        e.accept()
    
    def start(self, fn):
        """
        Public slot to start the annotate command.
        
        @param fn filename to show the annotation for (string)
        """
        self.annotateList.clear()
        self.errorGroup.hide()
        self.intercept = False
        self.activateWindow()
        self.lineno = 1
        
        dname, fname = self.vcs.splitPath(fn)
        
        # find the root of the repo
        repodir = dname
        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
            repodir = os.path.dirname(repodir)
            if os.path.splitdrive(repodir)[1] == os.sep:
                return
        
        args = self.vcs.initCommand("annotate")
        args.append('--follow')
        args.append('--user')
        args.append('--date')
        args.append('--number')
        args.append('--changeset')
        args.append('--quiet')
        args.append(fn)
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines():
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break
            self.__finish()
        else:
            self.process.kill()
            self.process.setWorkingDirectory(repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
            else:
                self.inputGroup.setEnabled(True)
                self.inputGroup.show()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        self.__resizeColumns()
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.annotateList.header().resizeSections(QHeaderView.ResizeToContents)
    
    def __generateItem(self, revision, changeset, author, date, text):
        """
        Private method to generate an annotate item in the annotation list.
        
        @param revision revision string (string)
        @param changeset changeset string (string)
        @param author author of the change (string)
        @param date date of the change (string)
        @param text text of the change (string)
        """
        itm = QTreeWidgetItem(
            self.annotateList,
            [revision, changeset, author, date, "{0:d}".format(self.lineno),
             text])
        self.lineno += 1
        itm.setTextAlignment(0, Qt.AlignRight)
        itm.setTextAlignment(4, Qt.AlignRight)
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the annotation list.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(), self.vcs.getEncoding(),
                    'replace').strip()
            self.__processOutputLine(s)
    
    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        try:
            info, text = line.split(": ", 1)
        except ValueError:
            info = line[:-2]
            text = ""
        match = self.__annotateRe.match(info)
        author, rev, changeset, date, file = match.groups()
        self.__generateItem(rev.strip(), changeset.strip(), author.strip(),
                            date.strip(), text)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the hg process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgAnnotateDialog, self).keyPressEvent(evt)
コード例 #42
0
class GUIProcess(QObject):

    """An external process which shows notifications in the GUI.

    Args:
        cmd: The command which was started.
        args: A list of arguments which gets passed.
        verbose: Whether to show more messages.
        running: Whether the underlying process is started.
        what: What kind of thing is spawned (process/editor/userscript/...).
              Used in messages.
        _output_messages: Show output as messages.
        _proc: The underlying QProcess.

    Signals:
        error/finished/started signals proxied from QProcess.
    """

    error = pyqtSignal(QProcess.ProcessError)
    finished = pyqtSignal(int, QProcess.ExitStatus)
    started = pyqtSignal()

    def __init__(
            self,
            what: str,
            *,
            verbose: bool = False,
            additional_env: Mapping[str, str] = None,
            output_messages: bool = False,
            parent: QObject = None,
    ):
        super().__init__(parent)
        self.what = what
        self.verbose = verbose
        self._output_messages = output_messages
        self.outcome = ProcessOutcome(what=what)
        self.cmd: Optional[str] = None
        self.args: Optional[Sequence[str]] = None
        self.pid: Optional[int] = None

        self.stdout: str = ""
        self.stderr: str = ""

        self._cleanup_timer = usertypes.Timer(self, 'process-cleanup')
        self._cleanup_timer.setTimerType(Qt.VeryCoarseTimer)
        self._cleanup_timer.setInterval(3600 * 1000)  # 1h
        self._cleanup_timer.timeout.connect(self._on_cleanup_timer)
        self._cleanup_timer.setSingleShot(True)

        self._proc = QProcess(self)
        self._proc.errorOccurred.connect(self._on_error)
        self._proc.errorOccurred.connect(self.error)
        self._proc.finished.connect(self._on_finished)
        self._proc.finished.connect(self.finished)
        self._proc.started.connect(self._on_started)
        self._proc.started.connect(self.started)
        self._proc.readyReadStandardOutput.connect(self._on_ready_read_stdout)
        self._proc.readyReadStandardError.connect(self._on_ready_read_stderr)

        if additional_env is not None:
            procenv = QProcessEnvironment.systemEnvironment()
            for k, v in additional_env.items():
                procenv.insert(k, v)
            self._proc.setProcessEnvironment(procenv)

    def __str__(self) -> str:
        if self.cmd is None or self.args is None:
            return f'<unknown {self.what} command>'
        return ' '.join(shlex.quote(e) for e in [self.cmd] + list(self.args))

    def _decode_data(self, qba: QByteArray) -> str:
        """Decode data coming from a process."""
        encoding = locale.getpreferredencoding(do_setlocale=False)
        return qba.data().decode(encoding, 'replace')

    def _process_text(self, data: QByteArray, attr: str) -> None:
        """Process new stdout/stderr text.

        Arguments:
            data: The new process data.
            attr: Either 'stdout' or 'stderr'.
        """
        text = self._decode_data(data)

        if '\r' in text and not utils.is_windows:
            # Crude handling of CR for e.g. progress output.
            # Discard everything before the last \r in the new input, then discard
            # everything after the last \n in self.stdout/self.stderr.
            text = text.rsplit('\r', maxsplit=1)[-1]
            existing = getattr(self, attr)
            if '\n' in existing:
                new = existing.rsplit('\n', maxsplit=1)[0] + '\n'
            else:
                new = ''
            setattr(self, attr, new)

        if attr == 'stdout':
            self.stdout += text
        elif attr == 'stderr':
            self.stderr += text
        else:
            raise utils.Unreachable(attr)

    @pyqtSlot()
    def _on_ready_read_stdout(self) -> None:
        if not self._output_messages:
            return

        self._process_text(self._proc.readAllStandardOutput(), 'stdout')
        message.info(self._elide_output(self.stdout), replace=f"stdout-{self.pid}")

    @pyqtSlot()
    def _on_ready_read_stderr(self) -> None:
        if not self._output_messages:
            return
        self._process_text(self._proc.readAllStandardError(), 'stderr')
        message.error(self._elide_output(self.stderr), replace=f"stderr-{self.pid}")

    @pyqtSlot(QProcess.ProcessError)
    def _on_error(self, error: QProcess.ProcessError) -> None:
        """Show a message if there was an error while spawning."""
        if error == QProcess.Crashed and not utils.is_windows:
            # Already handled via ExitStatus in _on_finished
            return

        what = f"{self.what} {self.cmd!r}"
        error_descriptions = {
            QProcess.FailedToStart: f"{what.capitalize()} failed to start",
            QProcess.Crashed: f"{what.capitalize()} crashed",
            QProcess.Timedout: f"{what.capitalize()} timed out",
            QProcess.WriteError: f"Write error for {what}",
            QProcess.ReadError: f"Read error for {what}",
        }
        error_string = self._proc.errorString()
        msg = ': '.join([error_descriptions[error], error_string])

        # We can't get some kind of error code from Qt...
        # https://bugreports.qt.io/browse/QTBUG-44769
        # However, it looks like those strings aren't actually translated?
        known_errors = ['No such file or directory', 'Permission denied']
        if (': ' in error_string and  # pragma: no branch
                error_string.split(': ', maxsplit=1)[1] in known_errors):
            msg += f'\nHint: Make sure {self.cmd!r} exists and is executable'
            if version.is_flatpak():
                msg += ' inside the Flatpak container'

        message.error(msg)

    def _elide_output(self, output: str) -> str:
        """Shorten long output before showing it."""
        output = output.strip()
        lines = output.splitlines()
        count = len(lines)
        threshold = 20

        if count > threshold:
            lines = [
                f'[{count - threshold} lines hidden, see :process for the full output]'
            ] + lines[-threshold:]
            output = '\n'.join(lines)

        return output

    @pyqtSlot(int, QProcess.ExitStatus)
    def _on_finished(self, code: int, status: QProcess.ExitStatus) -> None:
        """Show a message when the process finished."""
        log.procs.debug("Process finished with code {}, status {}.".format(
            code, status))

        self.outcome.running = False
        self.outcome.code = code
        self.outcome.status = status

        self.stderr += self._decode_data(self._proc.readAllStandardError())
        self.stdout += self._decode_data(self._proc.readAllStandardOutput())

        if self._output_messages:
            if self.stdout:
                message.info(
                    self._elide_output(self.stdout), replace=f"stdout-{self.pid}")
            if self.stderr:
                message.error(
                    self._elide_output(self.stderr), replace=f"stderr-{self.pid}")

        if self.outcome.was_successful():
            if self.verbose:
                message.info(str(self.outcome))
            self._cleanup_timer.start()
        else:
            if self.stdout:
                log.procs.error("Process stdout:\n" + self.stdout.strip())
            if self.stderr:
                log.procs.error("Process stderr:\n" + self.stderr.strip())
            message.error(str(self.outcome) + " See :process for details.")

    @pyqtSlot()
    def _on_started(self) -> None:
        """Called when the process started successfully."""
        log.procs.debug("Process started.")
        assert not self.outcome.running
        self.outcome.running = True

    def _pre_start(self, cmd: str, args: Sequence[str]) -> None:
        """Prepare starting of a QProcess."""
        if self.outcome.running:
            raise ValueError("Trying to start a running QProcess!")
        self.cmd = cmd
        self.args = args
        log.procs.debug(f"Executing: {self}")
        if self.verbose:
            message.info(f'Executing: {self}')

    def start(self, cmd: str, args: Sequence[str]) -> None:
        """Convenience wrapper around QProcess::start."""
        log.procs.debug("Starting process.")
        self._pre_start(cmd, args)
        self._proc.start(cmd, args)
        self._post_start()
        self._proc.closeWriteChannel()

    def start_detached(self, cmd: str, args: Sequence[str]) -> bool:
        """Convenience wrapper around QProcess::startDetached."""
        log.procs.debug("Starting detached.")
        self._pre_start(cmd, args)
        ok, self.pid = self._proc.startDetached(
            cmd, args, None)  # type: ignore[call-arg]

        if not ok:
            message.error("Error while spawning {}".format(self.what))
            return False

        log.procs.debug("Process started.")
        self.outcome.running = True
        self._post_start()
        return True

    def _post_start(self) -> None:
        """Register this process and remember the process ID after starting."""
        self.pid = self._proc.processId()
        all_processes[self.pid] = self
        global last_pid
        last_pid = self.pid

    @pyqtSlot()
    def _on_cleanup_timer(self) -> None:
        """Remove the process from all registered processes."""
        log.procs.debug(f"Cleaning up data for {self.pid}")
        assert self.pid in all_processes
        all_processes[self.pid] = None
        self.deleteLater()

    def terminate(self, kill: bool = False) -> None:
        """Terminate or kill the process."""
        if kill:
            self._proc.kill()
        else:
            self._proc.terminate()
コード例 #43
0
class HgQueuesHeaderDialog(QDialog, Ui_HgQueuesHeaderDialog):
    """
    Class implementing a dialog to show the commit message of the current
    patch.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent reference to the parent widget (QWidget)
        """
        super(HgQueuesHeaderDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.vcs = vcs
        self.__hgClient = vcs.getClient()
        
        if self.__hgClient:
            self.process = None
        else:
            self.process = QProcess()
            self.process.finished.connect(self.__procFinished)
            self.process.readyReadStandardOutput.connect(self.__readStdout)
            self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.show()
        QCoreApplication.processEvents()
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        e.accept()
    
    def start(self, path):
        """
        Public slot to start the list command.
        
        @param path name of directory to be listed (string)
        """
        self.activateWindow()
        
        dname, fname = self.vcs.splitPath(path)
        
        # find the root of the repo
        repodir = dname
        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
            repodir = os.path.dirname(repodir)
            if os.path.splitdrive(repodir)[1] == os.sep:
                return
        
        args = self.vcs.initCommand("qheader")
        
        if self.__hgClient:
            out, err = self.__hgClient.runcommand(
                args, output=self.__showOutput, error=self.__showError)
            if err:
                self.__showError(err)
            if out:
                self.__showOutPut(out)
            self.__finish()
        else:
            self.process.kill()
            self.process.setWorkingDirectory(repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardOutput(),
                    self.vcs.getEncoding(), 'replace')
            self.__showOutput(s)
    
    def __showOutput(self, out):
        """
        Private slot to show some output.
        
        @param out output to be shown (string)
        """
        self.messageEdit.appendPlainText(out)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.messageEdit.appendPlainText(self.tr("Error: "))
        self.messageEdit.appendPlainText(out)
コード例 #44
0
class HgBookmarksInOutDialog(QDialog, Ui_HgBookmarksInOutDialog):
    """
    Class implementing a dialog to show a list of incoming or outgoing
    bookmarks.
    """
    INCOMING = 0
    OUTGOING = 1
    
    def __init__(self, vcs, mode, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param mode mode of the dialog (HgBookmarksInOutDialog.INCOMING,
            HgBookmarksInOutDialog.OUTGOING)
        @param parent reference to the parent widget (QWidget)
        @exception ValueError raised to indicate an invalid dialog mode
        """
        super(HgBookmarksInOutDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.Window)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        if mode not in [self.INCOMING, self.OUTGOING]:
            raise ValueError("Bad value for mode")
        if mode == self.INCOMING:
            self.setWindowTitle(self.tr("Mercurial Incoming Bookmarks"))
        elif mode == self.OUTGOING:
            self.setWindowTitle(self.tr("Mercurial Outgoing Bookmarks"))
        
        self.process = QProcess()
        self.vcs = vcs
        self.mode = mode
        self.__hgClient = vcs.getClient()
        
        self.bookmarksList.headerItem().setText(
            self.bookmarksList.columnCount(), "")
        self.bookmarksList.header().setSortIndicator(3, Qt.AscendingOrder)
        
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.show()
        QCoreApplication.processEvents()
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        e.accept()
    
    def start(self, path):
        """
        Public slot to start the bookmarks command.
        
        @param path name of directory to be listed (string)
        @exception ValueError raised to indicate an invalid dialog mode
        """
        self.errorGroup.hide()
        
        self.intercept = False
        self.activateWindow()
        
        dname, fname = self.vcs.splitPath(path)
        
        # find the root of the repo
        repodir = dname
        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
            repodir = os.path.dirname(repodir)
            if os.path.splitdrive(repodir)[1] == os.sep:
                return
        
        if self.mode == self.INCOMING:
            args = self.vcs.initCommand("incoming")
        elif self.mode == self.OUTGOING:
            args = self.vcs.initCommand("outgoing")
        else:
            raise ValueError("Bad value for mode")
        args.append('--bookmarks')
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            if err:
                self.__showError(err)
            if out:
                for line in out.splitlines():
                    self.__processOutputLine(line)
                    if self.__hgClient.wasCanceled():
                        break
            self.__finish()
        else:
            self.process.kill()
            self.process.setWorkingDirectory(repodir)
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
            else:
                self.inputGroup.setEnabled(True)
                self.inputGroup.show()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        if self.bookmarksList.topLevelItemCount() == 0:
            # no bookmarks defined
            self.__generateItem(self.tr("no bookmarks found"), "")
        self.__resizeColumns()
        self.__resort()
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.bookmarksList.sortItems(
            self.bookmarksList.sortColumn(),
            self.bookmarksList.header().sortIndicatorOrder())
    
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.bookmarksList.header().resizeSections(
            QHeaderView.ResizeToContents)
        self.bookmarksList.header().setStretchLastSection(True)
    
    def __generateItem(self, changeset, name):
        """
        Private method to generate a bookmark item in the bookmarks list.
        
        @param changeset changeset of the bookmark (string)
        @param name name of the bookmark (string)
        """
        QTreeWidgetItem(self.bookmarksList, [
            name,
            changeset])
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(), self.vcs.getEncoding(), 'replace')
            self.__processOutputLine(s)
    
    def __processOutputLine(self, line):
        """
        Private method to process the lines of output.
        
        @param line output line to be processed (string)
        """
        if line.startswith(" "):
            li = line.strip().split()
            changeset = li[-1]
            del li[-1]
            name = " ".join(li)
            self.__generateItem(changeset, name)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgBookmarksInOutDialog, self).keyPressEvent(evt)
コード例 #45
0
ファイル: SvnLogDialog.py プロジェクト: pycom/EricShort
class SvnLogDialog(QWidget, Ui_SvnLogDialog):
    """
    Class implementing a dialog to show the output of the svn log command
    process.
    
    The dialog is nonmodal. Clicking a link in the upper text pane shows
    a diff of the versions.
    """
    def __init__(self, vcs, isFile=False, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param isFile flag indicating log for a file is to be shown (boolean)
        @param parent parent widget (QWidget)
        """
        super(SvnLogDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.process = QProcess()
        self.vcs = vcs
        
        self.contents.setHtml(
            self.tr('<b>Processing your request, please wait...</b>'))
        
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.contents.anchorClicked.connect(self.__sourceChanged)
        
        self.rx_sep = QRegExp('\\-+\\s*')
        self.rx_sep2 = QRegExp('=+\\s*')
        self.rx_rev = QRegExp(
            'rev ([0-9]+):  ([^|]*) \| ([^|]*) \| ([0-9]+) .*')
        # "rev" followed by one or more decimals followed by a colon followed
        # anything up to " | " (twice) followed by one or more decimals
        # followed by anything
        self.rx_rev2 = QRegExp(
            'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*')
        # "r" followed by one or more decimals followed by " | " followed
        # anything up to " | " (twice) followed by one or more decimals
        # followed by anything
        self.rx_flags = QRegExp('   ([ADM])( .*)\\s*')
        # three blanks followed by A or D or M
        self.rx_changed = QRegExp('Changed .*\\s*')
        
        self.flags = {
            'A': self.tr('Added'),
            'D': self.tr('Deleted'),
            'M': self.tr('Modified')
        }
        
        self.revisions = []  # stack of remembered revisions
        self.revString = self.tr('revision')
        
        self.buf = []        # buffer for stdout
        self.diff = None
        
        self.sbsCheckBox.setEnabled(isFile)
        self.sbsCheckBox.setVisible(isFile)
        
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        e.accept()
        
    def start(self, fn, noEntries=0):
        """
        Public slot to start the cvs log command.
        
        @param fn filename to show the log for (string)
        @param noEntries number of entries to show (integer)
        """
        self.errorGroup.hide()
        QApplication.processEvents()
        
        self.intercept = False
        self.filename = fn
        self.dname, self.fname = self.vcs.splitPath(fn)
        
        self.process.kill()
        
        args = []
        args.append('log')
        self.vcs.addArguments(args, self.vcs.options['global'])
        self.vcs.addArguments(args, self.vcs.options['log'])
        if noEntries:
            args.append('--limit')
            args.append(str(noEntries))
        self.activateWindow()
        self.raise_()
        args.append(self.fname)
        
        self.process.setWorkingDirectory(self.dname)
        
        self.process.start('svn', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            self.inputGroup.setEnabled(False)
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('svn'))
        
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.contents.clear()
        
        lvers = 1
        for s in self.buf:
            rev_match = False
            if self.rx_rev.exactMatch(s):
                ver = self.rx_rev.cap(1)
                author = self.rx_rev.cap(2)
                date = self.rx_rev.cap(3)
                # number of lines is ignored
                rev_match = True
            elif self.rx_rev2.exactMatch(s):
                ver = self.rx_rev2.cap(1)
                author = self.rx_rev2.cap(2)
                date = self.rx_rev2.cap(3)
                # number of lines is ignored
                rev_match = True
            
            if rev_match:
                dstr = '<b>{0} {1}</b>'.format(self.revString, ver)
                try:
                    lv = self.revisions[lvers]
                    lvers += 1
                    url = QUrl()
                    url.setScheme("file")
                    url.setPath(self.filename)
                    if qVersion() >= "5.0.0":
                        query = lv + '_' + ver
                        url.setQuery(query)
                    else:
                        query = QByteArray()
                        query.append(lv).append('_').append(ver)
                        url.setEncodedQuery(query)
                    dstr += ' [<a href="{0}" name="{1}">{2}</a>]'.format(
                        url.toString(), query,
                        self.tr('diff to {0}').format(lv),
                    )
                except IndexError:
                    pass
                dstr += '<br />\n'
                self.contents.insertHtml(dstr)
                
                dstr = self.tr('<i>author: {0}</i><br />\n').format(author)
                self.contents.insertHtml(dstr)
                
                dstr = self.tr('<i>date: {0}</i><br />\n').format(date)
                self.contents.insertHtml(dstr)
            
            elif self.rx_sep.exactMatch(s) or self.rx_sep2.exactMatch(s):
                self.contents.insertHtml('<hr />\n')
            
            elif self.rx_flags.exactMatch(s):
                dstr = self.flags[self.rx_flags.cap(1)]
                dstr += self.rx_flags.cap(2)
                dstr += '<br />\n'
                self.contents.insertHtml(dstr)
            
            elif self.rx_changed.exactMatch(s):
                dstr = '<br />{0}<br />\n'.format(s)
                self.contents.insertHtml(dstr)
            
            else:
                if s == "":
                    s = self.contents.insertHtml('<br />\n')
                else:
                    self.contents.insertHtml(Utilities.html_encode(s))
                    self.contents.insertHtml('<br />\n')
        
        tc = self.contents.textCursor()
        tc.movePosition(QTextCursor.Start)
        self.contents.setTextCursor(tc)
        self.contents.ensureCursorVisible()
        
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process and inserts it into a buffer.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            line = str(self.process.readLine(),
                       Preferences.getSystem("IOEncoding"),
                       'replace')
            self.buf.append(line)
            if self.rx_rev.exactMatch(line):
                ver = self.rx_rev.cap(1)
                # save revision number for later use
                self.revisions.append(ver)
            elif self.rx_rev2.exactMatch(line):
                ver = self.rx_rev2.cap(1)
                # save revision number for later use
                self.revisions.append(ver)
        
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            self.errorGroup.show()
            s = str(self.process.readAllStandardError(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
        
    def __sourceChanged(self, url):
        """
        Private slot to handle the sourceChanged signal of the contents pane.
        
        @param url the url that was clicked (QUrl)
        """
        self.contents.setSource(QUrl(''))
        filename = url.path()
        if Utilities.isWindowsPlatform():
            if filename.startswith("/"):
                filename = filename[1:]
        if qVersion() >= "5.0.0":
            ver = url.query()
        else:
            ver = bytes(url.encodedQuery()).decode()
        v1 = ver.split('_')[0]
        v2 = ver.split('_')[1]
        if v1 == "" or v2 == "":
            return
        self.contents.scrollToAnchor(ver)
        
        if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked():
            self.vcs.svnSbsDiff(filename, revisions=(v1, v2))
        else:
            if self.diff is None:
                from .SvnDiffDialog import SvnDiffDialog
                self.diff = SvnDiffDialog(self.vcs)
            self.diff.show()
            self.diff.start(filename, [v1, v2])
        
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
        
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
        
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
        
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(SvnLogDialog, self).keyPressEvent(evt)
コード例 #46
0
ファイル: SvnLogBrowserDialog.py プロジェクト: testmana2/test
class SvnLogBrowserDialog(QWidget, Ui_SvnLogBrowserDialog):
    """
    Class implementing a dialog to browse the log history.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(SvnLogBrowserDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.__position = QPoint()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.filesTree.headerItem().setText(self.filesTree.columnCount(), "")
        self.filesTree.header().setSortIndicator(0, Qt.AscendingOrder)
        
        self.vcs = vcs
        
        self.__initData()
        
        self.fromDate.setDisplayFormat("yyyy-MM-dd")
        self.toDate.setDisplayFormat("yyyy-MM-dd")
        self.__resetUI()
        
        self.__messageRole = Qt.UserRole
        self.__changesRole = Qt.UserRole + 1
        
        self.process = QProcess()
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.rx_sep1 = QRegExp('\\-+\\s*')
        self.rx_sep2 = QRegExp('=+\\s*')
        self.rx_rev1 = QRegExp(
            'rev ([0-9]+):  ([^|]*) \| ([^|]*) \| ([0-9]+) .*')
        # "rev" followed by one or more decimals followed by a colon followed
        # anything up to " | " (twice) followed by one or more decimals
        # followed by anything
        self.rx_rev2 = QRegExp(
            'r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*')
        # "r" followed by one or more decimals followed by " | " followed
        # anything up to " | " (twice) followed by one or more decimals
        # followed by anything
        self.rx_flags1 = QRegExp(
            r"""   ([ADM])\s(.*)\s+\(\w+\s+(.*):([0-9]+)\)\s*""")
        # three blanks followed by A or D or M followed by path followed by
        # path copied from followed by copied from revision
        self.rx_flags2 = QRegExp('   ([ADM]) (.*)\\s*')
        # three blanks followed by A or D or M followed by path
        
        self.flags = {
            'A': self.tr('Added'),
            'D': self.tr('Deleted'),
            'M': self.tr('Modified'),
            'R': self.tr('Replaced'),
        }
        self.intercept = False
    
    def __initData(self):
        """
        Private method to (re-)initialize some data.
        """
        self.__maxDate = QDate()
        self.__minDate = QDate()
        self.__filterLogsEnabled = True
        
        self.buf = []        # buffer for stdout
        self.diff = None
        self.__started = False
        self.__lastRev = 0
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.__position = self.pos()
        
        e.accept()
        
    def show(self):
        """
        Public slot to show the dialog.
        """
        if not self.__position.isNull():
            self.move(self.__position)
        self.__resetUI()
        
        super(SvnLogBrowserDialog, self).show()
    
    def __resetUI(self):
        """
        Private method to reset the user interface.
        """
        self.fromDate.setDate(QDate.currentDate())
        self.toDate.setDate(QDate.currentDate())
        self.fieldCombo.setCurrentIndex(self.fieldCombo.findText(
            self.tr("Message")))
        self.limitSpinBox.setValue(self.vcs.getPlugin().getPreferences(
            "LogLimit"))
        self.stopCheckBox.setChecked(self.vcs.getPlugin().getPreferences(
            "StopLogOnCopy"))
        
        self.logTree.clear()
        
        self.nextButton.setEnabled(True)
        self.limitSpinBox.setEnabled(True)

    def __resizeColumnsLog(self):
        """
        Private method to resize the log tree columns.
        """
        self.logTree.header().resizeSections(QHeaderView.ResizeToContents)
        self.logTree.header().setStretchLastSection(True)
    
    def __resortLog(self):
        """
        Private method to resort the log tree.
        """
        self.logTree.sortItems(
            self.logTree.sortColumn(),
            self.logTree.header().sortIndicatorOrder())
    
    def __resizeColumnsFiles(self):
        """
        Private method to resize the changed files tree columns.
        """
        self.filesTree.header().resizeSections(QHeaderView.ResizeToContents)
        self.filesTree.header().setStretchLastSection(True)
    
    def __resortFiles(self):
        """
        Private method to resort the changed files tree.
        """
        sortColumn = self.filesTree.sortColumn()
        self.filesTree.sortItems(
            1, self.filesTree.header().sortIndicatorOrder())
        self.filesTree.sortItems(
            sortColumn, self.filesTree.header().sortIndicatorOrder())
    
    def __generateLogItem(self, author, date, message, revision, changedPaths):
        """
        Private method to generate a log tree entry.
        
        @param author author info (string)
        @param date date info (string)
        @param message text of the log message (list of strings)
        @param revision revision info (string)
        @param changedPaths list of dictionary objects containing
            info about the changed files/directories
        @return reference to the generated item (QTreeWidgetItem)
        """
        msg = []
        for line in message:
            msg.append(line.strip())
        
        itm = QTreeWidgetItem(self.logTree)
        itm.setData(0, Qt.DisplayRole, int(revision))
        itm.setData(1, Qt.DisplayRole, author)
        itm.setData(2, Qt.DisplayRole, date)
        itm.setData(3, Qt.DisplayRole, " ".join(msg))
        
        itm.setData(0, self.__messageRole, message)
        itm.setData(0, self.__changesRole, changedPaths)
        
        itm.setTextAlignment(0, Qt.AlignRight)
        itm.setTextAlignment(1, Qt.AlignLeft)
        itm.setTextAlignment(2, Qt.AlignLeft)
        itm.setTextAlignment(3, Qt.AlignLeft)
        itm.setTextAlignment(4, Qt.AlignLeft)
        
        try:
            self.__lastRev = int(revision)
        except ValueError:
            self.__lastRev = 0
        
        return itm
    
    def __generateFileItem(self, action, path, copyFrom, copyRev):
        """
        Private method to generate a changed files tree entry.
        
        @param action indicator for the change action ("A", "D" or "M")
        @param path path of the file in the repository (string)
        @param copyFrom path the file was copied from (None, string)
        @param copyRev revision the file was copied from (None, string)
        @return reference to the generated item (QTreeWidgetItem)
        """
        itm = QTreeWidgetItem(self.filesTree, [
            self.flags[action],
            path,
            copyFrom,
            copyRev,
        ])
        
        itm.setTextAlignment(3, Qt.AlignRight)
        
        return itm
    
    def __getLogEntries(self, startRev=None):
        """
        Private method to retrieve log entries from the repository.
        
        @param startRev revision number to start from (integer, string)
        """
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        QApplication.processEvents()
        
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        QApplication.processEvents()
        
        self.intercept = False
        self.process.kill()
        
        self.buf = []
        self.cancelled = False
        self.errors.clear()
        
        args = []
        args.append('log')
        self.vcs.addArguments(args, self.vcs.options['global'])
        self.vcs.addArguments(args, self.vcs.options['log'])
        args.append('--verbose')
        args.append('--limit')
        args.append('{0:d}'.format(self.limitSpinBox.value()))
        if startRev is not None:
            args.append('--revision')
            args.append('{0}:0'.format(startRev))
        if self.stopCheckBox.isChecked():
            args.append('--stop-on-copy')
        args.append(self.fname)
        
        self.process.setWorkingDirectory(self.dname)
        
        self.inputGroup.setEnabled(True)
        self.inputGroup.show()
        
        self.process.start('svn', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('svn'))
    
    def start(self, fn, isFile=False):
        """
        Public slot to start the svn log command.
        
        @param fn filename to show the log for (string)
        @keyparam isFile flag indicating log for a file is to be shown
            (boolean)
        """
        self.sbsCheckBox.setEnabled(isFile)
        self.sbsCheckBox.setVisible(isFile)
        
        self.errorGroup.hide()
        QApplication.processEvents()
        
        self.__initData()
        
        self.filename = fn
        self.dname, self.fname = self.vcs.splitPath(fn)
        
        self.activateWindow()
        self.raise_()
        
        self.logTree.clear()
        self.__started = True
        self.__getLogEntries()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__processBuffer()
        self.__finish()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed the
        button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        QApplication.restoreOverrideCursor()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
    
    def __processBuffer(self):
        """
        Private method to process the buffered output of the svn log command.
        """
        noEntries = 0
        log = {"message": []}
        changedPaths = []
        for s in self.buf:
            if self.rx_rev1.exactMatch(s):
                log["revision"] = self.rx_rev.cap(1)
                log["author"] = self.rx_rev.cap(2)
                log["date"] = self.rx_rev.cap(3)
                # number of lines is ignored
            elif self.rx_rev2.exactMatch(s):
                log["revision"] = self.rx_rev2.cap(1)
                log["author"] = self.rx_rev2.cap(2)
                log["date"] = self.rx_rev2.cap(3)
                # number of lines is ignored
            elif self.rx_flags1.exactMatch(s):
                changedPaths.append({
                    "action":
                    self.rx_flags1.cap(1).strip(),
                    "path":
                    self.rx_flags1.cap(2).strip(),
                    "copyfrom_path":
                    self.rx_flags1.cap(3).strip(),
                    "copyfrom_revision":
                    self.rx_flags1.cap(4).strip(),
                })
            elif self.rx_flags2.exactMatch(s):
                changedPaths.append({
                    "action":
                    self.rx_flags2.cap(1).strip(),
                    "path":
                    self.rx_flags2.cap(2).strip(),
                    "copyfrom_path": "",
                    "copyfrom_revision": "",
                })
            elif self.rx_sep1.exactMatch(s) or self.rx_sep2.exactMatch(s):
                if len(log) > 1:
                    self.__generateLogItem(
                        log["author"], log["date"], log["message"],
                        log["revision"], changedPaths)
                    dt = QDate.fromString(log["date"], Qt.ISODate)
                    if not self.__maxDate.isValid() and \
                            not self.__minDate.isValid():
                        self.__maxDate = dt
                        self.__minDate = dt
                    else:
                        if self.__maxDate < dt:
                            self.__maxDate = dt
                        if self.__minDate > dt:
                            self.__minDate = dt
                    noEntries += 1
                    log = {"message": []}
                    changedPaths = []
            else:
                if s.strip().endswith(":") or not s.strip():
                    continue
                else:
                    log["message"].append(s)
        
        self.__resizeColumnsLog()
        self.__resortLog()
        
        if self.__started:
            self.logTree.setCurrentItem(self.logTree.topLevelItem(0))
            self.__started = False
        
        if noEntries < self.limitSpinBox.value() and not self.cancelled:
            self.nextButton.setEnabled(False)
            self.limitSpinBox.setEnabled(False)
        
        self.__filterLogsEnabled = False
        self.fromDate.setMinimumDate(self.__minDate)
        self.fromDate.setMaximumDate(self.__maxDate)
        self.fromDate.setDate(self.__minDate)
        self.toDate.setMinimumDate(self.__minDate)
        self.toDate.setMaximumDate(self.__maxDate)
        self.toDate.setDate(self.__maxDate)
        self.__filterLogsEnabled = True
        self.__filterLogs()
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process and inserts it into a buffer.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            line = str(self.process.readLine(),
                       Preferences.getSystem("IOEncoding"),
                       'replace')
            self.buf.append(line)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            self.errorGroup.show()
            s = str(self.process.readAllStandardError(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
    
    def __diffRevisions(self, rev1, rev2):
        """
        Private method to do a diff of two revisions.
        
        @param rev1 first revision number (integer)
        @param rev2 second revision number (integer)
        """
        if self.sbsCheckBox.isEnabled() and self.sbsCheckBox.isChecked():
            self.vcs.svnSbsDiff(self.filename,
                                revisions=(str(rev1), str(rev2)))
        else:
            if self.diff is None:
                from .SvnDiffDialog import SvnDiffDialog
                self.diff = SvnDiffDialog(self.vcs)
            self.diff.show()
            self.diff.raise_()
            self.diff.start(self.filename, [rev1, rev2])
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.cancelled = True
            self.__finish()
    
    @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
    def on_logTree_currentItemChanged(self, current, previous):
        """
        Private slot called, when the current item of the log tree changes.
        
        @param current reference to the new current item (QTreeWidgetItem)
        @param previous reference to the old current item (QTreeWidgetItem)
        """
        if current is not None:
            self.messageEdit.clear()
            for line in current.data(0, self.__messageRole):
                self.messageEdit.append(line.strip())
            
            self.filesTree.clear()
            changes = current.data(0, self.__changesRole)
            if len(changes) > 0:
                for change in changes:
                    self.__generateFileItem(
                        change["action"], change["path"],
                        change["copyfrom_path"],
                        change["copyfrom_revision"])
                self.__resizeColumnsFiles()
            self.__resortFiles()
        
        self.diffPreviousButton.setEnabled(
            current != self.logTree.topLevelItem(
                self.logTree.topLevelItemCount() - 1))
    
    @pyqtSlot()
    def on_logTree_itemSelectionChanged(self):
        """
        Private slot called, when the selection has changed.
        """
        self.diffRevisionsButton.setEnabled(
            len(self.logTree.selectedItems()) == 2)
    
    @pyqtSlot()
    def on_nextButton_clicked(self):
        """
        Private slot to handle the Next button.
        """
        if self.__lastRev > 1:
            self.__getLogEntries(self.__lastRev - 1)
    
    @pyqtSlot()
    def on_diffPreviousButton_clicked(self):
        """
        Private slot to handle the Diff to Previous button.
        """
        itm = self.logTree.currentItem()
        if itm is None:
            self.diffPreviousButton.setEnabled(False)
            return
        rev2 = int(itm.text(0))
        
        itm = self.logTree.topLevelItem(
            self.logTree.indexOfTopLevelItem(itm) + 1)
        if itm is None:
            self.diffPreviousButton.setEnabled(False)
            return
        rev1 = int(itm.text(0))
        
        self.__diffRevisions(rev1, rev2)
    
    @pyqtSlot()
    def on_diffRevisionsButton_clicked(self):
        """
        Private slot to handle the Compare Revisions button.
        """
        items = self.logTree.selectedItems()
        if len(items) != 2:
            self.diffRevisionsButton.setEnabled(False)
            return
        
        rev2 = int(items[0].text(0))
        rev1 = int(items[1].text(0))
        
        self.__diffRevisions(min(rev1, rev2), max(rev1, rev2))
    
    @pyqtSlot(QDate)
    def on_fromDate_dateChanged(self, date):
        """
        Private slot called, when the from date changes.
        
        @param date new date (QDate)
        """
        self.__filterLogs()
    
    @pyqtSlot(QDate)
    def on_toDate_dateChanged(self, date):
        """
        Private slot called, when the from date changes.
        
        @param date new date (QDate)
        """
        self.__filterLogs()
    
    @pyqtSlot(str)
    def on_fieldCombo_activated(self, txt):
        """
        Private slot called, when a new filter field is selected.
        
        @param txt text of the selected field (string)
        """
        self.__filterLogs()
    
    @pyqtSlot(str)
    def on_rxEdit_textChanged(self, txt):
        """
        Private slot called, when a filter expression is entered.
        
        @param txt filter expression (string)
        """
        self.__filterLogs()
    
    def __filterLogs(self):
        """
        Private method to filter the log entries.
        """
        if self.__filterLogsEnabled:
            from_ = self.fromDate.date().toString("yyyy-MM-dd")
            to_ = self.toDate.date().addDays(1).toString("yyyy-MM-dd")
            txt = self.fieldCombo.currentText()
            if txt == self.tr("Author"):
                fieldIndex = 1
                searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive)
            elif txt == self.tr("Revision"):
                fieldIndex = 0
                txt = self.rxEdit.text()
                if txt.startswith("^"):
                    searchRx = QRegExp(
                        "^\s*{0}".format(txt[1:]), Qt.CaseInsensitive)
                else:
                    searchRx = QRegExp(txt, Qt.CaseInsensitive)
            else:
                fieldIndex = 3
                searchRx = QRegExp(self.rxEdit.text(), Qt.CaseInsensitive)
            
            currentItem = self.logTree.currentItem()
            for topIndex in range(self.logTree.topLevelItemCount()):
                topItem = self.logTree.topLevelItem(topIndex)
                if topItem.text(2) <= to_ and topItem.text(2) >= from_ and \
                   searchRx.indexIn(topItem.text(fieldIndex)) > -1:
                    topItem.setHidden(False)
                    if topItem is currentItem:
                        self.on_logTree_currentItemChanged(topItem, None)
                else:
                    topItem.setHidden(True)
                    if topItem is currentItem:
                        self.messageEdit.clear()
                        self.filesTree.clear()
    
    @pyqtSlot(bool)
    def on_stopCheckBox_clicked(self, checked):
        """
        Private slot called, when the stop on copy/move checkbox is clicked.
        
        @param checked flag indicating the checked state (boolean)
        """
        self.vcs.getPlugin().setPreferences("StopLogOnCopy",
                                            self.stopCheckBox.isChecked())
        self.nextButton.setEnabled(True)
        self.limitSpinBox.setEnabled(True)
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        self.errorGroup.show()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(SvnLogBrowserDialog, self).keyPressEvent(evt)
コード例 #47
0
class SvnBlameDialog(QDialog, Ui_SvnBlameDialog):
    """
    Class implementing a dialog to show the output of the svn blame command.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(SvnBlameDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.process = QProcess()
        self.vcs = vcs
        
        self.blameList.headerItem().setText(self.blameList.columnCount(), "")
        font = Preferences.getEditorOtherFonts("MonospacedFont")
        self.blameList.setFont(font)
        
        self.__ioEncoding = Preferences.getSystem("IOEncoding")
        
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        e.accept()
        
    def start(self, fn):
        """
        Public slot to start the svn status command.
        
        @param fn filename to show the log for (string)
        """
        self.errorGroup.hide()
        self.intercept = False
        self.activateWindow()
        self.lineno = 1
        
        dname, fname = self.vcs.splitPath(fn)
        
        self.process.kill()
        
        args = []
        args.append('blame')
        self.vcs.addArguments(args, self.vcs.options['global'])
        args.append(fname)
        
        self.process.setWorkingDirectory(dname)
        
        self.process.start('svn', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('svn'))
        else:
            self.inputGroup.setEnabled(True)
            self.inputGroup.show()
        
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed the
        button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.process = None
        
        self.__resizeColumns()
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__finish()
        
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
        
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.blameList.header().resizeSections(QHeaderView.ResizeToContents)
        
    def __generateItem(self, revision, author, text):
        """
        Private method to generate a blame item in the blame list.
        
        @param revision revision string (string)
        @param author author of the change (string)
        @param text line of text from the annotated file (string)
        """
        itm = QTreeWidgetItem(
            self.blameList,
            [revision, author, "{0:d}".format(self.lineno), text])
        self.lineno += 1
        itm.setTextAlignment(0, Qt.AlignRight)
        itm.setTextAlignment(2, Qt.AlignRight)
        
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(), self.__ioEncoding, 'replace')\
                .strip()
            rev, s = s.split(None, 1)
            try:
                author, text = s.split(' ', 1)
            except ValueError:
                author = s.strip()
                text = ""
            self.__generateItem(rev, author, text)
        
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            self.errorGroup.show()
            s = str(self.process.readAllStandardError(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
        
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
        
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
        
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
        
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(SvnBlameDialog, self).keyPressEvent(evt)
コード例 #48
0
ファイル: SvnDiffDialog.py プロジェクト: testmana2/test
class SvnDiffDialog(QWidget, Ui_SvnDiffDialog):
    """
    Class implementing a dialog to show the output of the svn diff command
    process.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(SvnDiffDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.refreshButton = self.buttonBox.addButton(
            self.tr("Refresh"), QDialogButtonBox.ActionRole)
        self.refreshButton.setToolTip(
            self.tr("Press to refresh the display"))
        self.refreshButton.setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.searchWidget.attachTextEdit(self.contents)
        
        self.process = QProcess()
        self.vcs = vcs
        
        font = Preferences.getEditorOtherFonts("MonospacedFont")
        self.contents.setFontFamily(font.family())
        self.contents.setFontPointSize(font.pointSize())
        
        self.highlighter = SvnDiffHighlighter(self.contents.document())
        
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        e.accept()
        
    def __getVersionArg(self, version):
        """
        Private method to get a svn revision argument for the given revision.
        
        @param version revision (integer or string)
        @return version argument (string)
        """
        if version == "WORKING":
            return None
        else:
            return str(version)
        
    def start(self, fn, versions=None, urls=None, summary=False,
              refreshable=False):
        """
        Public slot to start the svn diff command.
        
        @param fn filename to be diffed (string)
        @param versions list of versions to be diffed (list of up to 2 strings
            or None)
        @keyparam urls list of repository URLs (list of 2 strings)
        @keyparam summary flag indicating a summarizing diff
            (only valid for URL diffs) (boolean)
        @keyparam refreshable flag indicating a refreshable diff (boolean)
        """
        self.refreshButton.setVisible(refreshable)
        
        self.errorGroup.hide()
        self.inputGroup.show()
        self.inputGroup.setEnabled(True)
        self.intercept = False
        self.filename = fn
        
        self.process.kill()
        
        self.contents.clear()
        self.paras = 0
        
        self.filesCombo.clear()
        
        self.__oldFile = ""
        self.__oldFileLine = -1
        self.__fileSeparators = []
        
        args = []
        args.append('diff')
        self.vcs.addArguments(args, self.vcs.options['global'])
        self.vcs.addArguments(args, self.vcs.options['diff'])
        if '--diff-cmd' in self.vcs.options['diff']:
            self.buttonBox.button(QDialogButtonBox.Save).hide()
        
        if versions is not None:
            self.raise_()
            self.activateWindow()
            
            rev1 = self.__getVersionArg(versions[0])
            rev2 = None
            if len(versions) == 2:
                rev2 = self.__getVersionArg(versions[1])
            
            if rev1 is not None or rev2 is not None:
                args.append('-r')
                if rev1 is not None and rev2 is not None:
                    args.append('{0}:{1}'.format(rev1, rev2))
                elif rev2 is None:
                    args.append(rev1)
                elif rev1 is None:
                    args.append(rev2)
        
        self.summaryPath = None
        if urls is not None:
            if summary:
                args.append("--summarize")
                self.summaryPath = urls[0]
            args.append("--old={0}".format(urls[0]))
            args.append("--new={0}".format(urls[1]))
            if isinstance(fn, list):
                dname, fnames = self.vcs.splitPathList(fn)
            else:
                dname, fname = self.vcs.splitPath(fn)
                fnames = [fname]
            project = e5App().getObject('Project')
            if dname == project.getProjectPath():
                path = ""
            else:
                path = project.getRelativePath(dname)
            if path:
                path += "/"
            for fname in fnames:
                args.append(path + fname)
        else:
            if isinstance(fn, list):
                dname, fnames = self.vcs.splitPathList(fn)
                self.vcs.addArguments(args, fnames)
            else:
                dname, fname = self.vcs.splitPath(fn)
                args.append(fname)
        
        self.process.setWorkingDirectory(dname)
        
        self.process.start('svn', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('svn'))
        
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        self.refreshButton.setEnabled(True)
        
        if self.paras == 0:
            self.contents.setPlainText(self.tr('There is no difference.'))
            
        self.buttonBox.button(QDialogButtonBox.Save).setEnabled(self.paras > 0)
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        tc = self.contents.textCursor()
        tc.movePosition(QTextCursor.Start)
        self.contents.setTextCursor(tc)
        self.contents.ensureCursorVisible()
        
        self.filesCombo.addItem(self.tr("<Start>"), 0)
        self.filesCombo.addItem(self.tr("<End>"), -1)
        for oldFile, newFile, pos in sorted(self.__fileSeparators):
            if oldFile != newFile:
                self.filesCombo.addItem(
                    "{0}\n{1}".format(oldFile, newFile), pos)
            else:
                self.filesCombo.addItem(oldFile, pos)
        
    def __appendText(self, txt):
        """
        Private method to append text to the end of the contents pane.
        
        @param txt text to insert (string)
        """
        tc = self.contents.textCursor()
        tc.movePosition(QTextCursor.End)
        self.contents.setTextCursor(tc)
        self.contents.insertPlainText(txt)
        
    def __extractFileName(self, line):
        """
        Private method to extract the file name out of a file separator line.
        
        @param line line to be processed (string)
        @return extracted file name (string)
        """
        f = line.split(None, 1)[1]
        f = f.rsplit(None, 2)[0]
        return f
    
    def __processFileLine(self, line):
        """
        Private slot to process a line giving the old/new file.
        
        @param line line to be processed (string)
        """
        if line.startswith('---'):
            self.__oldFileLine = self.paras
            self.__oldFile = self.__extractFileName(line)
        else:
            self.__fileSeparators.append(
                (self.__oldFile, self.__extractFileName(line),
                 self.__oldFileLine))
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            line = str(self.process.readLine(),
                       Preferences.getSystem("IOEncoding"),
                       'replace')
            if self.summaryPath:
                line = line.replace(self.summaryPath + '/', '')
                line = " ".join(line.split())
            if line.startswith("--- ") or line.startswith("+++ "):
                    self.__processFileLine(line)
                
            self.__appendText(line)
            self.paras += 1
        
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            self.errorGroup.show()
            s = str(self.process.readAllStandardError(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Save):
            self.on_saveButton_clicked()
        elif button == self.refreshButton:
            self.on_refreshButton_clicked()
    
    @pyqtSlot(int)
    def on_filesCombo_activated(self, index):
        """
        Private slot to handle the selection of a file.
        
        @param index activated row (integer)
        """
        para = self.filesCombo.itemData(index)
        
        if para == 0:
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.Start)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
        elif para == -1:
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.End)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
        else:
            # step 1: move cursor to end
            tc = self.contents.textCursor()
            tc.movePosition(QTextCursor.End)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
            
            # step 2: move cursor to desired line
            tc = self.contents.textCursor()
            delta = tc.blockNumber() - para
            tc.movePosition(QTextCursor.PreviousBlock, QTextCursor.MoveAnchor,
                            delta)
            self.contents.setTextCursor(tc)
            self.contents.ensureCursorVisible()
    
    @pyqtSlot()
    def on_saveButton_clicked(self):
        """
        Private slot to handle the Save button press.
        
        It saves the diff shown in the dialog to a file in the local
        filesystem.
        """
        if isinstance(self.filename, list):
            if len(self.filename) > 1:
                fname = self.vcs.splitPathList(self.filename)[0]
            else:
                dname, fname = self.vcs.splitPath(self.filename[0])
                if fname != '.':
                    fname = "{0}.diff".format(self.filename[0])
                else:
                    fname = dname
        else:
            fname = self.vcs.splitPath(self.filename)[0]
        
        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            self,
            self.tr("Save Diff"),
            fname,
            self.tr("Patch Files (*.diff)"),
            None,
            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
        
        if not fname:
            return  # user aborted
        
        ext = QFileInfo(fname).suffix()
        if not ext:
            ex = selectedFilter.split("(*")[1].split(")")[0]
            if ex:
                fname += ex
        if QFileInfo(fname).exists():
            res = E5MessageBox.yesNo(
                self,
                self.tr("Save Diff"),
                self.tr("<p>The patch file <b>{0}</b> already exists."
                        " Overwrite it?</p>").format(fname),
                icon=E5MessageBox.Warning)
            if not res:
                return
        fname = Utilities.toNativeSeparators(fname)
        
        eol = e5App().getObject("Project").getEolString()
        try:
            f = open(fname, "w", encoding="utf-8", newline="")
            f.write(eol.join(self.contents.toPlainText().splitlines()))
            f.close()
        except IOError as why:
            E5MessageBox.critical(
                self, self.tr('Save Diff'),
                self.tr(
                    '<p>The patch file <b>{0}</b> could not be saved.'
                    '<br>Reason: {1}</p>')
                .format(fname, str(why)))
        
    @pyqtSlot()
    def on_refreshButton_clicked(self):
        """
        Private slot to refresh the display.
        """
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        
        self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
        self.refreshButton.setEnabled(False)
        
        self.start(self.filename, refreshable=True)
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
        
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
        
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
        
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(SvnDiffDialog, self).keyPressEvent(evt)
コード例 #49
0
class GitRemoteRepositoriesDialog(QWidget, Ui_GitRemoteRepositoriesDialog):
    """
    Class implementing a dialog to show available remote repositories.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(GitRemoteRepositoriesDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.vcs = vcs
        self.process = QProcess()
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.refreshButton = self.buttonBox.addButton(
            self.tr("Refresh"), QDialogButtonBox.ActionRole)
        self.refreshButton.setToolTip(
            self.tr("Press to refresh the repositories display"))
        self.refreshButton.setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.__lastColumn = self.repolist.columnCount()
        
        self.repolist.headerItem().setText(self.__lastColumn, "")
        self.repolist.header().setSortIndicator(0, Qt.AscendingOrder)
        
        self.__ioEncoding = Preferences.getSystem("IOEncoding")
    
    def __resort(self):
        """
        Private method to resort the list.
        """
        self.repolist.sortItems(
            self.repolist.sortColumn(),
            self.repolist.header().sortIndicatorOrder())
    
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.repolist.header().resizeSections(QHeaderView.ResizeToContents)
        self.repolist.header().setStretchLastSection(True)
    
    def __generateItem(self, name, url, oper):
        """
        Private method to generate a status item in the status list.
        
        @param name name of the remote repository (string)
        @param url URL of the remote repository (string)
        @param oper operation the remote repository may be used for (string)
        """
        foundItems = self.repolist.findItems(name, Qt.MatchExactly, 0)
        if foundItems:
            # modify the operations column only
            foundItems[0].setText(
                2, "{0} + {1}".format(foundItems[0].text(2), oper))
        else:
            QTreeWidgetItem(self.repolist, [name, url, oper])
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        e.accept()
    
    def start(self, projectDir):
        """
        Public slot to start the git remote command.
        
        @param projectDir name of the project directory (string)
        """
        self.repolist.clear()
        
        self.errorGroup.hide()
        self.intercept = False
        self.projectDir = projectDir
        
        self.__ioEncoding = Preferences.getSystem("IOEncoding")
        
        self.removeButton.setEnabled(False)
        self.renameButton.setEnabled(False)
        self.pruneButton.setEnabled(False)
        self.showInfoButton.setEnabled(False)
        
        args = self.vcs.initCommand("remote")
        args.append('--verbose')
        
        # find the root of the repo
        repodir = self.projectDir
        while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
            repodir = os.path.dirname(repodir)
            if os.path.splitdrive(repodir)[1] == os.sep:
                return
        
        self.process.kill()
        self.process.setWorkingDirectory(repodir)
        
        self.process.start('git', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('git'))
        else:
            self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
            self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
            self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
            
            self.inputGroup.setEnabled(True)
            self.inputGroup.show()
            self.refreshButton.setEnabled(False)
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        self.refreshButton.setEnabled(True)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        self.__resort()
        self.__resizeColumns()
        
        self.__updateButtons()
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__finish()
        elif button == self.refreshButton:
            self.on_refreshButton_clicked()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        if self.process is not None:
            self.process.setReadChannel(QProcess.StandardOutput)
            
            while self.process.canReadLine():
                line = str(self.process.readLine(), self.__ioEncoding,
                           'replace').strip()
                
                name, line = line.split(None, 1)
                url, oper = line.rsplit(None, 1)
                oper = oper[1:-1]   # it is enclosed in ()
                self.__generateItem(name, url, oper)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.__ioEncoding, 'replace')
            self.errorGroup.show()
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the git process.
        """
        inputTxt = self.input.text()
        inputTxt += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(inputTxt)
            self.errors.ensureCursorVisible()
        
        self.process.write(inputTxt)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(GitRemoteRepositoriesDialog, self).keyPressEvent(evt)
    
    @pyqtSlot()
    def on_refreshButton_clicked(self):
        """
        Private slot to refresh the status display.
        """
        self.start(self.projectDir)
    
    def __updateButtons(self):
        """
        Private method to update the buttons status.
        """
        enable = len(self.repolist.selectedItems()) == 1
        
        self.removeButton.setEnabled(enable)
        self.renameButton.setEnabled(enable)
        self.pruneButton.setEnabled(enable)
        self.showInfoButton.setEnabled(enable)
    
    @pyqtSlot()
    def on_repolist_itemSelectionChanged(self):
        """
        Private slot to act upon changes of selected items.
        """
        self.__updateButtons()
    
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to add a remote repository.
        """
        self.vcs.gitAddRemote(self.projectDir)
        self.on_refreshButton_clicked()
    
    @pyqtSlot()
    def on_renameButton_clicked(self):
        """
        Private slot to rename a remote repository.
        """
        remoteName = self.repolist.selectedItems()[0].text(0)
        self.vcs.gitRenameRemote(self.projectDir, remoteName)
        self.on_refreshButton_clicked()
    
    @pyqtSlot()
    def on_removeButton_clicked(self):
        """
        Private slot to remove a remote repository.
        """
        remoteName = self.repolist.selectedItems()[0].text(0)
        self.vcs.gitRemoveRemote(self.projectDir, remoteName)
        self.on_refreshButton_clicked()
    
    @pyqtSlot()
    def on_showInfoButton_clicked(self):
        """
        Private slot to show information about a remote repository.
        """
        remoteName = self.repolist.selectedItems()[0].text(0)
        self.vcs.gitShowRemote(self.projectDir, remoteName)
    
    @pyqtSlot()
    def on_pruneButton_clicked(self):
        """
        Private slot to prune all stale remote-tracking branches.
        """
        remoteName = self.repolist.selectedItems()[0].text(0)
        self.vcs.gitPruneRemote(self.projectDir, remoteName)
コード例 #50
0
class HgShelveBrowserDialog(QWidget, Ui_HgShelveBrowserDialog):
    """
    Class implementing Mercurial shelve browser dialog.
    """
    NameColumn = 0
    AgeColumn = 1
    MessageColumn = 2
    
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(HgShelveBrowserDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.__position = QPoint()
        
        self.__fileStatisticsRole = Qt.UserRole
        self.__totalStatisticsRole = Qt.UserRole + 1
        
        self.shelveList.header().setSortIndicator(0, Qt.AscendingOrder)
        
        self.refreshButton = self.buttonBox.addButton(
            self.tr("&Refresh"), QDialogButtonBox.ActionRole)
        self.refreshButton.setToolTip(
            self.tr("Press to refresh the list of shelves"))
        self.refreshButton.setEnabled(False)
        
        self.vcs = vcs
        self.__hgClient = vcs.getClient()
        self.__resetUI()
        
        if self.__hgClient:
            self.process = None
        else:
            self.process = QProcess()
            self.process.finished.connect(self.__procFinished)
            self.process.readyReadStandardOutput.connect(self.__readStdout)
            self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.__contextMenu = QMenu()
        self.__unshelveAct = self.__contextMenu.addAction(
            self.tr("Restore selected shelve"), self.__unshelve)
        self.__deleteAct = self.__contextMenu.addAction(
            self.tr("Delete selected shelves"), self.__deleteShelves)
        self.__contextMenu.addAction(
            self.tr("Delete all shelves"), self.__cleanupShelves)
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.__hgClient:
            if self.__hgClient.isExecuting():
                self.__hgClient.cancel()
        else:
            if self.process is not None and \
               self.process.state() != QProcess.NotRunning:
                self.process.terminate()
                QTimer.singleShot(2000, self.process.kill)
                self.process.waitForFinished(3000)
        
        self.__position = self.pos()
        
        e.accept()
    
    def show(self):
        """
        Public slot to show the dialog.
        """
        if not self.__position.isNull():
            self.move(self.__position)
        self.__resetUI()
        
        super(HgShelveBrowserDialog, self).show()
    
    def __resetUI(self):
        """
        Private method to reset the user interface.
        """
        self.shelveList.clear()
    
    def __resizeColumnsShelves(self):
        """
        Private method to resize the shelve list columns.
        """
        self.shelveList.header().resizeSections(QHeaderView.ResizeToContents)
        self.shelveList.header().setStretchLastSection(True)
    
    def __generateShelveEntry(self, name, age, message, fileStatistics,
                              totals):
        """
        Private method to generate the shelve items.
        
        @param name name of the shelve (string)
        @param age age of the shelve (string)
        @param message shelve message (string)
        @param fileStatistics per file change statistics (tuple of
            four strings with file name, number of changes, number of
            added lines and number of deleted lines)
        @param totals overall statistics (tuple of three strings with
            number of changed files, number of added lines and number
            of deleted lines)
        """
        itm = QTreeWidgetItem(self.shelveList, [name, age, message])
        itm.setData(0, self.__fileStatisticsRole, fileStatistics)
        itm.setData(0, self.__totalStatisticsRole, totals)
    
    def __getShelveEntries(self):
        """
        Private method to retrieve the list of shelves.
        """
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        QApplication.processEvents()
        
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        QApplication.processEvents()
        
        self.buf = []
        self.errors.clear()
        self.intercept = False
        
        args = self.vcs.initCommand("shelve")
        args.append("--list")
        args.append("--stat")
        
        if self.__hgClient:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            
            out, err = self.__hgClient.runcommand(args)
            self.buf = out.splitlines(True)
            if err:
                self.__showError(err)
            self.__processBuffer()
            self.__finish()
        else:
            self.process.kill()
            
            self.process.setWorkingDirectory(self.repodir)
            
            self.inputGroup.setEnabled(True)
            self.inputGroup.show()
            
            self.process.start('hg', args)
            procStarted = self.process.waitForStarted(5000)
            if not procStarted:
                self.inputGroup.setEnabled(False)
                self.inputGroup.hide()
                E5MessageBox.critical(
                    self,
                    self.tr('Process Generation Error'),
                    self.tr(
                        'The process {0} could not be started. '
                        'Ensure, that it is in the search path.'
                    ).format('hg'))
    
    def start(self, projectDir):
        """
        Public slot to start the hg shelve command.
        
        @param projectDir name of the project directory (string)
        """
        self.errorGroup.hide()
        QApplication.processEvents()
        
        self.__projectDir = projectDir
        
        # find the root of the repo
        self.repodir = self.__projectDir
        while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)):
            self.repodir = os.path.dirname(self.repodir)
            if os.path.splitdrive(self.repodir)[1] == os.sep:
                return
        
        self.activateWindow()
        self.raise_()
        
        self.shelveList.clear()
        self.__started = True
        self.__getShelveEntries()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__processBuffer()
        self.__finish()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        QApplication.restoreOverrideCursor()
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        self.refreshButton.setEnabled(True)
    
    def __processBuffer(self):
        """
        Private method to process the buffered output of the hg shelve command.
        """
        lastWasFileStats = False
        firstLine = True
        itemData = {}
        for line in self.buf:
            if firstLine:
                name, line = line.split("(", 1)
                age, message = line.split(")", 1)
                itemData["name"] = name.strip()
                itemData["age"] = age.strip()
                itemData["message"] = message.strip()
                itemData["files"] = []
                firstLine = False
            elif '|' in line:
                # file stats: foo.py |  3 ++-
                file, changes = line.strip().split("|", 1)
                if changes.strip().endswith(("+", "-")):
                    total, addDelete = changes.strip().split(None, 1)
                    additions = str(addDelete.count("+"))
                    deletions = str(addDelete.count("-"))
                else:
                    total = changes.strip()
                    additions = '0'
                    deletions = '0'
                itemData["files"].append((file, total, additions, deletions))
                lastWasFileStats = True
            elif lastWasFileStats:
                # summary line
                # 2 files changed, 15 insertions(+), 1 deletions(-)
                total, added, deleted = line.strip().split(",", 2)
                total = total.split()[0]
                added = added.split()[0]
                deleted = deleted.split()[0]
                itemData["summary"] = (total, added, deleted)
                
                self.__generateShelveEntry(
                    itemData["name"], itemData["age"], itemData["message"],
                    itemData["files"], itemData["summary"])
                
                lastWasFileStats = False
                firstLine = True
                itemData = {}
        
        self.__resizeColumnsShelves()
        
        if self.__started:
            self.shelveList.setCurrentItem(self.shelveList.topLevelItem(0))
            self.__started = False
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process and inserts it into a buffer.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            line = str(self.process.readLine(), self.vcs.getEncoding(),
                       'replace')
            self.buf.append(line)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    self.vcs.getEncoding(), 'replace')
            self.__showError(s)
    
    def __showError(self, out):
        """
        Private slot to show some error.
        
        @param out error to be shown (string)
        """
        self.errorGroup.show()
        self.errors.insertPlainText(out)
        self.errors.ensureCursorVisible()
    
    @pyqtSlot(QAbstractButton)
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.cancelled = True
            if self.__hgClient:
                self.__hgClient.cancel()
            else:
                self.__finish()
        elif button == self.refreshButton:
            self.on_refreshButton_clicked()
    
    @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
    def on_shelveList_currentItemChanged(self, current, previous):
        """
        Private slot called, when the current item of the shelve list changes.
        
        @param current reference to the new current item (QTreeWidgetItem)
        @param previous reference to the old current item (QTreeWidgetItem)
        """
        self.statisticsList.clear()
        if current:
            for dataSet in current.data(0, self.__fileStatisticsRole):
                QTreeWidgetItem(self.statisticsList, list(dataSet))
            self.statisticsList.header().resizeSections(
                QHeaderView.ResizeToContents)
            self.statisticsList.header().setStretchLastSection(True)
            
            totals = current.data(0, self.__totalStatisticsRole)
            self.filesLabel.setText(
                self.tr("%n file(s) changed", None, int(totals[0])))
            self.insertionsLabel.setText(
                self.tr("%n line(s) inserted", None, int(totals[1])))
            self.deletionsLabel.setText(
                self.tr("%n line(s) deleted", None, int(totals[2])))
        else:
            self.filesLabel.setText("")
            self.insertionsLabel.setText("")
            self.deletionsLabel.setText("")
    
    @pyqtSlot(QPoint)
    def on_shelveList_customContextMenuRequested(self, pos):
        """
        Private slot to show the context menu of the shelve list.
        
        @param pos position of the mouse pointer (QPoint)
        """
        selectedItemsCount = len(self.shelveList.selectedItems())
        self.__unshelveAct.setEnabled(selectedItemsCount == 1)
        self.__deleteAct.setEnabled(selectedItemsCount > 0)
        
        self.__contextMenu.popup(self.mapToGlobal(pos))
    
    @pyqtSlot()
    def on_refreshButton_clicked(self):
        """
        Private slot to refresh the list of shelves.
        """
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.inputGroup.setEnabled(True)
        self.inputGroup.show()
        self.refreshButton.setEnabled(False)
        
        self.start(self.__projectDir)
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the mercurial process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        self.errorGroup.show()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(HgShelveBrowserDialog, self).keyPressEvent(evt)
    
    def __unshelve(self):
        """
        Private slot to restore the selected shelve of changes.
        """
        itm = self.shelveList.selectedItems()[0]
        if itm is not None:
            name = itm.text(self.NameColumn)
            self.vcs.getExtensionObject("shelve")\
                .hgUnshelve(self.__projectDir, shelveName=name)
            self.on_refreshButton_clicked()
    
    def __deleteShelves(self):
        """
        Private slot to delete the selected shelves.
        """
        shelveNames = []
        for itm in self.shelveList.selectedItems():
            shelveNames.append(itm.text(self.NameColumn))
        if shelveNames:
            self.vcs.getExtensionObject("shelve")\
                .hgDeleteShelves(self.__projectDir, shelveNames=shelveNames)
            self.on_refreshButton_clicked()
    
    def __cleanupShelves(self):
        """
        Private slot to delete all shelves.
        """
        self.vcs.getExtensionObject("shelve")\
            .hgCleanupShelves(self.__projectDir)
        self.on_refreshButton_clicked()
コード例 #51
0
ファイル: SvnPropListDialog.py プロジェクト: Darriall/eric
class SvnPropListDialog(QWidget, Ui_SvnPropListDialog):
    """
    Class implementing a dialog to show the output of the svn proplist command
    process.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(SvnPropListDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.process = QProcess()
        env = QProcessEnvironment.systemEnvironment()
        env.insert("LANG", "C")
        self.process.setProcessEnvironment(env)
        self.vcs = vcs
        
        self.propsList.headerItem().setText(self.propsList.columnCount(), "")
        self.propsList.header().setSortIndicator(0, Qt.AscendingOrder)
        
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.rx_path = QRegExp(r"Properties on '([^']+)':\s*")
        self.rx_prop = QRegExp(r"  (.*) *: *(.*)[\r\n]")
        self.lastPath = None
        self.lastProp = None
        self.propBuffer = ""
        
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.propsList.sortItems(
            self.propsList.sortColumn(),
            self.propsList.header().sortIndicatorOrder())
        
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.propsList.header().resizeSections(QHeaderView.ResizeToContents)
        self.propsList.header().setStretchLastSection(True)
        
    def __generateItem(self, path, propName, propValue):
        """
        Private method to generate a properties item in the properties list.
        
        @param path file/directory name the property applies to (string)
        @param propName name of the property (string)
        @param propValue value of the property (string)
        """
        QTreeWidgetItem(self.propsList, [path, propName, propValue.strip()])
        
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        e.accept()
        
    def start(self, fn, recursive=False):
        """
        Public slot to start the svn status command.
        
        @param fn filename(s) (string or list of string)
        @param recursive flag indicating a recursive list is requested
        """
        self.errorGroup.hide()
        
        self.process.kill()
        
        args = []
        args.append('proplist')
        self.vcs.addArguments(args, self.vcs.options['global'])
        args.append('--verbose')
        if recursive:
            args.append('--recursive')
        if isinstance(fn, list):
            dname, fnames = self.vcs.splitPathList(fn)
            self.vcs.addArguments(args, fnames)
        else:
            dname, fname = self.vcs.splitPath(fn)
            args.append(fname)
        
        self.process.setWorkingDirectory(dname)
        
        self.process.start('svn', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('svn'))
        
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed the
        button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.process = None
        if self.lastProp:
            self.__generateItem(self.lastPath, self.lastProp, self.propBuffer)
        
        self.__resort()
        self.__resizeColumns()
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__finish()
        
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        if self.lastPath is None:
            self.__generateItem('', 'None', '')
        
        self.__finish()
        
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            if self.rx_path.exactMatch(s):
                if self.lastProp:
                    self.__generateItem(
                        self.lastPath, self.lastProp, self.propBuffer)
                self.lastPath = self.rx_path.cap(1)
                self.lastProp = None
                self.propBuffer = ""
            elif self.rx_prop.exactMatch(s):
                if self.lastProp:
                    self.__generateItem(
                        self.lastPath, self.lastProp, self.propBuffer)
                self.lastProp = self.rx_prop.cap(1)
                self.propBuffer = self.rx_prop.cap(2)
            else:
                self.propBuffer += ' '
                self.propBuffer += s
        
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            self.errorGroup.show()
            s = str(self.process.readAllStandardError(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
コード例 #52
0
ファイル: HgClient.py プロジェクト: Darriall/eric
class HgClient(QObject):
    """
    Class implementing the Mercurial command server interface.
    """
    InputFormat = ">I"
    OutputFormat = ">cI"
    OutputFormatSize = struct.calcsize(OutputFormat)
    ReturnFormat = ">i"
    
    Channels = (b"I", b"L", b"o", b"e", b"r", b"d")
    
    def __init__(self, repoPath, encoding, vcs, parent=None):
        """
        Constructor
        
        @param repoPath root directory of the repository (string)
        @param encoding encoding to be used by the command server (string)
        @param vcs reference to the VCS object (Hg)
        @param parent reference to the parent object (QObject)
        """
        super(HgClient, self).__init__(parent)
        
        self.__server = None
        self.__started = False
        self.__version = None
        self.__encoding = vcs.getEncoding()
        self.__cancel = False
        self.__commandRunning = False
        self.__repoPath = repoPath
        
        # generate command line and environment
        self.__serverArgs = vcs.initCommand("serve")
        self.__serverArgs.append("--cmdserver")
        self.__serverArgs.append("pipe")
        self.__serverArgs.append("--config")
        self.__serverArgs.append("ui.interactive=True")
        if repoPath:
            self.__serverArgs.append("--repository")
            self.__serverArgs.append(repoPath)
        
        if encoding:
            self.__encoding = encoding
            if "--encoding" in self.__serverArgs:
                # use the defined encoding via the environment
                index = self.__serverArgs.index("--encoding")
                del self.__serverArgs[index:index + 2]
    
    def startServer(self):
        """
        Public method to start the command server.
        
        @return tuple of flag indicating a successful start (boolean) and
            an error message (string) in case of failure
        """
        self.__server = QProcess()
        self.__server.setWorkingDirectory(self.__repoPath)
        
        # connect signals
        self.__server.finished.connect(self.__serverFinished)
        
        prepareProcess(self.__server, self.__encoding)
        
        self.__server.start('hg', self.__serverArgs)
        serverStarted = self.__server.waitForStarted(5000)
        if not serverStarted:
            return False, self.tr(
                'The process {0} could not be started. '
                'Ensure, that it is in the search path.'
            ).format('hg')
        
        self.__server.setReadChannel(QProcess.StandardOutput)
        ok, error = self.__readHello()
        self.__started = ok
        return ok, error
    
    def stopServer(self):
        """
        Public method to stop the command server.
        """
        if self.__server is not None:
            self.__server.closeWriteChannel()
            res = self.__server.waitForFinished(5000)
            if not res:
                self.__server.terminate()
                res = self.__server.waitForFinished(3000)
                if not res:
                    self.__server.kill()
                    self.__server.waitForFinished(3000)
            
            self.__started = False
            self.__server.deleteLater()
            self.__server = None
    
    def restartServer(self):
        """
        Public method to restart the command server.
        
        @return tuple of flag indicating a successful start (boolean) and
            an error message (string) in case of failure
        """
        self.stopServer()
        return self.startServer()
    
    def __readHello(self):
        """
        Private method to read the hello message sent by the command server.
        
        @return tuple of flag indicating success (boolean) and an error message
            in case of failure (string)
        """
        ch, msg = self.__readChannel()
        if not ch:
            return False, self.tr("Did not receive the 'hello' message.")
        elif ch != "o":
            return False, self.tr("Received data on unexpected channel.")
        
        msg = msg.split("\n")
        
        if not msg[0].startswith("capabilities: "):
            return False, self.tr(
                "Bad 'hello' message, expected 'capabilities: '"
                " but got '{0}'.").format(msg[0])
        self.__capabilities = msg[0][len('capabilities: '):]
        if not self.__capabilities:
            return False, self.tr("'capabilities' message did not contain"
                                  " any capability.")
        
        self.__capabilities = set(self.__capabilities.split())
        if "runcommand" not in self.__capabilities:
            return False, "'capabilities' did not contain 'runcommand'."
        
        if not msg[1].startswith("encoding: "):
            return False, self.tr(
                "Bad 'hello' message, expected 'encoding: '"
                " but got '{0}'.").format(msg[1])
        encoding = msg[1][len('encoding: '):]
        if not encoding:
            return False, self.tr("'encoding' message did not contain"
                                  " any encoding.")
        self.__encoding = encoding
        
        return True, ""
    
    def __serverFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__started = False
    
    def __readChannel(self):
        """
        Private method to read data from the command server.
        
        @return tuple of channel designator and channel data
            (string, integer or string or bytes)
        """
        if self.__server.bytesAvailable() > 0 or \
           self.__server.waitForReadyRead(10000):
            data = bytes(self.__server.peek(HgClient.OutputFormatSize))
            if not data or len(data) < HgClient.OutputFormatSize:
                return "", ""
            
            channel, length = struct.unpack(HgClient.OutputFormat, data)
            channel = channel.decode(self.__encoding)
            if channel in "IL":
                self.__server.read(HgClient.OutputFormatSize)
                return channel, length
            else:
                if self.__server.bytesAvailable() < \
                        HgClient.OutputFormatSize + length:
                    return "", ""
                self.__server.read(HgClient.OutputFormatSize)
                data = self.__server.read(length)
                if channel == "r":
                    return (channel, data)
                else:
                    return (channel, str(data, self.__encoding, "replace"))
        else:
            return "", ""
    
    def __writeDataBlock(self, data):
        """
        Private slot to write some data to the command server.
        
        @param data data to be sent (string)
        """
        if not isinstance(data, bytes):
            data = data.encode(self.__encoding)
        self.__server.write(
            QByteArray(struct.pack(HgClient.InputFormat, len(data))))
        self.__server.write(QByteArray(data))
        self.__server.waitForBytesWritten()
    
    def __runcommand(self, args, inputChannels, outputChannels):
        """
        Private method to run a command in the server (low level).
        
        @param args list of arguments for the command (list of string)
        @param inputChannels dictionary of input channels. The dictionary must
            have the keys 'I' and 'L' and each entry must be a function
            receiving the number of bytes to write.
        @param outputChannels dictionary of output channels. The dictionary
            must have the keys 'o' and 'e' and each entry must be a function
            receiving the data.
        @return result code of the command, -1 if the command server wasn't
            started or -10, if the command was canceled (integer)
        @exception RuntimeError raised to indicate an unexpected command
            channel
        """
        if not self.__started:
            return -1
        
        self.__server.write(QByteArray(b'runcommand\n'))
        self.__writeDataBlock('\0'.join(args))
        
        while True:
            QCoreApplication.processEvents()
            
            if self.__cancel:
                return -10
            
            if self.__server is None:
                return -1
            
            if self.__server is None or self.__server.bytesAvailable() == 0:
                QThread.msleep(50)
                continue
            channel, data = self.__readChannel()
            
            # input channels
            if channel in inputChannels:
                input = inputChannels[channel](data)
                if channel == "L":
                    # echo the input to the output if it was a prompt
                    outputChannels["o"](input)
                self.__writeDataBlock(input)
            
            # output channels
            elif channel in outputChannels:
                outputChannels[channel](data)
            
            # result channel, command is finished
            elif channel == "r":
                return struct.unpack(HgClient.ReturnFormat, data)[0]
            
            # unexpected but required channel
            elif channel.isupper():
                raise RuntimeError(
                    "Unexpected but required channel '{0}'.".format(channel))
            
            # optional channels or no channel at all
            else:
                pass
    
    def __prompt(self, size, message):
        """
        Private method to prompt the user for some input.
        
        @param size maximum length of the requested input (integer)
        @param message message sent by the server (string)
        @return data entered by the user (string)
        """
        from .HgClientPromptDialog import HgClientPromptDialog
        input = ""
        dlg = HgClientPromptDialog(size, message)
        if dlg.exec_() == QDialog.Accepted:
            input = dlg.getInput() + '\n'
        return input
    
    def runcommand(self, args, prompt=None, input=None, output=None,
                   error=None):
        """
        Public method to execute a command via the command server.
        
        @param args list of arguments for the command (list of string)
        @keyparam prompt function to reply to prompts by the server. It
            receives the max number of bytes to return and the contents
            of the output channel received so far.
        @keyparam input function to reply to bulk data requests by the server.
            It receives the max number of bytes to return.
        @keyparam output function receiving the data from the server (string).
            If a prompt function is given, this parameter will be ignored.
        @keyparam error function receiving error messages from the server
            (string)
        @return output and errors of the command server (string). In case
            output and/or error functions were given, the respective return
            value will be an empty string.
        """
        self.__commandRunning = True
        outputChannels = {}
        outputBuffer = None
        errorBuffer = None
        
        if prompt is not None or output is None:
            outputBuffer = io.StringIO()
            outputChannels["o"] = outputBuffer.write
        else:
            outputChannels["o"] = output
        if error:
            outputChannels["e"] = error
        else:
            errorBuffer = io.StringIO()
            outputChannels["e"] = errorBuffer.write
        
        inputChannels = {}
        if prompt is not None:
            def func(size):
                reply = prompt(size, outputBuffer.getvalue())
                return reply
            inputChannels["L"] = func
        else:
            def myprompt(size):
                if outputBuffer is None:
                    msg = self.tr("For message see output dialog.")
                else:
                    msg = outputBuffer.getvalue()
                reply = self.__prompt(size, msg)
                return reply
            inputChannels["L"] = myprompt
        if input is not None:
            inputChannels["I"] = input
        
        self.__cancel = False
        self.__runcommand(args, inputChannels, outputChannels)
        if outputBuffer:
            out = outputBuffer.getvalue()
        else:
            out = ""
        if errorBuffer:
            err = errorBuffer.getvalue()
        else:
            err = ""
        
        self.__commandRunning = False
        
        return out, err
    
    def cancel(self):
        """
        Public method to cancel the running command.
        """
        self.__cancel = True
        self.restartServer()
    
    def wasCanceled(self):
        """
        Public method to check, if the last command was canceled.
        
        @return flag indicating the cancel state (boolean)
        """
        return self.__cancel
    
    def isExecuting(self):
        """
        Public method to check, if the server is executing a command.
        
        @return flag indicating the execution of a command (boolean)
        """
        return self.__commandRunning
コード例 #53
0
class SvnTagBranchListDialog(QDialog, Ui_SvnTagBranchListDialog):
    """
    Class implementing a dialog to show a list of tags or branches.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(SvnTagBranchListDialog, self).__init__(parent)
        self.setupUi(self)

        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)

        self.process = QProcess()
        self.vcs = vcs
        self.tagsList = None
        self.allTagsList = None

        self.tagList.headerItem().setText(self.tagList.columnCount(), "")
        self.tagList.header().setSortIndicator(3, Qt.AscendingOrder)

        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)

        self.rx_list = QRegExp(
            r"""\w*\s*(\d+)\s+(\w+)\s+\d*\s*"""
            r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)/\s*""")

    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)

        e.accept()

    def start(self, path, tags, tagsList, allTagsList):
        """
        Public slot to start the svn status command.
        
        @param path name of directory to be listed (string)
        @param tags flag indicating a list of tags is requested
            (False = branches, True = tags)
        @param tagsList reference to string list receiving the tags
            (list of strings)
        @param allTagsList reference to string list all tags (list of strings)
        """
        self.errorGroup.hide()

        self.intercept = False
        if not tags:
            self.setWindowTitle(self.tr("Subversion Branches List"))
        self.activateWindow()

        self.tagsList = tagsList
        self.allTagsList = allTagsList
        dname, fname = self.vcs.splitPath(path)

        self.process.kill()

        reposURL = self.vcs.svnGetReposName(dname)
        if reposURL is None:
            E5MessageBox.critical(
                self, self.tr("Subversion Error"),
                self.tr(
                    """The URL of the project repository could not be"""
                    """ retrieved from the working copy. The list operation"""
                    """ will be aborted"""))
            self.close()
            return

        args = []
        args.append('list')
        self.vcs.addArguments(args, self.vcs.options['global'])
        args.append('--verbose')

        if self.vcs.otherData["standardLayout"]:
            # determine the base path of the project in the repository
            rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
            if not rx_base.exactMatch(reposURL):
                E5MessageBox.critical(
                    self, self.tr("Subversion Error"),
                    self.tr("""The URL of the project repository has an"""
                            """ invalid format. The list operation will"""
                            """ be aborted"""))
                return

            reposRoot = rx_base.cap(1)

            if tags:
                args.append("{0}/tags".format(reposRoot))
            else:
                args.append("{0}/branches".format(reposRoot))
            self.path = None
        else:
            reposPath, ok = QInputDialog.getText(
                self, self.tr("Subversion List"),
                self.tr("Enter the repository URL containing the tags"
                        " or branches"), QLineEdit.Normal,
                self.vcs.svnNormalizeURL(reposURL))
            if not ok:
                self.close()
                return
            if not reposPath:
                E5MessageBox.critical(
                    self, self.tr("Subversion List"),
                    self.tr("""The repository URL is empty."""
                            """ Aborting..."""))
                self.close()
                return
            args.append(reposPath)
            self.path = reposPath

        self.process.setWorkingDirectory(dname)

        self.process.start('svn', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            E5MessageBox.critical(
                self, self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.').format('svn'))
        else:
            self.inputGroup.setEnabled(True)
            self.inputGroup.show()

    def __finish(self):
        """
        Private slot called when the process finished or the user pressed the
        button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)

        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)

        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()

        self.process = None

        self.__resizeColumns()
        self.__resort()

    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__finish()

    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()

    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.tagList.sortItems(self.tagList.sortColumn(),
                               self.tagList.header().sortIndicatorOrder())

    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.tagList.header().resizeSections(QHeaderView.ResizeToContents)
        self.tagList.header().setStretchLastSection(True)

    def __generateItem(self, revision, author, date, name):
        """
        Private method to generate a tag item in the taglist.
        
        @param revision revision string (string)
        @param author author of the tag (string)
        @param date date of the tag (string)
        @param name name (path) of the tag (string)
        """
        itm = QTreeWidgetItem(self.tagList)
        itm.setData(0, Qt.DisplayRole, int(revision))
        itm.setData(1, Qt.DisplayRole, author)
        itm.setData(2, Qt.DisplayRole, date)
        itm.setData(3, Qt.DisplayRole, name)
        itm.setTextAlignment(0, Qt.AlignRight)

    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)

        while self.process.canReadLine():
            s = str(self.process.readLine(),
                    Preferences.getSystem("IOEncoding"), 'replace')
            if self.rx_list.exactMatch(s):
                rev = "{0:6}".format(self.rx_list.cap(1))
                author = self.rx_list.cap(2)
                date = self.rx_list.cap(3)
                path = self.rx_list.cap(4)
                if path == ".":
                    continue
                self.__generateItem(rev, author, date, path)
                if not self.vcs.otherData["standardLayout"]:
                    path = self.path + '/' + path
                if self.tagsList is not None:
                    self.tagsList.append(path)
                if self.allTagsList is not None:
                    self.allTagsList.append(path)

    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            self.errorGroup.show()
            s = str(self.process.readAllStandardError(),
                    Preferences.getSystem("IOEncoding"), 'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()

    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)

    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep

        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()

        self.process.write(input)

        self.passwordCheckBox.setChecked(False)
        self.input.clear()

    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()

    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(SvnTagBranchListDialog, self).keyPressEvent(evt)
コード例 #54
0
class SvnRepoBrowserDialog(QDialog, Ui_SvnRepoBrowserDialog):
    """
    Class implementing the subversion repository browser dialog.
    """
    def __init__(self, vcs, mode="browse", parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param mode mode of the dialog (string, "browse" or "select")
        @param parent parent widget (QWidget)
        """
        super(SvnRepoBrowserDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.Window)
        
        self.repoTree.headerItem().setText(self.repoTree.columnCount(), "")
        self.repoTree.header().setSortIndicator(0, Qt.AscendingOrder)
        
        self.vcs = vcs
        self.mode = mode
        
        self.process = QProcess()
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        if self.mode == "select":
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
            self.buttonBox.button(QDialogButtonBox.Close).hide()
        else:
            self.buttonBox.button(QDialogButtonBox.Ok).hide()
            self.buttonBox.button(QDialogButtonBox.Cancel).hide()
        
        self.__dirIcon = UI.PixmapCache.getIcon("dirClosed.png")
        self.__fileIcon = UI.PixmapCache.getIcon("fileMisc.png")
        
        self.__urlRole = Qt.UserRole
        self.__ignoreExpand = False
        self.intercept = False
        
        self.__rx_dir = QRegExp(
            r"""\s*([0-9]+)\s+(\w+)\s+"""
            r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""")
        self.__rx_file = QRegExp(
            r"""\s*([0-9]+)\s+(\w+)\s+([0-9]+)\s"""
            r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""")
    
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        e.accept()
        
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.repoTree.sortItems(
            self.repoTree.sortColumn(),
            self.repoTree.header().sortIndicatorOrder())
    
    def __resizeColumns(self):
        """
        Private method to resize the tree columns.
        """
        self.repoTree.header().resizeSections(QHeaderView.ResizeToContents)
        self.repoTree.header().setStretchLastSection(True)
    
    def __generateItem(self, repopath, revision, author, size, date,
                       nodekind, url):
        """
        Private method to generate a tree item in the repository tree.
        
        @param repopath path of the item (string)
        @param revision revision info (string)
        @param author author info (string)
        @param size size info (string)
        @param date date info (string)
        @param nodekind node kind info (string, "dir" or "file")
        @param url url of the entry (string)
        @return reference to the generated item (QTreeWidgetItem)
        """
        path = repopath
        
        if revision == "":
            rev = ""
        else:
            rev = int(revision)
        if size == "":
            sz = ""
        else:
            sz = int(size)
        
        itm = QTreeWidgetItem(self.parentItem)
        itm.setData(0, Qt.DisplayRole, path)
        itm.setData(1, Qt.DisplayRole, rev)
        itm.setData(2, Qt.DisplayRole, author)
        itm.setData(3, Qt.DisplayRole, sz)
        itm.setData(4, Qt.DisplayRole, date)
        
        if nodekind == "dir":
            itm.setIcon(0, self.__dirIcon)
            itm.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator)
        elif nodekind == "file":
            itm.setIcon(0, self.__fileIcon)
        
        itm.setData(0, self.__urlRole, url)
        
        itm.setTextAlignment(0, Qt.AlignLeft)
        itm.setTextAlignment(1, Qt.AlignRight)
        itm.setTextAlignment(2, Qt.AlignLeft)
        itm.setTextAlignment(3, Qt.AlignRight)
        itm.setTextAlignment(4, Qt.AlignLeft)
        
        return itm
    
    def __repoRoot(self, url):
        """
        Private method to get the repository root using the svn info command.
        
        @param url the repository URL to browser (string)
        @return repository root (string)
        """
        ioEncoding = Preferences.getSystem("IOEncoding")
        repoRoot = None
        
        process = QProcess()
        
        args = []
        args.append('info')
        self.vcs.addArguments(args, self.vcs.options['global'])
        args.append('--xml')
        args.append(url)
        
        process.start('svn', args)
        procStarted = process.waitForStarted(5000)
        if procStarted:
            finished = process.waitForFinished(30000)
            if finished:
                if process.exitCode() == 0:
                    output = str(process.readAllStandardOutput(), ioEncoding,
                                 'replace')
                    for line in output.splitlines():
                        line = line.strip()
                        if line.startswith('<root>'):
                            repoRoot = line.replace('<root>', '')\
                                .replace('</root>', '')
                            break
                else:
                    error = str(process.readAllStandardError(),
                                Preferences.getSystem("IOEncoding"),
                                'replace')
                    self.errors.insertPlainText(error)
                    self.errors.ensureCursorVisible()
        else:
            QApplication.restoreOverrideCursor()
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('svn'))
        return repoRoot
    
    def __listRepo(self, url, parent=None):
        """
        Private method to perform the svn list command.
        
        @param url the repository URL to browse (string)
        @param parent reference to the item, the data should be appended to
            (QTreeWidget or QTreeWidgetItem)
        """
        self.errorGroup.hide()
        
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        QApplication.processEvents()
        
        self.repoUrl = url
        
        if parent is None:
            self.parentItem = self.repoTree
        else:
            self.parentItem = parent
        
        if self.parentItem == self.repoTree:
            repoRoot = self.__repoRoot(url)
            if repoRoot is None:
                self.__finish()
                return
            self.__ignoreExpand = True
            itm = self.__generateItem(
                repoRoot, "", "", "", "", "dir", repoRoot)
            itm.setExpanded(True)
            self.parentItem = itm
            urlPart = repoRoot
            for element in url.replace(repoRoot, "").split("/"):
                if element:
                    urlPart = "{0}/{1}".format(urlPart, element)
                    itm = self.__generateItem(
                        element, "", "", "", "", "dir", urlPart)
                    itm.setExpanded(True)
                    self.parentItem = itm
            itm.setExpanded(False)
            self.__ignoreExpand = False
            self.__finish()
            return
        
        self.intercept = False
        
        self.process.kill()
        
        args = []
        args.append('list')
        self.vcs.addArguments(args, self.vcs.options['global'])
        if '--verbose' not in self.vcs.options['global']:
            args.append('--verbose')
        args.append(url)
        
        self.process.start('svn', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            self.__finish()
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('svn'))
        else:
            self.inputGroup.setEnabled(True)
            self.inputGroup.show()
    
    def __normalizeUrl(self, url):
        """
        Private method to normalite the url.
        
        @param url the url to normalize (string)
        @return normalized URL (string)
        """
        if url.endswith("/"):
            return url[:-1]
        return url
    
    def start(self, url):
        """
        Public slot to start the svn info command.
        
        @param url the repository URL to browser (string)
        """
        self.repoTree.clear()
        
        self.url = ""
        
        url = self.__normalizeUrl(url)
        if self.urlCombo.findText(url) == -1:
            self.urlCombo.addItem(url)
    
    @pyqtSlot(str)
    def on_urlCombo_currentIndexChanged(self, text):
        """
        Private slot called, when a new repository URL is entered or selected.
        
        @param text the text of the current item (string)
        """
        url = self.__normalizeUrl(text)
        if url != self.url:
            self.url = url
            self.repoTree.clear()
            self.__listRepo(url)
    
    @pyqtSlot(QTreeWidgetItem)
    def on_repoTree_itemExpanded(self, item):
        """
        Private slot called when an item is expanded.
        
        @param item reference to the item to be expanded (QTreeWidgetItem)
        """
        if not self.__ignoreExpand:
            url = item.data(0, self.__urlRole)
            self.__listRepo(url, item)
    
    @pyqtSlot(QTreeWidgetItem)
    def on_repoTree_itemCollapsed(self, item):
        """
        Private slot called when an item is collapsed.
        
        @param item reference to the item to be collapsed (QTreeWidgetItem)
        """
        for child in item.takeChildren():
            del child
    
    @pyqtSlot()
    def on_repoTree_itemSelectionChanged(self):
        """
        Private slot called when the selection changes.
        """
        if self.mode == "select":
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
    
    def accept(self):
        """
        Public slot called when the dialog is accepted.
        """
        if self.focusWidget() == self.urlCombo:
            return
        
        super(SvnRepoBrowserDialog, self).accept()
    
    def getSelectedUrl(self):
        """
        Public method to retrieve the selected repository URL.
        
        @return the selected repository URL (string)
        """
        items = self.repoTree.selectedItems()
        if len(items) == 1:
            return items[0].data(0, self.__urlRole)
        else:
            return ""
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed the
        button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.__resizeColumns()
        self.__resort()
        QApplication.restoreOverrideCursor()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        if self.process is not None:
            self.process.setReadChannel(QProcess.StandardOutput)
            
            while self.process.canReadLine():
                s = str(self.process.readLine(),
                        Preferences.getSystem("IOEncoding"),
                        'replace')
                if self.__rx_dir.exactMatch(s):
                    revision = self.__rx_dir.cap(1)
                    author = self.__rx_dir.cap(2)
                    date = self.__rx_dir.cap(3)
                    name = self.__rx_dir.cap(4).strip()
                    if name.endswith("/"):
                        name = name[:-1]
                    size = ""
                    nodekind = "dir"
                    if name == ".":
                        continue
                elif self.__rx_file.exactMatch(s):
                    revision = self.__rx_file.cap(1)
                    author = self.__rx_file.cap(2)
                    size = self.__rx_file.cap(3)
                    date = self.__rx_file.cap(4)
                    name = self.__rx_file.cap(5).strip()
                    nodekind = "file"
                else:
                    continue
                url = "{0}/{1}".format(self.repoUrl, name)
                self.__generateItem(
                    name, revision, author, size, date, nodekind, url)
   
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            s = str(self.process.readAllStandardError(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
            self.errorGroup.show()
    
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
    
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
    
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
    
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(SvnRepoBrowserDialog, self).keyPressEvent(evt)
コード例 #55
0
class MyQProcess(QWidget):
    """creates a q process for running the classification"""
    def __init__(self,text,title):
        super().__init__()

        # Add the UI components (here we use a QTextEdit to display the stdout from the process)
        self.layout = QVBoxLayout()
        self.stoppush = QPushButton('Stop')
        self.edit = QTextEdit()
        self.label =QLabel()
        self.edit.setStyleSheet('font-size:12pt')
        self.setLayout(self.layout)
        self.cmd = None
        self.setGeometry(100,100, 800, 300)
        self.setMaximumHeight(300)
        self.setMaximumWidth(800)
        self.setWindowTitle(title)
        self.edit.setText(text)
        self.label.setText(text)
        self.args =None
        #self.show()

        # Add the process and start it
        self.process = QProcess()
        self.setupProcess()
        self.stoppush.clicked.connect(self.kill)
        self.process.finished.connect(lambda: self.edit.append('Done'))

    def setupProcess(self):
        # Set the channels
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        # Connect the signal readyReadStandardOutput to the slot of the widget
        self.process.readyReadStandardOutput.connect(self.readStdOutput)

    def start_process2(self):
        """start the process with a poping Qlabel massage (used for: polygonize,rasterize,confusion matrix"""
        self.setGeometry(100, 100, 400, 300)
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.stoppush)
        # Show the widget
        # print('starting process')
        self.process.start(self.cmd)
        # print('started')
        self.show()


    def start_process(self):
        """start the process with a poping Qedit that shows all the input (used for classification running"""
        self.layout.addWidget(self.edit)
        self.layout.addWidget(self.stoppush)
        # Show the widget
        self.show()
        # print('starting process')
        self.process.start(self.cmd)
        # print('started')

    def __del__(self):
        # If QApplication is closed attempt to kill the process
        self.process.terminate()
        self.edit.clear()
        self.label.clear()
        # Wait for Xms and then elevate the situation to terminate
        if not self.process.waitForFinished(10000):
            print('killing process')
            self.process.kill()
            self.edit.clear()
            self.label.clear()

    def kill(self):
        print('killing process')
        self.process.terminate()
        self.edit.clear()
        self.label.clear()
        self.close()


    def readStdOutput(self):
        # Every time the process has something to output we attach it to the QTextEdit
        #self.edit.setText(str(self.process.readAllStandardOutput()))
        self.edit.append(str(self.process.readAllStandardOutput()))

    def main():
        app = QApplication(sys.argv)
        w = MyQProcess()

        return app.exec_()
コード例 #56
0
class Process(QObject):

    """Abstraction over a running test subprocess process.

    Reads the log from its stdout and parses it.

    Attributes:
        _invalid: A list of lines which could not be parsed.
        _data: A list of parsed lines.
        _started: Whether the process was ever started.
        proc: The QProcess for the underlying process.
        exit_expected: Whether the process is expected to quit.
        request: The request object for the current test.

    Signals:
        ready: Emitted when the server finished starting up.
        new_data: Emitted when a new line was parsed.
    """

    ready = pyqtSignal()
    new_data = pyqtSignal(object)
    KEYS = ['data']

    def __init__(self, request, parent=None):
        super().__init__(parent)
        self.request = request
        self.captured_log = []
        self._started = False
        self._invalid = []
        self._data = []
        self.proc = QProcess()
        self.proc.setReadChannel(QProcess.StandardError)
        self.exit_expected = None  # Not started at all yet

    def _log(self, line):
        """Add the given line to the captured log output."""
        if self.request.config.getoption('--capture') == 'no':
            print(line)
        self.captured_log.append(line)

    def log_summary(self, text):
        """Log the given line as summary/title."""
        text = '\n{line} {text} {line}\n'.format(line='='*30, text=text)
        self._log(text)

    def _parse_line(self, line):
        """Parse the given line from the log.

        Return:
            A self.ParseResult member.
        """
        raise NotImplementedError

    def _executable_args(self):
        """Get the executable and necessary arguments as a tuple."""
        raise NotImplementedError

    def _default_args(self):
        """Get the default arguments to use if none were passed to start()."""
        raise NotImplementedError

    def _get_data(self):
        """Get the parsed data for this test.

        Also waits for 0.5s to make sure any new data is received.

        Subprocesses are expected to alias this to a public method with a
        better name.
        """
        self.proc.waitForReadyRead(500)
        self.read_log()
        return self._data

    def _wait_signal(self, signal, timeout=5000, raising=True):
        """Wait for a signal to be emitted.

        Should be used in a contextmanager.
        """
        blocker = pytestqt.plugin.SignalBlocker(timeout=timeout,
                                                raising=raising)
        blocker.connect(signal)
        return blocker

    @pyqtSlot()
    def read_log(self):
        """Read the log from the process' stdout."""
        if not hasattr(self, 'proc'):
            # I have no idea how this happens, but it does...
            return
        while self.proc.canReadLine():
            line = self.proc.readLine()
            line = bytes(line).decode('utf-8', errors='ignore').rstrip('\r\n')

            try:
                parsed = self._parse_line(line)
            except InvalidLine:
                self._invalid.append(line)
                self._log("INVALID: {}".format(line))
                continue

            if parsed is None:
                if self._invalid:
                    self._log("IGNORED: {}".format(line))
            else:
                self._data.append(parsed)
                self.new_data.emit(parsed)

    def start(self, args=None, *, env=None):
        """Start the process and wait until it started."""
        self._start(args, env=env)
        self._started = True
        verbose = self.request.config.getoption('--verbose')

        timeout = 60 if 'CI' in os.environ else 20
        for _ in range(timeout):
            with self._wait_signal(self.ready, timeout=1000,
                                   raising=False) as blocker:
                pass

            if not self.is_running():
                if self.exit_expected:
                    return
                # _start ensures it actually started, but it might quit shortly
                # afterwards
                raise ProcessExited('\n' + _render_log(self.captured_log,
                                                       verbose=verbose))

            if blocker.signal_triggered:
                self._after_start()
                return

        raise WaitForTimeout("Timed out while waiting for process start.\n" +
                             _render_log(self.captured_log, verbose=verbose))

    def _start(self, args, env):
        """Actually start the process."""
        executable, exec_args = self._executable_args()
        if args is None:
            args = self._default_args()

        procenv = QProcessEnvironment.systemEnvironment()
        if env is not None:
            for k, v in env.items():
                procenv.insert(k, v)

        self.proc.readyRead.connect(self.read_log)
        self.proc.setProcessEnvironment(procenv)
        self.proc.start(executable, exec_args + args)
        ok = self.proc.waitForStarted()
        assert ok
        assert self.is_running()

    def _after_start(self):
        """Do things which should be done immediately after starting."""

    def before_test(self):
        """Restart process before a test if it exited before."""
        self._invalid = []
        if not self.is_running():
            self.start()

    def after_test(self):
        """Clean up data after each test.

        Also checks self._invalid so the test counts as failed if there were
        unexpected output lines earlier.
        """
        __tracebackhide__ = lambda e: e.errisinstance(ProcessExited)
        self.captured_log = []
        if self._invalid:
            # Wait for a bit so the full error has a chance to arrive
            time.sleep(1)
            # Exit the process to make sure we're in a defined state again
            self.terminate()
            self.clear_data()
            raise InvalidLine('\n' + '\n'.join(self._invalid))

        self.clear_data()
        if not self.is_running() and not self.exit_expected and self._started:
            raise ProcessExited
        self.exit_expected = False

    def clear_data(self):
        """Clear the collected data."""
        self._data.clear()

    def terminate(self):
        """Clean up and shut down the process."""
        if not self.is_running():
            return

        if quteutils.is_windows:
            self.proc.kill()
        else:
            self.proc.terminate()

        ok = self.proc.waitForFinished()
        assert ok

    def is_running(self):
        """Check if the process is currently running."""
        return self.proc.state() == QProcess.Running

    def _match_data(self, value, expected):
        """Helper for wait_for to match a given value.

        The behavior of this method is slightly different depending on the
        types of the filtered values:

        - If expected is None, the filter always matches.
        - If the value is a string or bytes object and the expected value is
          too, the pattern is treated as a glob pattern (with only * active).
        - If the value is a string or bytes object and the expected value is a
          compiled regex, it is used for matching.
        - If the value is any other type, == is used.

        Return:
            A bool
        """
        regex_type = type(re.compile(''))
        if expected is None:
            return True
        elif isinstance(expected, regex_type):
            return expected.search(value)
        elif isinstance(value, (bytes, str)):
            return utils.pattern_match(pattern=expected, value=value)
        else:
            return value == expected

    def _wait_for_existing(self, override_waited_for, after, **kwargs):
        """Check if there are any line in the history for wait_for.

        Return: either the found line or None.
        """
        for line in self._data:
            matches = []

            for key, expected in kwargs.items():
                value = getattr(line, key)
                matches.append(self._match_data(value, expected))

            if after is None:
                too_early = False
            else:
                too_early = ((line.timestamp, line.msecs) <
                             (after.timestamp, after.msecs))

            if (all(matches) and
                    (not line.waited_for or override_waited_for) and
                    not too_early):
                # If we waited for this line, chances are we don't mean the
                # same thing the next time we use wait_for and it matches
                # this line again.
                line.waited_for = True
                self._log("\n----> Already found {!r} in the log: {}".format(
                    kwargs.get('message', 'line'), line))
                return line
        return None

    def _wait_for_new(self, timeout, do_skip, **kwargs):
        """Wait for a log message which doesn't exist yet.

        Called via wait_for.
        """
        __tracebackhide__ = lambda e: e.errisinstance(WaitForTimeout)
        message = kwargs.get('message', None)
        if message is not None:
            elided = quteutils.elide(repr(message), 100)
            self._log("\n----> Waiting for {} in the log".format(elided))

        spy = QSignalSpy(self.new_data)
        elapsed_timer = QElapsedTimer()
        elapsed_timer.start()

        while True:
            # Skip if there are pending messages causing a skip
            self._maybe_skip()
            got_signal = spy.wait(timeout)
            if not got_signal or elapsed_timer.hasExpired(timeout):
                msg = "Timed out after {}ms waiting for {!r}.".format(
                    timeout, kwargs)
                if do_skip:
                    pytest.skip(msg)
                else:
                    raise WaitForTimeout(msg)

            match = self._wait_for_match(spy, kwargs)
            if match is not None:
                if message is not None:
                    self._log("----> found it")
                return match

        raise quteutils.Unreachable

    def _wait_for_match(self, spy, kwargs):
        """Try matching the kwargs with the given QSignalSpy."""
        for args in spy:
            assert len(args) == 1
            line = args[0]

            matches = []

            for key, expected in kwargs.items():
                value = getattr(line, key)
                matches.append(self._match_data(value, expected))

            if all(matches):
                # If we waited for this line, chances are we don't mean the
                # same thing the next time we use wait_for and it matches
                # this line again.
                line.waited_for = True
                return line
        return None

    def _maybe_skip(self):
        """Can be overridden by subclasses to skip on certain log lines.

        We can't run pytest.skip directly while parsing the log, as that would
        lead to a pytest.skip.Exception error in a virtual Qt method, which
        means pytest-qt fails the test.

        Instead, we check for skip messages periodically in
        QuteProc._maybe_skip, and call _maybe_skip after every parsed message
        in wait_for (where it's most likely that new messages arrive).
        """

    def wait_for(self, timeout=None, *, override_waited_for=False,
                 do_skip=False, divisor=1, after=None, **kwargs):
        """Wait until a given value is found in the data.

        Keyword arguments to this function get interpreted as attributes of the
        searched data. Every given argument is treated as a pattern which
        the attribute has to match against.

        Args:
            timeout: How long to wait for the message.
            override_waited_for: If set, gets triggered by previous messages
                                 again.
            do_skip: If set, call pytest.skip on a timeout.
            divisor: A factor to decrease the timeout by.
            after: If it's an existing line, ensure it's after the given one.

        Return:
            The matched line.
        """
        __tracebackhide__ = lambda e: e.errisinstance(WaitForTimeout)

        if timeout is None:
            if do_skip:
                timeout = 2000
            elif 'CI' in os.environ:
                timeout = 15000
            else:
                timeout = 5000

        timeout /= divisor

        if not kwargs:
            raise TypeError("No keyword arguments given!")
        for key in kwargs:
            assert key in self.KEYS

        existing = self._wait_for_existing(override_waited_for, after,
                                           **kwargs)
        if existing is not None:
            return existing
        else:
            return self._wait_for_new(timeout=timeout, do_skip=do_skip,
                                      **kwargs)

    def ensure_not_logged(self, delay=500, **kwargs):
        """Make sure the data matching the given arguments is not logged.

        If nothing is found in the log, we wait for delay ms to make sure
        nothing arrives.
        """
        __tracebackhide__ = lambda e: e.errisinstance(BlacklistedMessageError)
        try:
            line = self.wait_for(timeout=delay, override_waited_for=True,
                                 **kwargs)
        except WaitForTimeout:
            return
        else:
            raise BlacklistedMessageError(line)

    def wait_for_quit(self):
        """Wait until the process has quit."""
        self.exit_expected = True
        with self._wait_signal(self.proc.finished, timeout=15000):
            pass
コード例 #57
0
class SvnTagBranchListDialog(QDialog, Ui_SvnTagBranchListDialog):
    """
    Class implementing a dialog to show a list of tags or branches.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        super(SvnTagBranchListDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.Window)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.vcs = vcs
        self.tagsList = None
        self.allTagsList = None
        
        self.tagList.headerItem().setText(self.tagList.columnCount(), "")
        self.tagList.header().setSortIndicator(3, Qt.AscendingOrder)
        
        self.process = QProcess()
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        self.rx_list = QRegExp(
            r"""\w*\s*(\d+)\s+(\w+)\s+\d*\s*"""
            r"""((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)/\s*""")
        
    def closeEvent(self, e):
        """
        Protected slot implementing a close event handler.
        
        @param e close event (QCloseEvent)
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        e.accept()
        
    def start(self, path, tags, tagsList, allTagsList):
        """
        Public slot to start the svn status command.
        
        @param path name of directory to be listed (string)
        @param tags flag indicating a list of tags is requested
            (False = branches, True = tags)
        @param tagsList reference to string list receiving the tags
            (list of strings)
        @param allTagsList reference to string list all tags (list of strings)
        """
        self.errorGroup.hide()
        
        self.tagList.clear()
        
        self.intercept = False
        if not tags:
            self.setWindowTitle(self.tr("Subversion Branches List"))
        self.activateWindow()
        
        self.tagsList = tagsList
        self.allTagsList = allTagsList
        dname, fname = self.vcs.splitPath(path)
        
        self.process.kill()
        
        reposURL = self.vcs.svnGetReposName(dname)
        if reposURL is None:
            E5MessageBox.critical(
                self,
                self.tr("Subversion Error"),
                self.tr(
                    """The URL of the project repository could not be"""
                    """ retrieved from the working copy. The list operation"""
                    """ will be aborted"""))
            self.close()
            return
        
        args = []
        args.append('list')
        self.vcs.addArguments(args, self.vcs.options['global'])
        args.append('--verbose')
        
        if self.vcs.otherData["standardLayout"]:
            # determine the base path of the project in the repository
            rx_base = QRegExp('(.+)/(trunk|tags|branches).*')
            if not rx_base.exactMatch(reposURL):
                E5MessageBox.critical(
                    self,
                    self.tr("Subversion Error"),
                    self.tr(
                        """The URL of the project repository has an"""
                        """ invalid format. The list operation will"""
                        """ be aborted"""))
                return
            
            reposRoot = rx_base.cap(1)
            
            if tags:
                args.append("{0}/tags".format(reposRoot))
            else:
                args.append("{0}/branches".format(reposRoot))
            self.path = None
        else:
            reposPath, ok = QInputDialog.getText(
                self,
                self.tr("Subversion List"),
                self.tr("Enter the repository URL containing the tags"
                        " or branches"),
                QLineEdit.Normal,
                self.vcs.svnNormalizeURL(reposURL))
            if not ok:
                self.close()
                return
            if not reposPath:
                E5MessageBox.critical(
                    self,
                    self.tr("Subversion List"),
                    self.tr("""The repository URL is empty."""
                            """ Aborting..."""))
                self.close()
                return
            args.append(reposPath)
            self.path = reposPath
        
        self.process.setWorkingDirectory(dname)
        
        self.process.start('svn', args)
        procStarted = self.process.waitForStarted(5000)
        if not procStarted:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format('svn'))
        else:
            self.inputGroup.setEnabled(True)
            self.inputGroup.show()
        
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed the
        button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
            Qt.OtherFocusReason)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        self.__resizeColumns()
        self.__resort()
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__finish()
        
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
        
    def __resort(self):
        """
        Private method to resort the tree.
        """
        self.tagList.sortItems(
            self.tagList.sortColumn(),
            self.tagList.header().sortIndicatorOrder())
        
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.tagList.header().resizeSections(QHeaderView.ResizeToContents)
        self.tagList.header().setStretchLastSection(True)
        
    def __generateItem(self, revision, author, date, name):
        """
        Private method to generate a tag item in the taglist.
        
        @param revision revision string (string)
        @param author author of the tag (string)
        @param date date of the tag (string)
        @param name name (path) of the tag (string)
        """
        itm = QTreeWidgetItem(self.tagList)
        itm.setData(0, Qt.DisplayRole, int(revision))
        itm.setData(1, Qt.DisplayRole, author)
        itm.setData(2, Qt.DisplayRole, date)
        itm.setData(3, Qt.DisplayRole, name)
        itm.setTextAlignment(0, Qt.AlignRight)
        
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        self.process.setReadChannel(QProcess.StandardOutput)
        
        while self.process.canReadLine():
            s = str(self.process.readLine(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            if self.rx_list.exactMatch(s):
                rev = "{0:6}".format(self.rx_list.cap(1))
                author = self.rx_list.cap(2)
                date = self.rx_list.cap(3)
                path = self.rx_list.cap(4)
                if path == ".":
                    continue
                self.__generateItem(rev, author, date, path)
                if not self.vcs.otherData["standardLayout"]:
                    path = self.path + '/' + path
                if self.tagsList is not None:
                    self.tagsList.append(path)
                if self.allTagsList is not None:
                    self.allTagsList.append(path)
        
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.process is not None:
            self.errorGroup.show()
            s = str(self.process.readAllStandardError(),
                    Preferences.getSystem("IOEncoding"),
                    'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
        
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
        
    @pyqtSlot()
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
        
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
        
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        super(SvnTagBranchListDialog, self).keyPressEvent(evt)
コード例 #58
0
 def _performMonitor(self):
     """
     Protected method implementing the monitoring action.
     
     This method populates the statusList member variable
     with a list of strings giving the status in the first column and the
     path relative to the project directory starting with the third column.
     The allowed status flags are:
     <ul>
         <li>"A" path was added but not yet comitted</li>
         <li>"M" path has local changes</li>
         <li>"O" path was removed</li>
         <li>"R" path was deleted and then re-added</li>
         <li>"U" path needs an update</li>
         <li>"Z" path contains a conflict</li>
         <li>" " path is back at normal</li>
     </ul>
     
     @return tuple of flag indicating successful operation (boolean) and
         a status message in case of non successful operation (string)
     """
     self.shouldUpdate = False
     
     if self.__client is None and not self.__useCommandLine:
         if self.vcs.version >= (2, 9, 9):
             # versions below that have a bug causing a second
             # instance to not recognize changes to the status
             from .HgClient import HgClient
             client = HgClient(self.projectDir, "utf-8", self.vcs)
             ok, err = client.startServer()
             if ok:
                 self.__client = client
             else:
                 self.__useCommandLine = True
         else:
             self.__useCommandLine = True
     
     # step 1: get overall status
     args = self.vcs.initCommand("status")
     args.append('--noninteractive')
     args.append('--all')
     
     output = ""
     error = ""
     if self.__client:
         output, error = self.__client.runcommand(args)
     else:
         process = QProcess()
         process.setWorkingDirectory(self.projectDir)
         process.start('hg', args)
         procStarted = process.waitForStarted(5000)
         if procStarted:
             finished = process.waitForFinished(300000)
             if finished and process.exitCode() == 0:
                 output = str(process.readAllStandardOutput(),
                              self.vcs.getEncoding(), 'replace')
             else:
                 process.kill()
                 process.waitForFinished()
                 error = str(process.readAllStandardError(),
                             self.vcs.getEncoding(), 'replace')
         else:
             process.kill()
             process.waitForFinished()
             error = self.tr("Could not start the Mercurial process.")
     
     if error:
         return False, error
     
     states = {}
     for line in output.splitlines():
         if not line.startswith("  "):
             flag, name = line.split(" ", 1)
             if flag in "AMR":
                 if flag == "R":
                     status = "O"
                 else:
                     status = flag
                 states[name] = status
     
     # step 2: get conflicting changes
     args = self.vcs.initCommand("resolve")
     args.append('--list')
     
     output = ""
     error = ""
     if self.__client:
         output, error = self.__client.runcommand(args)
     else:
         process.setWorkingDirectory(self.projectDir)
         process.start('hg', args)
         procStarted = process.waitForStarted(5000)
         if procStarted:
             finished = process.waitForFinished(300000)
             if finished and process.exitCode() == 0:
                 output = str(process.readAllStandardOutput(),
                              self.vcs.getEncoding(), 'replace')
     
     for line in output.splitlines():
         flag, name = line.split(" ", 1)
         if flag == "U":
             states[name] = "Z"  # conflict
     
     # step 3: collect the status to be reported back
     for name in states:
         try:
             if self.reportedStates[name] != states[name]:
                 self.statusList.append(
                     "{0} {1}".format(states[name], name))
         except KeyError:
             self.statusList.append("{0} {1}".format(states[name], name))
     for name in self.reportedStates.keys():
         if name not in states:
             self.statusList.append("  {0}".format(name))
     self.reportedStates = states
     
     return True, \
         self.tr("Mercurial status checked successfully")