Exemplo n.º 1
0
def f_main():
    """主程序,保证程序单实例运行"""
    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
    servername = "TransTools"

    socket = QLocalSocket()
    socket.connectToServer(servername)
    if socket.waitForConnected(500):
        # 程序只允许单例运行
        showmsg("程序已运行!")
        return(app.quit())

    # 没有实例运行,创建服务器
    localServer = QLocalServer()
    localServer.listen(servername)

    try:
        main = Main()
        main.show()

        sys.exit(app.exec_())
    except Exception as e:
        showmsg(str(e), type=QMessageBox.Critical)
    finally:
        localServer.close()
Exemplo n.º 2
0
class SingleApplication(QObject):

    newInstance = pyqtSignal()
    urlPost = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.mServer = QLocalServer()
        self.mServer.newConnection.connect(self.newConnection)

    def listen(self, client):
        self.mServer.removeServer(client)
        self.mServer.listen(client)
        print(self.mServer.errorString())

    def hasPrevious(self, name, args):
        socket = QLocalSocket()
        socket.connectToServer(name, QLocalSocket.ReadWrite)
        if socket.waitForConnected():
            if len(args) > 1:
                socket.write(args[1])

            socket.flush()
            return True
        return False

    def newConnection(self):
        self.newInstance.emit()
        self.mSocket = self.mServer.nextPendingConnection()
        self.mSocket.readyRead.connect(self.readyRead)

    def readyRead(self):
        self.urlPost.emit(str(self.mSocket.readAll()))
        self.mSocket.close()
Exemplo n.º 3
0
def main():
    import sys
    app = QApplication(sys.argv)
    translator = QTranslator()
    locale = QLocale.system().name()
    translateFile = os.path.join(BASEDIR, 'i18n\\translations', '{}.qm'.format(locale))
    if translator.load(translateFile):
        app.installTranslator(translator)

    # QApplication.setStyle(QStyleFactory.create('Fusion'))

    if boolean(conf.value('General/LargeFont')):
        font = QFont('Courier New', 14)
        app.setFont(font)

    serverName = 'Tafor'
    socket = QLocalSocket()
    socket.connectToServer(serverName)

    # 如果连接成功,表明server已经存在,当前已有实例在运行
    if socket.waitForConnected(500):
        return(app.quit())

    # 没有实例运行,创建服务器     
    localServer = QLocalServer()
    localServer.listen(serverName)

    try:          
        window = MainWindow()
        window.show()
        sys.exit(app.exec_())
    except Exception as e:
        logger.error(e, exc_info=True)
    finally:  
        localServer.close()
Exemplo n.º 4
0
    def init_nxdrive_listener(self) -> None:
        """
        Set up a QLocalServer to listen to nxdrive protocol calls.

        On Windows, when an nxdrive:// URL is opened, it creates a new
        instance of Nuxeo Drive. As we want the already running instance to
        receive this call (particularly during the login process), we set
        up a QLocalServer in that instance to listen to the new ones who will
        send their data.
        The Qt implementation of QLocalSocket on Windows makes use of named
        pipes. We just need to connect a handler to the newConnection signal
        to process the URLs.
        """
        named_pipe = f"{BUNDLE_IDENTIFIER}.protocol.{os.getpid()}"
        server = QLocalServer()
        server.setSocketOptions(QLocalServer.WorldAccessOption)
        server.newConnection.connect(self._handle_connection)
        try:
            server.listen(named_pipe)
            log.info(f"Listening for nxdrive:// calls on {server.fullServerName()}")
        except:
            log.info(
                f"Unable to start local server on {named_pipe}: {server.errorString()}"
            )

        self._nxdrive_listener = server
        self.aboutToQuit.connect(self._nxdrive_listener.close)
Exemplo n.º 5
0
def main():
    # ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("AirMemo_appid")
    app = QApplication(sys.argv)

    serverName = 'AirMemo_client'
    socket = QLocalSocket()
    socket.connectToServer(serverName)

    # 如果连接成功,表明server已经存在,当前已有实例在运行
    if socket.waitForConnected(500):
        sys.exit(app.quit())

    # 没有实例运行,创建服务器
    localServer = QLocalServer()
    localServer.listen(serverName)
    try:
        setApp(app)
        link_db(config.LDB_FILENAME)
        tray = AirTray()
        mainWindow = Ui_MainWindow(tray)
        setting_win = Ui_Settings(tray)
        dict = {'main_win': mainWindow, 'setting_win': setting_win}
        tray.set_menu(dict)
        tray.show()
        # mainWindow.show()
        setting_win.show()
        sys.exit(app.exec_())
    finally:
        localServer.close()
Exemplo n.º 6
0
def init():
    """Start listening to incoming connections."""
    global _server
    if _server is not None:
        return
    
    server = QLocalServer(None)
    
    # find a free socket name to use
    for name in ids():
        if server.listen(name):
            break
    else:
        # all names failed, try to contact and remove stale file if that fails
        socket = QLocalSocket()
        for name in ids():
            socket.connectToServer(name)
            if not socket.waitForConnected(10000):
                QLocalServer.removeServer(name)
                if server.listen(name):
                    break
            else:
                socket.disconnectFromServer()
        else:
            # no ids left, don't listen
            return
    app.aboutToQuit.connect(server.close)
    server.newConnection.connect(slot_new_connection)
    os.environ["FRESCOBALDI_SOCKET"] = name
    _server = server
Exemplo n.º 7
0
def main():
    # ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("AirMemo_appid")
    app = QApplication(sys.argv)
    serverName = 'rest_clock'
    socket = QLocalSocket()
    socket.connectToServer(serverName)

    # 如果连接成功,表明server已经存在,当前已有实例在运行
    if socket.waitForConnected(500):
        sys.exit(app.quit())

    # 没有实例运行,创建服务器
    localServer = QLocalServer()
    localServer.listen(serverName)
    try:
        set_app(app)
        tray = clock_tray()
        timeout_win = Ui_Timeout(tray)
        settings_win = Ui_Settings(tray)
        tran_win = TranSignalWidget()
        tran_win.showSignal.connect(timeout_win.show)
        win_dict = {"timeout_win": timeout_win, 'settings_win': settings_win}
        tray.set_menu(win_dict)
        set_aps(tran_win)
        timeout_win.show()
        TIME_MISSION.set_aps_mission()
        MYAPS.start()
        logging.debug('APS jobs {}'.format(MYAPS.get_jobs()))
        sys.exit(app.exec_())
    finally:
        localServer.close()
Exemplo n.º 8
0
def init():
    """Start listening to incoming connections."""
    global _server
    if _server is not None:
        return

    server = QLocalServer(None)

    # find a free socket name to use
    for name in ids():
        if server.listen(name):
            break
    else:
        # all names failed, try to contact and remove stale file if that fails
        socket = QLocalSocket()
        for name in ids():
            socket.connectToServer(name)
            if not socket.waitForConnected(10000):
                QLocalServer.removeServer(name)
                if server.listen(name):
                    break
            else:
                socket.disconnectFromServer()
        else:
            # no ids left, don't listen
            return
    app.aboutToQuit.connect(server.close)
    server.newConnection.connect(slot_new_connection)
    os.environ["FRESCOBALDI_SOCKET"] = name
    _server = server
Exemplo n.º 9
0
class SocketServer(SocketInterface):
    def __init__(self, serverName, parent=None):
        SocketInterface.__init__(self, serverName, parent=parent)

        self.server = QLocalServer(parent)
        self.server.listen(serverName)
        self.server.newConnection.connect(self.newConnection)

    def nextMemoryKey(self):
        return SocketInterface.nextMemoryKey(self) + "S"

    def newConnection(self):
        conn = self.conn
        if not conn:
            conn = self.server.nextPendingConnection()
            conn.disconnected.connect(conn.deleteLater)

        conn.waitForReadyRead()
        data = conn.readAll().data()
        if not self.conn and data == "Hello {0}!".format(
                self.serverName).encode("utf-8"):
            self.conn = conn
            self.conn.readyRead.connect(self.receiveMessage)
            self.conn.disconnected.connect(self.connDisconnected)
            self.log("Connection established.")
        else:
            conn.disconnectFromServer()
            self.log("Connection refused.")

    def connDisconnected(self):
        self.conn = None
Exemplo n.º 10
0
class SingleApplication(QApplication):
    def __init__(self, args):
        super().__init__(args)
        self.TIME_OUT = 1000
        self.isRuning = False
        self.serverName = 'skyutils'
        self.args = args
        self.mousePos = QPointF(0, 0)
        self.initLocalConnection()

    def initLocalConnection(self):
        socket = QLocalSocket()
        socket.connectToServer(self.serverName)
        if socket.waitForConnected(self.TIME_OUT):
            self.isRuning = True
            if len(self.args) >= 3:
                data = self.args[1] + ',' + self.args[2]
                socket.writeData(data.encode())
                socket.flush()
                socket.waitForBytesWritten()
            return
        self.newLocalServer()

    def newLocalServer(self):
        self.localServer = QLocalServer(self)
        self.localServer.newConnection.connect(self.newLocalConnection)
        if not self.localServer.listen(self.serverName):
            if self.localServer.serverError(
            ) == QAbstractSocket.AddressInUseError:
                QLocalServer.removeServer(self.serverName)
                self.localServer.listen(self.serverName)

    def newLocalConnection(self):
        socket = self.localServer.nextPendingConnection()
        socket.readyRead.connect(self.readyRead)

    def readyRead(self):
        socket = self.sender()
        if socket:
            data = socket.readData(1000)
            socket.close()
            if data:
                data = data.decode().split(',')
                x, y = 0, 0
                if self.mousePos:
                    x = self.mousePos.x()
                    y = self.mousePos.y()
                self.mainWindow.move(int(data[0]) - x, int(data[1]) - y)
        self.mainWindow.setWindowFlags(self.mainWindow.windowFlags()
                                       | Qt.WindowStaysOnTopHint)
        self.mainWindow.show()

    def notify(self, receiver, event):
        if event.type() == QEvent.MouseButtonPress:
            self.mousePos = event.localPos()
        return super().notify(receiver, event)
Exemplo n.º 11
0
class SingleApplication(QApplication):
    messageAvailable = pyqtSignal(type(u''))

    def __init__(self, argv, key):
        QApplication.__init__(self, argv)

        self._key = key
        self._timeout = 1000

        socket = QLocalSocket(self)
        socket.connectToServer(self._key)
        if socket.waitForConnected(self._timeout):
            self._isRunning = True
            socket.abort()
            return
        socket.abort()

        self._isRunning = False
        self._server = QLocalServer(self)
        self._server.newConnection.connect(self.__onNewConnection)
        self._server.listen(self._key)

        self.aboutToQuit.connect(self.__onAboutToQuit)

    def __onAboutToQuit(self):
        if self._server:
            self._server.close()
            self._server = None

    def __onNewConnection(self):
        socket = self._server.nextPendingConnection()
        if socket.waitForReadyRead(self._timeout):
            self.messageAvailable.emit(socket.readAll().data().decode('utf-8'))
            socket.disconnectFromServer()
        else:
            pass

    def isRunning(self):
        return self._isRunning

    def sendMessage(self, message):
        assert (self._isRunning)

        if self.isRunning():
            socket = QLocalSocket(self)
            socket.connectToServer(self._key, QIODevice.WriteOnly)
            if not socket.waitForConnected(self._timeout):
                return False
            socket.write(message.encode('utf-8'))
            if not socket.waitForBytesWritten(self._timeout):
                return False
            socket.disconnectFromServer()
            return True
        return False
Exemplo n.º 12
0
class IpcServer(QObject):
    @classmethod
    def get_sock_name(cls):
        return "webmacs.ipc"

    @classmethod
    def check_server_connection(cls):
        sock = QLocalSocket()
        sock.connectToServer(cls.get_sock_name())
        if sock.waitForConnected(1000):
            return IPcReader(sock)
        return None

    def __init__(self):
        QObject.__init__(self)
        self._server = QLocalServer()
        self._server.newConnection.connect(self._on_new_connection)
        self._server.listen(self.get_sock_name())
        self._readers = {}

    def cleanup(self):
        try:
            os.unlink(self._server.fullServerName())
        except OSError:
            pass

    @Slot()
    def _on_new_connection(self):
        conn = self._server.nextPendingConnection()
        reader = IPcReader(conn)
        reader.message_received.connect(self.handle_data)
        conn.readyRead.connect(reader.on_ready_read)
        conn.disconnected.connect(self.reader_disconnected)
        self._readers[conn] = reader

    @Slot(object)
    def handle_data(self, data):
        reader = self.sender()
        try:
            res = ipc_dispatch(data)
        except Exception as exc:
            res = str(exc)

        if res in (True, None):
            reader.send_data({"result": True})
        else:
            reader.send_data({"result": False, "message": res})

    def reader_disconnected(self):
        conn = self.sender()
        reader = self._readers.pop(conn)
        reader.clear()
        reader.deleteLater()
Exemplo n.º 13
0
def main():
    global win
    signal.signal(signal.SIGINT, exit)
    args = parse_arguments()
    appKey = "scudcloud.pid"
    socket = QLocalSocket()
    socket.connectToServer(appKey)
    if socket.isOpen():
        socket.close()
        socket.deleteLater()
        return 0
    socket.deleteLater()
    app = QtWidgets.QApplication(sys.argv)
    app.setApplicationName(Resources.APP_NAME + ' Slack')
    app.setWindowIcon(QtGui.QIcon(Resources.get_path('scudcloud.png')))
    try:
        settings_path, cache_path = load_settings(args.confdir, args.cachedir)
    except:
        print("Data directories " + args.confdir + " and " + args.cachedir +
              " could not be created! Exiting...")
        raise SystemExit()
    minimized = True if args.minimized is True else None
    urgent_hint = True if args.urgent_hint is True else None

    # Let's move the CSS to cachedir to enable additional actions
    copyfile(Resources.get_path('resources.css'),
             os.path.join(cache_path, 'resources.css'))

    # If there is an qt4 config and not a qt5, let's copy the old one
    qt4_config = os.path.join(settings_path, 'scudcloud.cfg')
    qt5_config = os.path.join(settings_path, 'scudcloud_qt5.cfg')
    if os.path.exists(qt4_config) and not os.path.exists(qt5_config):
        copyfile(qt4_config, qt5_config)

    win = sca.ScudCloud(debug=args.debug,
                        minimized=minimized,
                        urgent_hint=urgent_hint,
                        settings_path=settings_path,
                        cache_path=cache_path)
    app.commitDataRequest.connect(win.setForceClose,
                                  type=QtCore.Qt.DirectConnection)

    server = QLocalServer()
    server.newConnection.connect(restore)
    server.listen(appKey)
    win.restore()
    if win.minimized is None:
        win.show()
    sys.exit(app.exec_())
Exemplo n.º 14
0
def main(now_version, new_version):
    try:
        serverName = 'dig_word_update_Server'
        socket = QLocalSocket()
        socket.connectToServer(serverName)
        # 如果连接成功,表明server已经存在,当前已有实例在运行
        if socket.waitForConnected(500):
            pass
        else:
            localServer = QLocalServer()  # 没有实例运行,创建服务器
            localServer.listen(serverName)
            # 处理其他
            downwin_class(now_version, new_version)
    except:
        pass
Exemplo n.º 15
0
class Server(QDialog):
    def __init__(self, parent=None):
        super(Server, self).__init__(parent)

        statusLabel = QLabel()
        statusLabel.setWordWrap(True)
        quitButton = QPushButton("Quit")
        quitButton.setAutoDefault(False)

        self.fortunes = (
            "You've been leading a dog's life. Stay off the furniture.",
            "You've got to think about tomorrow.",
            "You will be surprised by a loud noise.",
            "You will feel hungry again in another hour.",
            "You might have mail.",
            "You cannot kill time without injuring eternity.",
            "Computers are not intelligent. They only think they are.",
        )

        self.server = QLocalServer()
        if not self.server.listen('fortune'):
            QMessageBox.critical(
                self, "Fortune Server",
                "Unable to start the server: %s." % self.server.errorString())
            self.close()
            return

        statusLabel.setText("The server is running.\nRun the Fortune Client "
                            "example now.")

        quitButton.clicked.connect(self.close)
        self.server.newConnection.connect(self.sendFortune)

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch(1)
        buttonLayout.addWidget(quitButton)
        buttonLayout.addStretch(1)

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(statusLabel)
        mainLayout.addLayout(buttonLayout)
        self.setLayout(mainLayout)

        self.setWindowTitle("Fortune Server")

    def sendFortune(self):
        block = QByteArray()
        out = QDataStream(block, QIODevice.WriteOnly)
        out.setVersion(QDataStream.Qt_4_0)
        out.writeUInt16(0)
        out.writeQString(random.choice(self.fortunes))
        out.device().seek(0)
        out.writeUInt16(block.size() - 2)

        clientConnection = self.server.nextPendingConnection()
        clientConnection.disconnected.connect(clientConnection.deleteLater)
        clientConnection.write(block)
        clientConnection.flush()
        clientConnection.disconnectFromServer()
Exemplo n.º 16
0
class Server(QDialog):
    def __init__(self, parent=None):
        super(Server, self).__init__(parent)

        statusLabel = QLabel()
        statusLabel.setWordWrap(True)
        quitButton = QPushButton("Quit")
        quitButton.setAutoDefault(False)

        self.fortunes = (
            "You've been leading a dog's life. Stay off the furniture.",
            "You've got to think about tomorrow.",
            "You will be surprised by a loud noise.",
            "You will feel hungry again in another hour.",
            "You might have mail.",
            "You cannot kill time without injuring eternity.",
            "Computers are not intelligent. They only think they are.",
        )

        self.server = QLocalServer()
        if not self.server.listen('fortune'):
            QMessageBox.critical(self, "Fortune Server",
                    "Unable to start the server: %s." % self.server.errorString())
            self.close()
            return

        statusLabel.setText("The server is running.\nRun the Fortune Client "
                "example now.")

        quitButton.clicked.connect(self.close)
        self.server.newConnection.connect(self.sendFortune)

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch(1)
        buttonLayout.addWidget(quitButton)
        buttonLayout.addStretch(1)

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(statusLabel)
        mainLayout.addLayout(buttonLayout)
        self.setLayout(mainLayout)

        self.setWindowTitle("Fortune Server")

    def sendFortune(self):
        block = QByteArray()
        out = QDataStream(block, QIODevice.WriteOnly)
        out.setVersion(QDataStream.Qt_4_0)
        out.writeUInt16(0)
        out.writeQString(random.choice(self.fortunes))
        out.device().seek(0)
        out.writeUInt16(block.size() - 2)

        clientConnection = self.server.nextPendingConnection()
        clientConnection.disconnected.connect(clientConnection.deleteLater)
        clientConnection.write(block)
        clientConnection.flush()
        clientConnection.disconnectFromServer()
Exemplo n.º 17
0
class InstanceHandler(QObject):
    """ Makes sure that only one instance of this application can be run. """

    received = pyqtSignal(str)

    def __init__(self, parent, name):
        super(__class__, self).__init__(parent)
        self._parent = parent
        self._name = name
        self._timeout = 1000
        self._socket = QLocalSocket(self)
        self._socket.connectToServer(self._name)
        self._is_already_running = self._socket.waitForConnected(self._timeout)
        if not self.isAlreadyRunning():
            self._server = QLocalServer(self)
            self._server.newConnection.connect(self._receive_data)
            self._server.removeServer(self._name)
            self._server.listen(self._name)

    def _send_data(self, data=None):
        """ Sends model to an server-application. """
        if not data:
            data = ""
        self._socket.write(data.encode())
        self._socket.waitForBytesWritten(self._timeout)
        self._socket.disconnectFromServer()

    def _receive_data(self):
        """ Receives model from an client-application. """
        socket = self._server.nextPendingConnection()
        if socket.waitForReadyRead(self._timeout):
            # Emit transmitted model.
            self.received.emit(socket.readAll().data().decode(
                "utf-8", "surrogateescape"))
        else:
            # When no model was transmitted just emit empty string.
            self.received.emit("")

    def newTab(self, input):
        """ Opens a new tab in an already running instance. """
        self._send_data(input)

    def isAlreadyRunning(self) -> bool:
        """ Returns True when an instance of the application is already running, otherwise False. """
        return self._is_already_running
class NotificaitonServer(object):
    """docstring for NotificaitonServer"""

    notifications = []

    def __init__(self, name, app):
        super(NotificaitonServer, self).__init__()
        self.name = name
        self.app = app

    def startServer(self):
        self.server = QLocalServer()
        self.server.listen(self.name)
        self.server.newConnection.connect(self.connected)
        pass

    def connected(self):
        # print("conncted!")
        if self.server.hasPendingConnections():
            self.conn = self.server.nextPendingConnection()
            # print(type(self.conn))
            self.conn.readyRead.connect(self.received)
            pass
        pass

    def received(self):
        # print("received!")
        content = self.conn.readAll()
        # print()
        self.notifications = [w for w in self.notifications if w.isVisible()]
        # print(len(self.notifications))
        for w in self.notifications:
            w.moveDown()
            pass

        payload = json.loads(bytes(content).decode("utf-8"))
        no = notification.NotifyOnce(payload)

        self.notifications.append(no)
        # ret = app.exec_()
        # print("app able to exit")
        # sys.exit(ret)
        pass
Exemplo n.º 19
0
def test_socket_options_address_in_use_problem(qlocalserver, short_tmpdir):
    """Qt seems to ignore AddressInUseError when using socketOptions.

    With this test we verify this bug still exists. If it fails, we can
    probably start using setSocketOptions again.
    """
    servername = str(short_tmpdir / 'x')

    s1 = QLocalServer()
    ok = s1.listen(servername)
    assert ok

    s2 = QLocalServer()
    s2.setSocketOptions(QLocalServer.UserAccessOption)
    ok = s2.listen(servername)
    print(s2.errorString())
    # We actually would expect ok == False here - but we want the test to fail
    # when the Qt bug is fixed.
    assert ok
Exemplo n.º 20
0
def test_socket_options_address_in_use_problem(qlocalserver, short_tmpdir):
    """Qt seems to ignore AddressInUseError when using socketOptions.

    With this test we verify this bug still exists. If it fails, we can
    probably start using setSocketOptions again.
    """
    servername = str(short_tmpdir / 'x')

    s1 = QLocalServer()
    ok = s1.listen(servername)
    assert ok

    s2 = QLocalServer()
    s2.setSocketOptions(QLocalServer.UserAccessOption)
    ok = s2.listen(servername)
    print(s2.errorString())
    # We actually would expect ok == False here - but we want the test to fail
    # when the Qt bug is fixed.
    assert ok
Exemplo n.º 21
0
class QSingleApplication(QApplication):
    sock_file = 'sumokoin_wallet_sock'
    if sys.platform == 'win32':
        sock_file = "\\\\.\\pipe\\%s" % sock_file
    elif sys.platform == 'darwin':
        sock_file = os.path.join(DATA_DIR, '.%s' % sock_file)
    else:
        sock_file = os.path.join(getSockDir(), sock_file)

    def singleStart(self, appMain):
        self.appMain = appMain
        # Socket
        self.m_socket = QLocalSocket()
        self.m_socket.connected.connect(self.connectToExistingApp)
        self.m_socket.error.connect(
            lambda: self.startApplication(first_start=True))
        self.m_socket.connectToServer(self.sock_file, QIODevice.WriteOnly)

    def connectToExistingApp(self):
        # Quit application in 250 ms
        QTimer.singleShot(250, self.quit)
        print("App is already running.", file=sys.stderr)

    def startApplication(self, first_start=True):
        self.m_server = QLocalServer()
        if self.m_server.listen(self.sock_file):
            print("Starting app...")
            self.appMain.run()
        else:
            if not first_start:
                print("Error listening the socket. App can't start!",
                      file=sys.stderr)
                QTimer.singleShot(250, self.quit)
                return

            # remove the listener path file and try to restart app one more time
            print("Error listening the socket. Try to restart application...",
                  file=sys.stderr)
            if sys.platform != 'win32':
                try:
                    os.unlink(self.sock_file)
                except Exception, err:
                    print(err, file=sys.stderr)

            QTimer.singleShot(250,
                              lambda: self.startApplication(first_start=False))
Exemplo n.º 22
0
class Server(QObject):
    dataReceived = pyqtSignal(list)
    quit = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.conn = None
        self.server = None

    def create(self, name=piony.G_SOCKET_NAME):
        QLocalServer.removeServer(name)
        self.server = QLocalServer()
        if not self.server.listen(name):
            print("Error: server -- unable to start: {}."
                  .format(self.server.errorString()))
            self.quit.emit()
        self.server.newConnection.connect(self.notify)

    def close(self):
        self.server.close()

    def notify(self):
        logger.info("1 new conn")
        # WARNING: when multiple connections, each will overwrite previous!
        self.conn = self.server.nextPendingConnection()
        self.conn.readyRead.connect(self.receiveData)
        self.conn.disconnected.connect(self.conn.deleteLater)

    def receiveData(self):
        logger.info("waits for data")
        ins = QDataStream(self.conn)
        ins.setVersion(QDataStream.Qt_5_0)
        if ins.atEnd():
            return
        argv = ins.readQVariant()
        logger.info("reads '%s'", str(argv))
        # Must be setted up on 'show' action. Move from beginning to appropriate.
        action.search_dst_window()
        self.dataReceived.emit(argv)
Exemplo n.º 23
0
class Server(QObject):
    dataReceived = pyqtSignal(list)
    quit = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.conn = None
        self.server = None

    def create(self, name=piony.G_SOCKET_NAME):
        QLocalServer.removeServer(name)
        self.server = QLocalServer()
        if not self.server.listen(name):
            print("Error: server -- unable to start: {}.".format(
                self.server.errorString()))
            self.quit.emit()
        self.server.newConnection.connect(self.notify)

    def close(self):
        self.server.close()

    def notify(self):
        logger.info("1 new conn")
        # WARNING: when multiple connections, each will overwrite previous!
        self.conn = self.server.nextPendingConnection()
        self.conn.readyRead.connect(self.receiveData)
        self.conn.disconnected.connect(self.conn.deleteLater)

    def receiveData(self):
        logger.info("waits for data")
        ins = QDataStream(self.conn)
        ins.setVersion(QDataStream.Qt_5_0)
        if ins.atEnd():
            return
        argv = ins.readQVariant()
        logger.info("reads '%s'", str(argv))
        # Must be setted up on 'show' action. Move from beginning to appropriate.
        action.search_dst_window()
        self.dataReceived.emit(argv)
Exemplo n.º 24
0
class TreeMainControl(QObject):
    """Class to handle all global controls.

    Provides methods for all controls and stores local control objects.
    """
    def __init__(self, pathObjects, parent=None):
        """Initialize the main tree controls

        Arguments:
            pathObjects -- a list of file objects to open
            parent -- the parent QObject if given
        """
        super().__init__(parent)
        self.localControls = []
        self.activeControl = None
        self.trayIcon = None
        self.isTrayMinimized = False
        self.configDialog = None
        self.sortDialog = None
        self.numberingDialog = None
        self.findTextDialog = None
        self.findConditionDialog = None
        self.findReplaceDialog = None
        self.filterTextDialog = None
        self.filterConditionDialog = None
        self.basicHelpView = None
        self.passwords = {}
        globalref.mainControl = self
        self.allActions = {}
        try:
            # check for existing TreeLine session
            socket = QLocalSocket()
            socket.connectToServer('treeline3-session', QIODevice.WriteOnly)
            # if found, send files to open and exit TreeLine
            if socket.waitForConnected(1000):
                socket.write(
                    bytes(repr([str(path) for path in pathObjects]), 'utf-8'))
                if socket.waitForBytesWritten(1000):
                    socket.close()
                    sys.exit(0)
            # start local server to listen for attempt to start new session
            self.serverSocket = QLocalServer()
            self.serverSocket.listen('treeline3-session')
            self.serverSocket.newConnection.connect(self.getSocket)
        except AttributeError:
            print(_('Warning:  Could not create local socket'))
        mainVersion = '.'.join(__version__.split('.')[:2])
        globalref.genOptions = options.Options('general', 'TreeLine',
                                               mainVersion, 'bellz')
        optiondefaults.setGenOptionDefaults(globalref.genOptions)
        globalref.miscOptions = options.Options('misc')
        optiondefaults.setMiscOptionDefaults(globalref.miscOptions)
        globalref.histOptions = options.Options('history')
        optiondefaults.setHistOptionDefaults(globalref.histOptions)
        globalref.toolbarOptions = options.Options('toolbar')
        optiondefaults.setToolbarOptionDefaults(globalref.toolbarOptions)
        globalref.keyboardOptions = options.Options('keyboard')
        optiondefaults.setKeyboardOptionDefaults(globalref.keyboardOptions)
        try:
            globalref.genOptions.readFile()
            globalref.miscOptions.readFile()
            globalref.histOptions.readFile()
            globalref.toolbarOptions.readFile()
            globalref.keyboardOptions.readFile()
        except IOError:
            errorDir = options.Options.basePath
            if not errorDir:
                errorDir = _('missing directory')
            QMessageBox.warning(
                None, 'TreeLine',
                _('Error - could not write config file to {}').format(
                    errorDir))
            options.Options.basePath = None
        iconPathList = self.findResourcePaths('icons', iconPath)
        globalref.toolIcons = icondict.IconDict(
            [path / 'toolbar' for path in iconPathList],
            ['', '32x32', '16x16'])
        globalref.toolIcons.loadAllIcons()
        windowIcon = globalref.toolIcons.getIcon('treelogo')
        if windowIcon:
            QApplication.setWindowIcon(windowIcon)
        globalref.treeIcons = icondict.IconDict(iconPathList, ['', 'tree'])
        icon = globalref.treeIcons.getIcon('default')
        qApp.setStyle(QStyleFactory.create('Fusion'))
        setThemeColors()
        self.recentFiles = recentfiles.RecentFileList()
        if globalref.genOptions['AutoFileOpen'] and not pathObjects:
            recentPath = self.recentFiles.firstPath()
            if recentPath:
                pathObjects = [recentPath]
        self.setupActions()
        self.systemFont = QApplication.font()
        self.updateAppFont()
        if globalref.genOptions['MinToSysTray']:
            self.createTrayIcon()
        qApp.focusChanged.connect(self.updateActionsAvail)
        if pathObjects:
            for pathObj in pathObjects:
                self.openFile(pathObj, True)
        else:
            self.createLocalControl()

    def getSocket(self):
        """Open a socket from an attempt to open a second Treeline instance.

        Opens the file (or raise and focus if open) in this instance.
        """
        socket = self.serverSocket.nextPendingConnection()
        if socket and socket.waitForReadyRead(1000):
            data = str(socket.readAll(), 'utf-8')
            try:
                paths = ast.literal_eval(data)
                if paths:
                    for path in paths:
                        self.openFile(pathlib.Path(path), True)
                else:
                    self.activeControl.activeWindow.activateAndRaise()
            except (SyntaxError, ValueError, TypeError):
                pass

    def findResourcePaths(self, resourceName, preferredPath=''):
        """Return list of potential non-empty pathlib objects for the resource.

        List includes preferred, module and user option paths.
        Arguments:
            resourceName -- the typical name of the resource directory
            preferredPath -- add this as the second path if given
        """
        modPath = pathlib.Path(sys.path[0]).resolve()
        if modPath.is_file():
            modPath = modPath.parent  # for frozen binary
        pathList = [modPath / '..' / resourceName, modPath / resourceName]
        if options.Options.basePath:
            basePath = pathlib.Path(options.Options.basePath)
            pathList.insert(0, basePath / resourceName)
        if preferredPath:
            pathList.insert(1, pathlib.Path(preferredPath))
        return [
            path.resolve() for path in pathList
            if path.is_dir() and list(path.iterdir())
        ]

    def findResourceFile(self, fileName, resourceName, preferredPath=''):
        """Return a path object for a resource file.

        Add a language code before the extension if it exists.
        Arguments:
            fileName -- the name of the file to find
            resourceName -- the typical name of the resource directory
            preferredPath -- search this path first if given
        """
        fileList = [fileName]
        if globalref.lang and globalref.lang != 'C':
            fileList[0:0] = [
                fileName.replace('.', '_{0}.'.format(globalref.lang)),
                fileName.replace('.', '_{0}.'.format(globalref.lang[:2]))
            ]
        for fileName in fileList:
            for path in self.findResourcePaths(resourceName, preferredPath):
                if (path / fileName).is_file():
                    return path / fileName
        return None

    def defaultPathObj(self, dirOnly=False):
        """Return a reasonable default file path object.

        Used for open, save-as, import and export.
        Arguments:
            dirOnly -- if True, do not include basename of file
        """
        pathObj = None
        if self.activeControl:
            pathObj = self.activeControl.filePathObj
        if not pathObj:
            pathObj = self.recentFiles.firstDir()
            if not pathObj:
                pathObj = pathlib.Path.home()
        if dirOnly:
            pathObj = pathObj.parent
        return pathObj

    def openFile(self,
                 pathObj,
                 forceNewWindow=False,
                 checkModified=False,
                 importOnFail=True):
        """Open the file given by path if not already open.

        If already open in a different window, focus and raise the window.
        Arguments:
            pathObj -- the path object to read
            forceNewWindow -- if True, use a new window regardless of option
            checkModified -- if True & not new win, prompt if file modified
            importOnFail -- if True, prompts for import on non-TreeLine files
        """
        match = [
            control for control in self.localControls
            if pathObj == control.filePathObj
        ]
        if match and self.activeControl not in match:
            control = match[0]
            control.activeWindow.activateAndRaise()
            self.updateLocalControlRef(control)
            return
        if checkModified and not (forceNewWindow
                                  or globalref.genOptions['OpenNewWindow']
                                  or self.activeControl.checkSaveChanges()):
            return
        if not self.checkAutoSave(pathObj):
            if not self.localControls:
                self.createLocalControl()
            return
        QApplication.setOverrideCursor(Qt.WaitCursor)
        try:
            self.createLocalControl(pathObj, None, forceNewWindow)
            self.recentFiles.addItem(pathObj)
            if not (globalref.genOptions['SaveTreeStates'] and
                    self.recentFiles.retrieveTreeState(self.activeControl)):
                self.activeControl.expandRootNodes()
                self.activeControl.selectRootSpot()
            QApplication.restoreOverrideCursor()
        except IOError:
            QApplication.restoreOverrideCursor()
            QMessageBox.warning(
                QApplication.activeWindow(), 'TreeLine',
                _('Error - could not read file {0}').format(pathObj))
            self.recentFiles.removeItem(pathObj)
        except (ValueError, KeyError, TypeError):
            fileObj = pathObj.open('rb')
            fileObj, encrypted = self.decryptFile(fileObj)
            if not fileObj:
                if not self.localControls:
                    self.createLocalControl()
                QApplication.restoreOverrideCursor()
                return
            fileObj, compressed = self.decompressFile(fileObj)
            if compressed or encrypted:
                try:
                    textFileObj = io.TextIOWrapper(fileObj, encoding='utf-8')
                    self.createLocalControl(textFileObj, None, forceNewWindow)
                    fileObj.close()
                    textFileObj.close()
                    self.recentFiles.addItem(pathObj)
                    if not (globalref.genOptions['SaveTreeStates']
                            and self.recentFiles.retrieveTreeState(
                                self.activeControl)):
                        self.activeControl.expandRootNodes()
                        self.activeControl.selectRootSpot()
                    self.activeControl.compressed = compressed
                    self.activeControl.encrypted = encrypted
                    QApplication.restoreOverrideCursor()
                    return
                except (ValueError, KeyError, TypeError):
                    pass
            fileObj.close()
            importControl = imports.ImportControl(pathObj)
            structure = importControl.importOldTreeLine()
            if structure:
                self.createLocalControl(pathObj, structure, forceNewWindow)
                self.activeControl.printData.readData(
                    importControl.treeLineRootAttrib)
                self.recentFiles.addItem(pathObj)
                self.activeControl.expandRootNodes()
                self.activeControl.imported = True
                QApplication.restoreOverrideCursor()
                return
            QApplication.restoreOverrideCursor()
            if importOnFail:
                importControl = imports.ImportControl(pathObj)
                structure = importControl.interactiveImport(True)
                if structure:
                    self.createLocalControl(pathObj, structure, forceNewWindow)
                    self.activeControl.imported = True
                    return
            else:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - invalid TreeLine file {0}').format(pathObj))
                self.recentFiles.removeItem(pathObj)
        if not self.localControls:
            self.createLocalControl()

    def decryptFile(self, fileObj):
        """Check for encryption and decrypt the fileObj if needed.

        Return a tuple of the file object and True if it was encrypted.
        Return None for the file object if the user cancels.
        Arguments:
            fileObj -- the file object to check and decrypt
        """
        if fileObj.read(len(encryptPrefix)) != encryptPrefix:
            fileObj.seek(0)
            return (fileObj, False)
        while True:
            pathObj = pathlib.Path(fileObj.name)
            password = self.passwords.get(pathObj, '')
            if not password:
                QApplication.restoreOverrideCursor()
                dialog = miscdialogs.PasswordDialog(
                    False, pathObj.name, QApplication.activeWindow())
                if dialog.exec_() != QDialog.Accepted:
                    fileObj.close()
                    return (None, True)
                QApplication.setOverrideCursor(Qt.WaitCursor)
                password = dialog.password
                if miscdialogs.PasswordDialog.remember:
                    self.passwords[pathObj] = password
            try:
                text = p3.p3_decrypt(fileObj.read(), password.encode())
                fileIO = io.BytesIO(text)
                fileIO.name = fileObj.name
                fileObj.close()
                return (fileIO, True)
            except p3.CryptError:
                try:
                    del self.passwords[pathObj]
                except KeyError:
                    pass

    def decompressFile(self, fileObj):
        """Check for compression and decompress the fileObj if needed.

        Return a tuple of the file object and True if it was compressed.
        Arguments:
            fileObj -- the file object to check and decompress
        """
        prefix = fileObj.read(2)
        fileObj.seek(0)
        if prefix != b'\037\213':
            return (fileObj, False)
        try:
            newFileObj = gzip.GzipFile(fileobj=fileObj)
        except zlib.error:
            return (fileObj, False)
        newFileObj.name = fileObj.name
        return (newFileObj, True)

    def checkAutoSave(self, pathObj):
        """Check for presence of auto save file & prompt user.

        Return True if OK to contimue, False if aborting or already loaded.
        Arguments:
            pathObj -- the base path object to search for a backup
        """
        if not globalref.genOptions['AutoSaveMinutes']:
            return True
        basePath = pathObj
        pathObj = pathlib.Path(str(pathObj) + '~')
        if not pathObj.is_file():
            return True
        msgBox = QMessageBox(
            QMessageBox.Information, 'TreeLine',
            _('Backup file "{}" exists.\nA previous '
              'session may have crashed').format(pathObj),
            QMessageBox.NoButton, QApplication.activeWindow())
        restoreButton = msgBox.addButton(_('&Restore Backup'),
                                         QMessageBox.ApplyRole)
        deleteButton = msgBox.addButton(_('&Delete Backup'),
                                        QMessageBox.DestructiveRole)
        cancelButton = msgBox.addButton(_('&Cancel File Open'),
                                        QMessageBox.RejectRole)
        msgBox.exec_()
        if msgBox.clickedButton() == restoreButton:
            self.openFile(pathObj)
            if self.activeControl.filePathObj != pathObj:
                return False
            try:
                basePath.unlink()
                pathObj.rename(basePath)
            except OSError:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - could not rename "{0}" to "{1}"').format(
                        pathObj, basePath))
                return False
            self.activeControl.filePathObj = basePath
            self.activeControl.updateWindowCaptions()
            self.recentFiles.removeItem(pathObj)
            self.recentFiles.addItem(basePath)
            return False
        elif msgBox.clickedButton() == deleteButton:
            try:
                pathObj.unlink()
            except OSError:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - could not remove backup file {}').format(
                        pathObj))
        else:  # cancel button
            return False
        return True

    def createLocalControl(self,
                           pathObj=None,
                           treeStruct=None,
                           forceNewWindow=False):
        """Create a new local control object and add it to the list.

        Use an imported structure if given or open the file if path is given.
        Arguments:
            pathObj -- the path object or file object for the control to open
            treeStruct -- the imported structure to use
            forceNewWindow -- if True, use a new window regardless of option
        """
        localControl = treelocalcontrol.TreeLocalControl(
            self.allActions, pathObj, treeStruct, forceNewWindow)
        localControl.controlActivated.connect(self.updateLocalControlRef)
        localControl.controlClosed.connect(self.removeLocalControlRef)
        self.localControls.append(localControl)
        self.updateLocalControlRef(localControl)
        localControl.updateRightViews()
        localControl.updateCommandsAvail()

    def updateLocalControlRef(self, localControl):
        """Set the given local control as active.

        Called by signal from a window becoming active.
        Also updates non-modal dialogs.
        Arguments:
            localControl -- the new active local control
        """
        if localControl != self.activeControl:
            self.activeControl = localControl
            if self.configDialog and self.configDialog.isVisible():
                self.configDialog.setRefs(self.activeControl)

    def removeLocalControlRef(self, localControl):
        """Remove ref to local control based on a closing signal.

        Also do application exit clean ups if last control closing.
        Arguments:
            localControl -- the local control that is closing
        """
        self.localControls.remove(localControl)
        if globalref.genOptions['SaveTreeStates']:
            self.recentFiles.saveTreeState(localControl)
        if not self.localControls:
            if globalref.genOptions['SaveWindowGeom']:
                localControl.windowList[0].saveWindowGeom()
            else:
                localControl.windowList[0].resetWindowGeom()
            self.recentFiles.writeItems()
            localControl.windowList[0].saveToolbarPosition()
            globalref.histOptions.writeFile()
            if self.trayIcon:
                self.trayIcon.hide()
        localControl.deleteLater()

    def createTrayIcon(self):
        """Create a new system tray icon if not already created.
        """
        if QSystemTrayIcon.isSystemTrayAvailable:
            if not self.trayIcon:
                self.trayIcon = QSystemTrayIcon(qApp.windowIcon(), qApp)
                self.trayIcon.activated.connect(self.toggleTrayShow)
            self.trayIcon.show()

    def trayMinimize(self):
        """Minimize to tray based on window minimize signal.
        """
        if self.trayIcon and QSystemTrayIcon.isSystemTrayAvailable:
            # skip minimize to tray if not all windows minimized
            for control in self.localControls:
                for window in control.windowList:
                    if not window.isMinimized():
                        return
            for control in self.localControls:
                for window in control.windowList:
                    window.hide()
            self.isTrayMinimized = True

    def toggleTrayShow(self):
        """Toggle show and hide application based on system tray icon click.
        """
        if self.isTrayMinimized:
            for control in self.localControls:
                for window in control.windowList:
                    window.show()
                    window.showNormal()
            self.activeControl.activeWindow.treeView.setFocus()
        else:
            for control in self.localControls:
                for window in control.windowList:
                    window.hide()
        self.isTrayMinimized = not self.isTrayMinimized

    def updateConfigDialog(self):
        """Update the config dialog for changes if it exists.
        """
        if self.configDialog:
            self.configDialog.reset()

    def currentStatusBar(self):
        """Return the status bar from the current main window.
        """
        return self.activeControl.activeWindow.statusBar()

    def windowActions(self):
        """Return a list of window menu actions from each local control.
        """
        actions = []
        for control in self.localControls:
            actions.extend(
                control.windowActions(
                    len(actions) + 1, control == self.activeControl))
        return actions

    def updateActionsAvail(self, oldWidget, newWidget):
        """Update command availability based on focus changes.

        Arguments:
            oldWidget -- the previously focused widget
            newWidget -- the newly focused widget
        """
        self.allActions['FormatSelectAll'].setEnabled(
            hasattr(newWidget, 'selectAll')
            and not hasattr(newWidget, 'editTriggers'))

    def setupActions(self):
        """Add the actions for contols at the global level.
        """
        fileNewAct = QAction(_('&New...'),
                             self,
                             toolTip=_('New File'),
                             statusTip=_('Start a new file'))
        fileNewAct.triggered.connect(self.fileNew)
        self.allActions['FileNew'] = fileNewAct

        fileOpenAct = QAction(_('&Open...'),
                              self,
                              toolTip=_('Open File'),
                              statusTip=_('Open a file from disk'))
        fileOpenAct.triggered.connect(self.fileOpen)
        self.allActions['FileOpen'] = fileOpenAct

        fileSampleAct = QAction(_('Open Sa&mple...'),
                                self,
                                toolTip=_('Open Sample'),
                                statusTip=_('Open a sample file'))
        fileSampleAct.triggered.connect(self.fileOpenSample)
        self.allActions['FileOpenSample'] = fileSampleAct

        fileImportAct = QAction(_('&Import...'),
                                self,
                                statusTip=_('Open a non-TreeLine file'))
        fileImportAct.triggered.connect(self.fileImport)
        self.allActions['FileImport'] = fileImportAct

        fileQuitAct = QAction(_('&Quit'),
                              self,
                              statusTip=_('Exit the application'))
        fileQuitAct.triggered.connect(self.fileQuit)
        self.allActions['FileQuit'] = fileQuitAct

        dataConfigAct = QAction(
            _('&Configure Data Types...'),
            self,
            statusTip=_('Modify data types, fields & output lines'),
            checkable=True)
        dataConfigAct.triggered.connect(self.dataConfigDialog)
        self.allActions['DataConfigType'] = dataConfigAct

        dataVisualConfigAct = QAction(
            _('Show C&onfiguration Structure...'),
            self,
            statusTip=_('Show read-only visualization of type structure'))
        dataVisualConfigAct.triggered.connect(self.dataVisualConfig)
        self.allActions['DataVisualConfig'] = dataVisualConfigAct

        dataSortAct = QAction(_('Sor&t Nodes...'),
                              self,
                              statusTip=_('Define node sort operations'),
                              checkable=True)
        dataSortAct.triggered.connect(self.dataSortDialog)
        self.allActions['DataSortNodes'] = dataSortAct

        dataNumberingAct = QAction(_('Update &Numbering...'),
                                   self,
                                   statusTip=_('Update node numbering fields'),
                                   checkable=True)
        dataNumberingAct.triggered.connect(self.dataNumberingDialog)
        self.allActions['DataNumbering'] = dataNumberingAct

        toolsFindTextAct = QAction(
            _('&Find Text...'),
            self,
            statusTip=_('Find text in node titles & data'),
            checkable=True)
        toolsFindTextAct.triggered.connect(self.toolsFindTextDialog)
        self.allActions['ToolsFindText'] = toolsFindTextAct

        toolsFindConditionAct = QAction(
            _('&Conditional Find...'),
            self,
            statusTip=_('Use field conditions to find nodes'),
            checkable=True)
        toolsFindConditionAct.triggered.connect(self.toolsFindConditionDialog)
        self.allActions['ToolsFindCondition'] = toolsFindConditionAct

        toolsFindReplaceAct = QAction(
            _('Find and &Replace...'),
            self,
            statusTip=_('Replace text strings in node data'),
            checkable=True)
        toolsFindReplaceAct.triggered.connect(self.toolsFindReplaceDialog)
        self.allActions['ToolsFindReplace'] = toolsFindReplaceAct

        toolsFilterTextAct = QAction(
            _('&Text Filter...'),
            self,
            statusTip=_('Filter nodes to only show text matches'),
            checkable=True)
        toolsFilterTextAct.triggered.connect(self.toolsFilterTextDialog)
        self.allActions['ToolsFilterText'] = toolsFilterTextAct

        toolsFilterConditionAct = QAction(
            _('C&onditional Filter...'),
            self,
            statusTip=_('Use field conditions to filter nodes'),
            checkable=True)
        toolsFilterConditionAct.triggered.connect(
            self.toolsFilterConditionDialog)
        self.allActions['ToolsFilterCondition'] = toolsFilterConditionAct

        toolsGenOptionsAct = QAction(
            _('&General Options...'),
            self,
            statusTip=_('Set user preferences for all files'))
        toolsGenOptionsAct.triggered.connect(self.toolsGenOptions)
        self.allActions['ToolsGenOptions'] = toolsGenOptionsAct

        toolsShortcutAct = QAction(_('Set &Keyboard Shortcuts...'),
                                   self,
                                   statusTip=_('Customize keyboard commands'))
        toolsShortcutAct.triggered.connect(self.toolsCustomShortcuts)
        self.allActions['ToolsShortcuts'] = toolsShortcutAct

        toolsToolbarAct = QAction(_('C&ustomize Toolbars...'),
                                  self,
                                  statusTip=_('Customize toolbar buttons'))
        toolsToolbarAct.triggered.connect(self.toolsCustomToolbars)
        self.allActions['ToolsToolbars'] = toolsToolbarAct

        toolsFontsAct = QAction(
            _('Customize Fo&nts...'),
            self,
            statusTip=_('Customize fonts in various views'))
        toolsFontsAct.triggered.connect(self.toolsCustomFonts)
        self.allActions['ToolsFonts'] = toolsFontsAct

        helpBasicAct = QAction(_('&Basic Usage...'),
                               self,
                               statusTip=_('Display basic usage instructions'))
        helpBasicAct.triggered.connect(self.helpViewBasic)
        self.allActions['HelpBasic'] = helpBasicAct

        helpFullAct = QAction(
            _('&Full Documentation...'),
            self,
            statusTip=_('Open a TreeLine file with full documentation'))
        helpFullAct.triggered.connect(self.helpViewFull)
        self.allActions['HelpFull'] = helpFullAct

        helpAboutAct = QAction(
            _('&About TreeLine...'),
            self,
            statusTip=_('Display version info about this program'))
        helpAboutAct.triggered.connect(self.helpAbout)
        self.allActions['HelpAbout'] = helpAboutAct

        formatSelectAllAct = QAction(
            _('&Select All'),
            self,
            statusTip=_('Select all text in an editor'))
        formatSelectAllAct.setEnabled(False)
        formatSelectAllAct.triggered.connect(self.formatSelectAll)
        self.allActions['FormatSelectAll'] = formatSelectAllAct

        helpAboutAct = QAction(
            _('&About TreeLine...'),
            self,
            statusTip=_('Display version info about this program'))
        helpAboutAct.triggered.connect(self.helpAbout)
        self.allActions['HelpAbout'] = helpAboutAct

        for name, action in self.allActions.items():
            icon = globalref.toolIcons.getIcon(name.lower())
            if icon:
                action.setIcon(icon)
            key = globalref.keyboardOptions[name]
            if not key.isEmpty():
                action.setShortcut(key)

    def fileNew(self):
        """Start a new blank file.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            searchPaths = self.findResourcePaths('templates', templatePath)
            if searchPaths:
                dialog = miscdialogs.TemplateFileDialog(
                    _('New File'), _('&Select Template'), searchPaths)
                if dialog.exec_() == QDialog.Accepted:
                    self.createLocalControl(dialog.selectedPath())
                    self.activeControl.filePathObj = None
                    self.activeControl.updateWindowCaptions()
                    self.activeControl.expandRootNodes()
            else:
                self.createLocalControl()
            self.activeControl.selectRootSpot()

    def fileOpen(self):
        """Prompt for a filename and open it.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            filters = ';;'.join((globalref.fileFilters['trlnopen'],
                                 globalref.fileFilters['all']))
            fileName, selFilter = QFileDialog.getOpenFileName(
                QApplication.activeWindow(), _('TreeLine - Open File'),
                str(self.defaultPathObj(True)), filters)
            if fileName:
                self.openFile(pathlib.Path(fileName))

    def fileOpenSample(self):
        """Open a sample file from the doc directories.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            searchPaths = self.findResourcePaths('samples', samplePath)
            dialog = miscdialogs.TemplateFileDialog(_('Open Sample File'),
                                                    _('&Select Sample'),
                                                    searchPaths, False)
            if dialog.exec_() == QDialog.Accepted:
                self.createLocalControl(dialog.selectedPath())
                name = dialog.selectedName() + '.trln'
                self.activeControl.filePathObj = pathlib.Path(name)
                self.activeControl.updateWindowCaptions()
                self.activeControl.expandRootNodes()
                self.activeControl.imported = True

    def fileImport(self):
        """Prompt for an import type, then a file to import.
        """
        importControl = imports.ImportControl()
        structure = importControl.interactiveImport()
        if structure:
            self.createLocalControl(importControl.pathObj, structure)
            if importControl.treeLineRootAttrib:
                self.activeControl.printData.readData(
                    importControl.treeLineRootAttrib)
            self.activeControl.imported = True

    def fileQuit(self):
        """Close all windows to exit the applications.
        """
        for control in self.localControls[:]:
            control.closeWindows()

    def dataConfigDialog(self, show):
        """Show or hide the non-modal data config dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.configDialog:
                self.configDialog = configdialog.ConfigDialog()
                dataConfigAct = self.allActions['DataConfigType']
                self.configDialog.dialogShown.connect(dataConfigAct.setChecked)
            self.configDialog.setRefs(self.activeControl, True)
            self.configDialog.show()
        else:
            self.configDialog.close()

    def dataVisualConfig(self):
        """Show a TreeLine file to visualize the config structure.
        """
        structure = (
            self.activeControl.structure.treeFormats.visualConfigStructure(
                str(self.activeControl.filePathObj)))
        self.createLocalControl(treeStruct=structure, forceNewWindow=True)
        self.activeControl.filePathObj = pathlib.Path('structure.trln')
        self.activeControl.updateWindowCaptions()
        self.activeControl.expandRootNodes()
        self.activeControl.imported = True
        win = self.activeControl.activeWindow
        win.rightTabs.setCurrentWidget(win.outputSplitter)

    def dataSortDialog(self, show):
        """Show or hide the non-modal data sort nodes dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.sortDialog:
                self.sortDialog = miscdialogs.SortDialog()
                dataSortAct = self.allActions['DataSortNodes']
                self.sortDialog.dialogShown.connect(dataSortAct.setChecked)
            self.sortDialog.show()
        else:
            self.sortDialog.close()

    def dataNumberingDialog(self, show):
        """Show or hide the non-modal update node numbering dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.numberingDialog:
                self.numberingDialog = miscdialogs.NumberingDialog()
                dataNumberingAct = self.allActions['DataNumbering']
                self.numberingDialog.dialogShown.connect(
                    dataNumberingAct.setChecked)
            self.numberingDialog.show()
            if not self.numberingDialog.checkForNumberingFields():
                self.numberingDialog.close()
        else:
            self.numberingDialog.close()

    def toolsFindTextDialog(self, show):
        """Show or hide the non-modal find text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findTextDialog:
                self.findTextDialog = miscdialogs.FindFilterDialog()
                toolsFindTextAct = self.allActions['ToolsFindText']
                self.findTextDialog.dialogShown.connect(
                    toolsFindTextAct.setChecked)
            self.findTextDialog.selectAllText()
            self.findTextDialog.show()
        else:
            self.findTextDialog.close()

    def toolsFindConditionDialog(self, show):
        """Show or hide the non-modal conditional find dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findConditionDialog:
                dialogType = conditional.FindDialogType.findDialog
                self.findConditionDialog = (conditional.ConditionDialog(
                    dialogType, _('Conditional Find')))
                toolsFindConditionAct = self.allActions['ToolsFindCondition']
                (self.findConditionDialog.dialogShown.connect(
                    toolsFindConditionAct.setChecked))
            else:
                self.findConditionDialog.loadTypeNames()
            self.findConditionDialog.show()
        else:
            self.findConditionDialog.close()

    def toolsFindReplaceDialog(self, show):
        """Show or hide the non-modal find and replace text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findReplaceDialog:
                self.findReplaceDialog = miscdialogs.FindReplaceDialog()
                toolsFindReplaceAct = self.allActions['ToolsFindReplace']
                self.findReplaceDialog.dialogShown.connect(
                    toolsFindReplaceAct.setChecked)
            else:
                self.findReplaceDialog.loadTypeNames()
            self.findReplaceDialog.show()
        else:
            self.findReplaceDialog.close()

    def toolsFilterTextDialog(self, show):
        """Show or hide the non-modal filter text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.filterTextDialog:
                self.filterTextDialog = miscdialogs.FindFilterDialog(True)
                toolsFilterTextAct = self.allActions['ToolsFilterText']
                self.filterTextDialog.dialogShown.connect(
                    toolsFilterTextAct.setChecked)
            self.filterTextDialog.selectAllText()
            self.filterTextDialog.show()
        else:
            self.filterTextDialog.close()

    def toolsFilterConditionDialog(self, show):
        """Show or hide the non-modal conditional filter dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.filterConditionDialog:
                dialogType = conditional.FindDialogType.filterDialog
                self.filterConditionDialog = (conditional.ConditionDialog(
                    dialogType, _('Conditional Filter')))
                toolsFilterConditionAct = (
                    self.allActions['ToolsFilterCondition'])
                (self.filterConditionDialog.dialogShown.connect(
                    toolsFilterConditionAct.setChecked))
            else:
                self.filterConditionDialog.loadTypeNames()
            self.filterConditionDialog.show()
        else:
            self.filterConditionDialog.close()

    def toolsGenOptions(self):
        """Set general user preferences for all files.
        """
        oldAutoSaveMinutes = globalref.genOptions['AutoSaveMinutes']
        oldColorTheme = globalref.genOptions['ColorTheme']
        dialog = options.OptionDialog(globalref.genOptions,
                                      QApplication.activeWindow())
        dialog.setWindowTitle(_('General Options'))
        if (dialog.exec_() == QDialog.Accepted
                and globalref.genOptions.modified):
            globalref.genOptions.writeFile()
            self.recentFiles.updateOptions()
            if globalref.genOptions['MinToSysTray']:
                self.createTrayIcon()
            elif self.trayIcon:
                self.trayIcon.hide()
            autoSaveMinutes = globalref.genOptions['AutoSaveMinutes']
            for control in self.localControls:
                for window in control.windowList:
                    window.updateWinGenOptions()
                control.structure.undoList.setNumLevels()
                control.updateAll(False)
                if autoSaveMinutes != oldAutoSaveMinutes:
                    control.resetAutoSave()
            if globalref.genOptions['ColorTheme'] != oldColorTheme:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Application must be restarted for '
                      'color theme changes to take effect'))

    def toolsCustomShortcuts(self):
        """Show dialog to customize keyboard commands.
        """
        actions = self.activeControl.activeWindow.allActions
        dialog = miscdialogs.CustomShortcutsDialog(actions,
                                                   QApplication.activeWindow())
        dialog.exec_()

    def toolsCustomToolbars(self):
        """Show dialog to customize toolbar buttons.
        """
        actions = self.activeControl.activeWindow.allActions
        dialog = miscdialogs.CustomToolbarDialog(actions, self.updateToolbars,
                                                 QApplication.activeWindow())
        dialog.exec_()

    def updateToolbars(self):
        """Update toolbars after changes in custom toolbar dialog.
        """
        for control in self.localControls:
            for window in control.windowList:
                window.setupToolbars()

    def toolsCustomFonts(self):
        """Show dialog to customize fonts in various views.
        """
        dialog = miscdialogs.CustomFontDialog(QApplication.activeWindow())
        dialog.updateRequired.connect(self.updateCustomFonts)
        dialog.exec_()

    def updateCustomFonts(self):
        """Update fonts in all windows based on a dialog signal.
        """
        self.updateAppFont()
        for control in self.localControls:
            for window in control.windowList:
                window.updateFonts()
            control.printData.setDefaultFont()
        for control in self.localControls:
            control.updateAll(False)

    def updateAppFont(self):
        """Update application default font from settings.
        """
        appFont = QFont(self.systemFont)
        appFontName = globalref.miscOptions['AppFont']
        if appFontName:
            appFont.fromString(appFontName)
        QApplication.setFont(appFont)

    def formatSelectAll(self):
        """Select all text in any currently focused editor.
        """
        try:
            QApplication.focusWidget().selectAll()
        except AttributeError:
            pass

    def helpViewBasic(self):
        """Display basic usage instructions.
        """
        if not self.basicHelpView:
            path = self.findResourceFile('basichelp.html', 'doc', docPath)
            if not path:
                QMessageBox.warning(QApplication.activeWindow(), 'TreeLine',
                                    _('Error - basic help file not found'))
                return
            self.basicHelpView = helpview.HelpView(path,
                                                   _('TreeLine Basic Usage'),
                                                   globalref.toolIcons)
        self.basicHelpView.show()

    def helpViewFull(self):
        """Open a TreeLine file with full documentation.
        """
        path = self.findResourceFile('documentation.trln', 'doc', docPath)
        if not path:
            QMessageBox.warning(QApplication.activeWindow(), 'TreeLine',
                                _('Error - documentation file not found'))
            return
        self.createLocalControl(path, forceNewWindow=True)
        self.activeControl.filePathObj = pathlib.Path('documentation.trln')
        self.activeControl.updateWindowCaptions()
        self.activeControl.expandRootNodes()
        self.activeControl.imported = True
        win = self.activeControl.activeWindow
        win.rightTabs.setCurrentWidget(win.outputSplitter)

    def helpAbout(self):
        """ Display version info about this program.
        """
        pyVersion = '.'.join([repr(num) for num in sys.version_info[:3]])
        textLines = [
            _('TreeLine version {0}').format(__version__),
            _('written by {0}').format(__author__), '',
            _('Library versions:'), '   Python:  {0}'.format(pyVersion),
            '   Qt:  {0}'.format(qVersion()),
            '   PyQt:  {0}'.format(PYQT_VERSION_STR),
            '   OS:  {0}'.format(platform.platform())
        ]
        dialog = miscdialogs.AboutDialog('TreeLine', textLines,
                                         QApplication.windowIcon(),
                                         QApplication.activeWindow())
        dialog.exec_()
Exemplo n.º 25
0
class QSingleApplication(QApplication):

    messageReceived = pyqtSignal(str)

    def __init__(self, id, *argv):

        super(QSingleApplication, self).__init__(*argv)
        self._id = id
        self._activationWindow = None
        self._activateOnMessage = False
        self._server = None

        # Is there another instance running?
        self._outSocket = QLocalSocket()
        self._outSocket.connectToServer(self._id)
        self._outSocket.error.connect(self.handleError)
        self._isRunning = self._outSocket.waitForConnected()

        if self._isRunning:
            # Yes, there is.
            self._outStream = QTextStream(self._outSocket)
            self._outStream.setCodec('UTF-8')
        else:
            # No, there isn't.
            self._outSocket = None
            self._outStream = None
            self._inSocket = None
            self._inStream = None
            self._server = QLocalServer()
            self._server.listen(self._id)
            self._server.newConnection.connect(self._onNewConnection)
            self.aboutToQuit.connect(self.removeServer)

    def handleError(self, msg):
        print(msg)

    def server(self):
        return self._server

    def isRunning(self):
        return self._isRunning

    def id(self):
        return self._id

    def activationWindow(self):
        return self._activationWindow

    def setActivationWindow(self, activationWindow, activateOnMessage=True):
        self._activationWindow = activationWindow
        self._activateOnMessage = activateOnMessage

    def activateWindow(self):
        if not self._activationWindow:
            print("No registered ActivationWindow")
            return
        # Unfortunately this *doesn't* do much of any use, as it won't
        # bring the window to the foreground under KDE... sigh.
        self._activationWindow.setWindowState(
            self._activationWindow.windowState() & ~Qt.WindowMinimized)
        self._activationWindow.raise_()
        self._activationWindow.requestActivate()

    def sendMessage(self, msg, msecs=5000):
        if not self._outStream:
            return False
        self._outStream << msg << ''
        if not self._outSocket.waitForBytesWritten(msecs):
            raise RuntimeError("Bytes not written within %ss" %
                               (msecs / 1000.))

    def _onNewConnection(self):
        if self._inSocket:
            self._inSocket.readyRead.disconnect(self._onReadyRead)
        self._inSocket = self._server.nextPendingConnection()
        if not self._inSocket:
            return
        self._inStream = QTextStream(self._inSocket)
        self._inStream.setCodec('UTF-8')
        self._inSocket.readyRead.connect(self._onReadyRead)
        if self._activateOnMessage:
            self.activateWindow()

    def _onReadyRead(self):
        while True:
            msg = self._inStream.readLine()
            if not msg:
                break
            print("Message received")
            self.messageReceived.emit(msg)

    def removeServer(self):
        self._server.close()
        self._server.removeServer(self._id)
Exemplo n.º 26
0
class SingleApplication(QApplication):
    messageReceived = pyqtSignal(str)

    def __init__(self, appid, *argv):
        super(SingleApplication, self).__init__(*argv)
        self._appid = appid
        self._activationWindow = None
        self._activateOnMessage = False
        self._outSocket = QLocalSocket()
        self._outSocket.connectToServer(self._appid)
        self._isRunning = self._outSocket.waitForConnected()
        self._outStream = None
        self._inSocket = None
        self._inStream = None
        self._server = None
        self.settings = QSettings(SingleApplication.getSettingsPath(),
                                  QSettings.IniFormat)
        self.singleInstance = self.settings.value('singleInstance',
                                                  'on',
                                                  type=str) in {'on', 'true'}
        if self._isRunning and self.singleInstance:
            self._outStream = QTextStream(self._outSocket)
            for a in argv[0][1:]:
                a = os.path.join(os.getcwd(), a)
                if os.path.isfile(a):
                    self.sendMessage(a)
                    break
            sys.exit(0)
        else:
            error = self._outSocket.error()
            if error == QLocalSocket.ConnectionRefusedError:
                self.close()
                QLocalServer.removeServer(self._appid)
            self._outSocket = None
            self._server = QLocalServer()
            self._server.listen(self._appid)
            self._server.newConnection.connect(self._onNewConnection)

    def close(self):
        if self._inSocket:
            self._inSocket.disconnectFromServer()
        if self._outSocket:
            self._outSocket.disconnectFromServer()
        if self._server:
            self._server.close()

    @staticmethod
    def getSettingsPath() -> str:
        if sys.platform == 'win32':
            settings_path = os.path.join(QDir.homePath(), 'AppData', 'Local',
                                         'vidcutter')
        elif sys.platform == 'darwin':
            settings_path = os.path.join(QDir.homePath(), 'Library',
                                         'Preferences', 'vidcutter')
        else:
            if QFileInfo(__file__).absolutePath().startswith('/app/'):
                settings_path = QProcessEnvironment.systemEnvironment().value(
                    'XDG_CONFIG_HOME', '')
                if not len(settings_path):
                    settings_path = os.path.join(QDir.homePath(), '.var',
                                                 'app',
                                                 vidcutter.__desktopid__,
                                                 'config')
            else:
                settings_path = os.path.join(QDir.homePath(), '.config',
                                             'vidcutter')
        return os.path.join(settings_path, 'vidcutter.ini')

    def isRunning(self):
        return self._isRunning

    def appid(self):
        return self._appid

    def activationWindow(self):
        return self._activationWindow

    def setActivationWindow(self, activationWindow, activateOnMessage=True):
        self._activationWindow = activationWindow
        self._activateOnMessage = activateOnMessage

    def activateWindow(self):
        if not self._activationWindow:
            return
        self._activationWindow.setWindowState(
            self._activationWindow.windowState() & ~Qt.WindowMinimized)
        self._activationWindow.raise_()
        self._activationWindow.activateWindow()

    def sendMessage(self, msg):
        if not self._outStream:
            return False
        # noinspection PyUnresolvedReferences
        self._outStream << msg << '\n'
        self._outStream.flush()
        return self._outSocket.waitForBytesWritten()

    def _onNewConnection(self):
        if self._inSocket:
            self._inSocket.readyRead.disconnect(self._onReadyRead)
        self._inSocket = self._server.nextPendingConnection()
        if not self._inSocket:
            return
        self._inStream = QTextStream(self._inSocket)
        self._inSocket.readyRead.connect(self._onReadyRead)
        if self._activateOnMessage:
            self.activateWindow()

    def _onReadyRead(self):
        while True:
            msg = self._inStream.readLine()
            if not msg:
                break
            self.messageReceived.emit(msg)
Exemplo n.º 27
0
class BlenderLauncher(QMainWindow, BaseWindow, Ui_MainWindow):
    show_signal = pyqtSignal()
    close_signal = pyqtSignal()

    def __init__(self, app):
        super().__init__()
        self.setupUi(self)
        self.setAcceptDrops(True)

        # Server
        self.server = QLocalServer()
        self.server.listen("blender-launcher-server")
        self.server.newConnection.connect(self.new_connection)

        # Global scope
        self.app = app
        self.favorite = None
        self.status = "None"
        self.app_state = AppState.IDLE
        self.cashed_builds = []
        self.notification_pool = []
        self.windows = [self]
        self.manager = PoolManager(num_pools=50, maxsize=10)
        self.timer = None
        self.started = True
        self.latest_tag = ""
        self.new_downloads = False

        # Setup window
        self.setWindowTitle("Blender Launcher")
        self.app.setWindowIcon(
            QIcon(taskbar_icon_paths[get_taskbar_icon_color()]))

        # Setup font
        QFontDatabase.addApplicationFont(
            ":/resources/fonts/OpenSans-SemiBold.ttf")
        self.font = QFont("Open Sans SemiBold", 10)
        self.font.setHintingPreference(QFont.PreferNoHinting)
        self.app.setFont(self.font)

        # Setup style
        file = QFile(":/resources/styles/global.qss")
        file.open(QFile.ReadOnly | QFile.Text)
        self.style_sheet = QTextStream(file).readAll()
        self.app.setStyleSheet(self.style_sheet)

        # Check library folder
        if is_library_folder_valid() is False:
            self.dlg = DialogWindow(
                self,
                title="Information",
                text="First, choose where Blender<br>builds will be stored",
                accept_text="Continue",
                cancel_text=None,
                icon=DialogIcon.INFO)
            self.dlg.accepted.connect(self.set_library_folder)
        else:
            create_library_folders(get_library_folder())
            self.draw()

    def set_library_folder(self):
        library_folder = Path.cwd().as_posix()
        new_library_folder = QFileDialog.getExistingDirectory(
            self,
            "Select Library Folder",
            library_folder,
            options=QFileDialog.DontUseNativeDialog | QFileDialog.ShowDirsOnly)

        if new_library_folder:
            set_library_folder(new_library_folder)
            self.draw(True)
        else:
            self.app.quit()

    def draw(self, polish=False):
        self.HeaderLayout = QHBoxLayout()
        self.HeaderLayout.setContentsMargins(1, 1, 1, 0)
        self.HeaderLayout.setSpacing(0)
        self.CentralLayout.addLayout(self.HeaderLayout)

        self.SettingsButton = \
            QPushButton(QIcon(":resources/icons/settings.svg"), "")
        self.SettingsButton.setIconSize(QSize(20, 20))
        self.SettingsButton.setFixedSize(36, 32)
        self.SettingsButton.setToolTip("Show settings window")
        self.WikiButton = \
            QPushButton(QIcon(":resources/icons/wiki.svg"), "")
        self.WikiButton.setIconSize(QSize(20, 20))
        self.WikiButton.setFixedSize(36, 32)
        self.WikiButton.setToolTip("Open documentation")
        self.MinimizeButton = \
            QPushButton(QIcon(":resources/icons/minimize.svg"), "")
        self.MinimizeButton.setIconSize(QSize(20, 20))
        self.MinimizeButton.setFixedSize(36, 32)
        self.CloseButton = \
            QPushButton(QIcon(":resources/icons/close.svg"), "")
        self.CloseButton.setIconSize(QSize(20, 20))
        self.CloseButton.setFixedSize(36, 32)
        self.HeaderLabel = QLabel("Blender Launcher")
        self.HeaderLabel.setAlignment(Qt.AlignCenter)

        self.HeaderLayout.addWidget(self.SettingsButton, 0, Qt.AlignLeft)
        self.HeaderLayout.addWidget(self.WikiButton, 0, Qt.AlignLeft)
        self.HeaderLayout.addWidget(self.HeaderLabel, 1)
        self.HeaderLayout.addWidget(self.MinimizeButton, 0, Qt.AlignRight)
        self.HeaderLayout.addWidget(self.CloseButton, 0, Qt.AlignRight)

        self.SettingsButton.setProperty("HeaderButton", True)
        self.WikiButton.setProperty("HeaderButton", True)
        self.MinimizeButton.setProperty("HeaderButton", True)
        self.CloseButton.setProperty("HeaderButton", True)
        self.CloseButton.setProperty("CloseButton", True)

        # Tab layout
        self.TabWidget = QTabWidget()
        self.CentralLayout.addWidget(self.TabWidget)

        self.LibraryTab = QWidget()
        self.LibraryTabLayout = QVBoxLayout()
        self.LibraryTabLayout.setContentsMargins(0, 0, 0, 0)
        self.LibraryTab.setLayout(self.LibraryTabLayout)
        self.TabWidget.addTab(self.LibraryTab, "Library")

        self.DownloadsTab = QWidget()
        self.DownloadsTabLayout = QVBoxLayout()
        self.DownloadsTabLayout.setContentsMargins(0, 0, 0, 0)
        self.DownloadsTab.setLayout(self.DownloadsTabLayout)
        self.TabWidget.addTab(self.DownloadsTab, "Downloads")

        self.LibraryToolBox = BaseToolBoxWidget(self)

        self.LibraryStableListWidget = \
            self.LibraryToolBox.add_list_widget(
                "Stable Releases",
                "LibraryStableListWidget",
                "Nothing to show yet",
                "Commit Time",
                extended_selection=True)
        self.LibraryDailyListWidget = \
            self.LibraryToolBox.add_list_widget(
                "Daily Builds",
                "LibraryDailyListWidget",
                "Nothing to show yet",
                "Commit Time",
                extended_selection=True)
        self.LibraryExperimentalListWidget = \
            self.LibraryToolBox.add_list_widget(
                "Experimental Branches",
                "LibraryExperimentalListWidget",
                "Nothing to show yet",
                "Commit Time",
                extended_selection=True)
        self.LibraryCustomListWidget = \
            self.LibraryToolBox.add_list_widget(
                "Custom Builds",
                "LibraryCustomListWidget",
                "Nothing to show yet",
                "Commit Time",
                show_reload=True,
                extended_selection=True)
        self.LibraryTab.layout().addWidget(self.LibraryToolBox)

        self.DownloadsToolBox = BaseToolBoxWidget(self)

        self.DownloadsStableListWidget = \
            self.DownloadsToolBox.add_list_widget(
                "Stable Releases",
                "DownloadsStableListWidget",
                "No new builds available",
                "Upload Time",
                False)
        self.DownloadsDailyListWidget = \
            self.DownloadsToolBox.add_list_widget(
                "Daily Builds",
                "DownloadsDailyListWidget",
                "No new builds available",
                "Upload Time",)
        self.DownloadsExperimentalListWidget = \
            self.DownloadsToolBox.add_list_widget(
                "Experimental Branches",
                "DownloadsExperimentalListWidget",
                "No new builds available",
                "Upload Time",)
        self.DownloadsTab.layout().addWidget(self.DownloadsToolBox)

        self.LibraryToolBox.setCurrentIndex(get_default_library_page())
        self.DownloadsToolBox.setCurrentIndex(get_default_downloads_page())

        # Connect buttons
        self.SettingsButton.clicked.connect(self.show_settings_window)
        self.WikiButton.clicked.connect(lambda: webbrowser.open(
            "https://github.com/DotBow/Blender-Launcher/wiki"))
        self.MinimizeButton.clicked.connect(self.showMinimized)
        self.CloseButton.clicked.connect(self.close)

        self.StatusBar.setContentsMargins(0, 0, 0, 2)
        self.StatusBar.setFont(self.font)
        self.statusbarLabel = QLabel()
        self.statusbarLabel.setIndent(8)
        self.NewVersionButton = QPushButton()
        self.NewVersionButton.hide()
        self.NewVersionButton.clicked.connect(self.show_update_window)
        self.statusbarVersion = QLabel(self.app.applicationVersion())
        self.statusbarVersion.setToolTip(
            "The version of Blender Laucnher that is currently run")
        self.StatusBar.addPermanentWidget(self.statusbarLabel, 1)
        self.StatusBar.addPermanentWidget(self.NewVersionButton)
        self.StatusBar.addPermanentWidget(self.statusbarVersion)

        # Draw library
        self.draw_library()

        # Setup tray icon context Menu
        quit_action = QAction("Quit", self)
        quit_action.triggered.connect(self.quit)
        hide_action = QAction("Hide", self)
        hide_action.triggered.connect(self.close)
        show_action = QAction("Show", self)
        show_action.triggered.connect(self._show)
        launch_favorite_action = QAction(
            QIcon(":resources/icons/favorite.svg"), "Blender", self)
        launch_favorite_action.triggered.connect(self.launch_favorite)

        tray_menu = QMenu()
        tray_menu.setFont(self.font)
        tray_menu.addAction(launch_favorite_action)
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)

        # Setup tray icon
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(
            QIcon(taskbar_icon_paths[get_taskbar_icon_color()]))
        self.tray_icon.setToolTip("Blender Launcher")
        self.tray_icon.activated.connect(self.tray_icon_activated)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.messageClicked.connect(self._show)
        self.tray_icon.show()

        # Forse style update
        if polish is True:
            self.style().unpolish(self.app)
            self.style().polish(self.app)

        # Show window
        if get_launch_minimized_to_tray() is False:
            self._show()

    def show_update_window(self):
        download_widgets = []

        download_widgets.extend(self.DownloadsStableListWidget.items())
        download_widgets.extend(self.DownloadsDailyListWidget.items())
        download_widgets.extend(self.DownloadsExperimentalListWidget.items())

        for widget in download_widgets:
            if widget.state == DownloadState.DOWNLOADING:
                self.dlg = DialogWindow(
                    self,
                    title="Warning",
                    text="In order to update Blender Launcher<br> \
                    complete all active downloads!",
                    accept_text="OK",
                    cancel_text=None,
                    icon=DialogIcon.WARNING)

                return

        self.tray_icon.hide()
        self.close()
        self.update_window = UpdateWindow(self, self.latest_tag)

    def _show(self):
        platform = get_platform()

        if platform == "Windows":
            self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
            self.show()
            self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint)
            self.show()
        elif platform == "Linux":
            self.show()
            self.activateWindow()

        self.set_status()
        self.show_signal.emit()

    def show_message(self, message, value=None, type=None):
        if (type == MessageType.DOWNLOADFINISHED
                and get_enable_download_notifications() is False):
            return
        elif (type == MessageType.NEWBUILDS
              and get_enable_new_builds_notifications() is False):
            return

        if value not in self.notification_pool:
            if value is not None:
                self.notification_pool.append(value)
            self.tray_icon.showMessage(
                "Blender Launcher", message,
                QIcon(taskbar_icon_paths[get_taskbar_icon_color()]), 10000)

    def launch_favorite(self):
        try:
            self.favorite.launch()
        except Exception:
            self.dlg = DialogWindow(self,
                                    text="Favorite build not found!",
                                    accept_text="OK",
                                    cancel_text=None)

    def tray_icon_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self._show()
        elif reason == QSystemTrayIcon.MiddleClick:
            self.launch_favorite()

    def quit(self):
        download_widgets = []

        download_widgets.extend(self.DownloadsStableListWidget.items())
        download_widgets.extend(self.DownloadsDailyListWidget.items())
        download_widgets.extend(self.DownloadsExperimentalListWidget.items())

        for widget in download_widgets:
            if widget.state == DownloadState.DOWNLOADING:
                self.dlg = DialogWindow(
                    self,
                    title="Warning",
                    text="Active downloads in progress!<br>\
                    Are you sure you want to quit?",
                    accept_text="Yes",
                    cancel_text="No",
                    icon=DialogIcon.WARNING)

                self.dlg.accepted.connect(self.destroy)
                return

        self.destroy()

    def destroy(self):
        if self.timer is not None:
            self.timer.cancel()

        self.tray_icon.hide()
        self.app.quit()

    def draw_library(self, clear=False):
        self.set_status("Reading local builds")

        if clear:
            self.timer.cancel()
            self.scraper.quit()
            self.DownloadsStableListWidget._clear()
            self.DownloadsDailyListWidget._clear()
            self.DownloadsExperimentalListWidget._clear()
            self.started = True

        self.favorite = None

        self.LibraryStableListWidget._clear()
        self.LibraryDailyListWidget._clear()
        self.LibraryExperimentalListWidget._clear()
        self.LibraryCustomListWidget._clear()

        self.library_drawer = LibraryDrawer()
        self.library_drawer.build_found.connect(self.draw_to_library)
        self.library_drawer.finished.connect(self.draw_downloads)
        self.library_drawer.start()

    def reload_custom_builds(self):
        self.LibraryCustomListWidget._clear()
        self.library_drawer = LibraryDrawer()
        self.library_drawer.build_found.connect(self.draw_to_library)
        self.library_drawer.start()

    def draw_downloads(self):
        for page in self.DownloadsToolBox.pages:
            page.set_info_label_text("Checking for new builds")

        self.cashed_builds.clear()
        self.new_downloads = False
        self.app_state = AppState.CHECKINGBUILDS
        self.set_status("Checking for new builds")
        self.scraper = Scraper(self, self.manager)
        self.scraper.links.connect(self.draw_to_downloads)
        self.scraper.new_bl_version.connect(self.set_version)
        self.scraper.error.connect(self.connection_error)
        self.scraper.finished.connect(self.scraper_finished)
        self.scraper.start()

    def connection_error(self):
        set_locale()
        utcnow = strftime(('%H:%M'), localtime())
        self.set_status("Connection Error at " + utcnow)
        self.app_state = AppState.IDLE

        self.timer = threading.Timer(600.0, self.draw_downloads)
        self.timer.start()

    def scraper_finished(self):
        if self.new_downloads and not self.started:
            self.show_message("New builds of Blender is available!",
                              type=MessageType.NEWBUILDS)

        for list_widget in self.DownloadsToolBox.list_widgets:
            for widget in list_widget.widgets:
                if widget.build_info not in self.cashed_builds:
                    widget.destroy()

        set_locale()
        utcnow = strftime(('%H:%M'), localtime())
        self.set_status("Last check at " + utcnow)
        self.app_state = AppState.IDLE

        for page in self.DownloadsToolBox.pages:
            page.set_info_label_text("No new builds available")

        self.timer = threading.Timer(600.0, self.draw_downloads)
        self.timer.start()
        self.started = False

    def draw_from_cashed(self, build_info):
        if self.app_state == AppState.IDLE:
            for cashed_build in self.cashed_builds:
                if build_info == cashed_build:
                    self.draw_to_downloads(cashed_build, False)
                    return

    def draw_to_downloads(self, build_info, show_new=True):
        if self.started:
            show_new = False

        if build_info not in self.cashed_builds:
            self.cashed_builds.append(build_info)

        branch = build_info.branch

        if (branch == 'stable') or (branch == 'lts'):
            downloads_list_widget = self.DownloadsStableListWidget
            library_list_widget = self.LibraryStableListWidget
        elif branch == 'daily':
            downloads_list_widget = self.DownloadsDailyListWidget
            library_list_widget = self.LibraryDailyListWidget
        else:
            downloads_list_widget = self.DownloadsExperimentalListWidget
            library_list_widget = self.LibraryExperimentalListWidget

        if not library_list_widget.contains_build_info(build_info) and \
                not downloads_list_widget.contains_build_info(build_info):
            item = BaseListWidgetItem(build_info.commit_time)
            widget = DownloadWidget(self, downloads_list_widget, item,
                                    build_info, show_new)
            downloads_list_widget.add_item(item, widget)
            self.new_downloads = True

    def draw_to_library(self, path, show_new=False):
        branch = Path(path).parent.name

        if (branch == 'stable') or (branch == 'lts'):
            list_widget = self.LibraryStableListWidget
        elif branch == 'daily':
            list_widget = self.LibraryDailyListWidget
        elif branch == 'experimental':
            list_widget = self.LibraryExperimentalListWidget
        elif branch == 'custom':
            list_widget = self.LibraryCustomListWidget
        else:
            return

        item = BaseListWidgetItem()
        widget = LibraryWidget(self, item, path, list_widget, show_new)
        list_widget.insert_item(item, widget)

    def set_status(self, status=None):
        if status is not None:
            self.status = status

        self.statusbarLabel.setText("Status: {0}".format(self.status))

    def set_version(self, latest_tag):
        current_tag = self.app.applicationVersion()
        latest_ver = re.sub(r'\D', '', latest_tag)
        current_ver = re.sub(r'\D', '', current_tag)

        if int(latest_ver) > int(current_ver):
            if latest_tag not in self.notification_pool:
                self.NewVersionButton.setText("Update to version {0}".format(
                    latest_tag.replace('v', '')))
                self.NewVersionButton.show()
                self.show_message(
                    "New version of Blender Launcher is available!",
                    latest_tag)

            self.latest_tag = latest_tag

    def show_settings_window(self):
        self.settings_window = SettingsWindow(self)

    def clear_temp(self):
        temp_folder = Path(get_library_folder()) / ".temp"
        self.remover = Remover(temp_folder)
        self.remover.start()

    def closeEvent(self, event):
        event.ignore()
        self.hide()
        self.close_signal.emit()

    def new_connection(self):
        self._show()

    def dragEnterEvent(self, e):
        if e.mimeData().hasFormat('text/plain'):
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        print(e.mimeData().text())
Exemplo n.º 28
0
class QtSingleApplication(QApplication):
    messageReceived = pyqtSignal(str)

    def __init__(self, _id, _viewer_id, *argv):

        super(QtSingleApplication, self).__init__(*argv)
        self._id = _id
        self._viewer_id = _viewer_id
        self._activationWindow = None
        self._activateOnMessage = False

        # Is there another instance running?
        self._outSocket = QLocalSocket()
        self._outSocket.connectToServer(self._id)
        self._isRunning = self._outSocket.waitForConnected(-1)

        if self._isRunning:
            # Yes, there is.
            self._outStream = QTextStream(self._outSocket)
            self._outStream.setCodec('UTF-8')
            # Is there another viewer runnging?
            self._outSocketViewer = QLocalSocket()
            self._outSocketViewer.connectToServer(self._viewer_id)
            self._isRunningViewer = self._outSocketViewer.waitForConnected(-1)
            if self._isRunningViewer:
                self._outStreamViewer = QTextStream(self._outSocketViewer)
                self._outStreamViewer.setCodec('UTF-8')
            else:
                # app is running, we announce us as viewer app
                # First we remove existing servers of that name that might not have been properly closed as the server died
                QLocalServer.removeServer(self._viewer_id)
                self._outSocketViewer = None
                self._outStreamViewer = None
                self._inSocket = None
                self._inStream = None
                self._server = QLocalServer()
                self._server.listen(self._viewer_id)
                self._server.newConnection.connect(self._onNewConnection)
        else:
            self._isRunningViewer = False
            # No, there isn't.
            # First we remove existing servers of that name that might not have been properly closed as the server died
            QLocalServer.removeServer(self._id)
            self._outSocket = None
            self._outStream = None
            self._inSocket = None
            self._inStream = None
            self._server = QLocalServer()
            self._server.listen(self._id)
            self._server.newConnection.connect(self._onNewConnection)

    def isRunning(self):
        return self._isRunning

    def isRunningViewer(self):
        return self._isRunningViewer

    def id(self):
        return self._id

    def activationWindow(self):
        return self._activationWindow

    def setActivationWindow(self, activationWindow, activateOnMessage=True):
        self._activationWindow = activationWindow
        self._activateOnMessage = activateOnMessage

    def activateWindow(self):
        if not self._activationWindow:
            return

        self._activationWindow.show()
        self._activationWindow.setWindowState(
            self._activationWindow.windowState() & ~Qt.WindowMinimized)
        self._activationWindow.raise_()
        self._activationWindow.activateWindow()

    def sendMessage(self, msg):
        if not self._outStream:
            return False
        self._outStream << msg << '\n'
        self._outStream.flush()
        return self._outSocket.waitForBytesWritten()

    @pyqtSlot()
    def _onNewConnection(self):
        if self._inSocket:
            self._inSocket.readyRead.disconnect(self._onReadyRead)
        self._inSocket = self._server.nextPendingConnection()
        if not self._inSocket:
            return
        self._inStream = QTextStream(self._inSocket)
        self._inStream.setCodec('UTF-8')
        self._inSocket.readyRead.connect(self._onReadyRead)
        if self._activateOnMessage and self._isRunning:
            self.activateWindow()

    @pyqtSlot()
    def _onReadyRead(self):
        while True:
            msg = self._inStream.readLine()
            if not msg: break
            self.messageReceived.emit(msg)
Exemplo n.º 29
0
class IPCServer(QObject):
    """IPC server to which clients connect to.

    Attributes:
        ignored: Whether requests are ignored (in exception hook).
        _timer: A timer to handle timeouts.
        _server: A QLocalServer to accept new connections.
        _socket: The QLocalSocket we're currently connected to.
    """
    def __init__(self, parent=None):
        """Start the IPC server and listen to commands."""
        super().__init__(parent)
        self.ignored = False
        self._remove_server()
        self._timer = usertypes.Timer(self, 'ipc-timeout')
        self._timer.setInterval(READ_TIMEOUT)
        self._timer.timeout.connect(self.on_timeout)
        self._server = QLocalServer(self)
        ok = self._server.listen(SOCKETNAME)
        if not ok:
            raise IPCError("Error while listening to IPC server: {} "
                           "(error {})".format(self._server.errorString(),
                                               self._server.serverError()))
        self._server.newConnection.connect(self.handle_connection)
        self._socket = None

    def _remove_server(self):
        """Remove an existing server."""
        ok = QLocalServer.removeServer(SOCKETNAME)
        if not ok:
            raise IPCError(
                "Error while removing server {}!".format(SOCKETNAME))

    @pyqtSlot(int)
    def on_error(self, error):
        """Convenience method which calls _socket_error on an error."""
        self._timer.stop()
        log.ipc.debug("Socket error {}: {}".format(self._socket.error(),
                                                   self._socket.errorString()))
        if error != QLocalSocket.PeerClosedError:
            _socket_error("handling IPC connection", self._socket)

    @pyqtSlot()
    def handle_connection(self):
        """Handle a new connection to the server."""
        if self.ignored:
            return
        if self._socket is not None:
            log.ipc.debug("Got new connection but ignoring it because we're "
                          "still handling another one.")
            return
        socket = self._server.nextPendingConnection()
        if socket is None:
            log.ipc.debug("No new connection to handle.")
            return
        log.ipc.debug("Client connected.")
        self._timer.start()
        self._socket = socket
        socket.readyRead.connect(self.on_ready_read)
        if socket.canReadLine():
            log.ipc.debug("We can read a line immediately.")
            self.on_ready_read()
        socket.error.connect(self.on_error)
        if socket.error() not in (QLocalSocket.UnknownSocketError,
                                  QLocalSocket.PeerClosedError):
            log.ipc.debug("We got an error immediately.")
            self.on_error(socket.error())
        socket.disconnected.connect(self.on_disconnected)
        if socket.state() == QLocalSocket.UnconnectedState:
            log.ipc.debug("Socket was disconnected immediately.")
            self.on_disconnected()

    @pyqtSlot()
    def on_disconnected(self):
        """Clean up socket when the client disconnected."""
        log.ipc.debug("Client disconnected.")
        self._timer.stop()
        self._socket.deleteLater()
        self._socket = None
        # Maybe another connection is waiting.
        self.handle_connection()

    @pyqtSlot()
    def on_ready_read(self):
        """Read json data from the client."""
        if self._socket is None:
            # this happened once and I don't know why
            log.ipc.warn("In on_ready_read with None socket!")
            return
        self._timer.start()
        while self._socket is not None and self._socket.canReadLine():
            data = bytes(self._socket.readLine())
            log.ipc.debug("Read from socket: {}".format(data))
            try:
                decoded = data.decode('utf-8')
            except UnicodeDecodeError:
                log.ipc.error("Ignoring invalid IPC data.")
                log.ipc.debug("invalid data: {}".format(
                    binascii.hexlify(data)))
                return
            log.ipc.debug("Processing: {}".format(decoded))
            try:
                json_data = json.loads(decoded)
            except ValueError:
                log.ipc.error("Ignoring invalid IPC data.")
                log.ipc.debug("invalid json: {}".format(decoded.strip()))
                return
            try:
                args = json_data['args']
            except KeyError:
                log.ipc.error("Ignoring invalid IPC data.")
                log.ipc.debug("no args: {}".format(decoded.strip()))
                return
            cwd = json_data.get('cwd', None)
            app = objreg.get('app')
            app.process_args(args, via_ipc=True, cwd=cwd)

    @pyqtSlot()
    def on_timeout(self):
        """Cancel the current connection if it was idle for too long."""
        log.ipc.error("IPC connection timed out.")
        self._socket.close()

    def shutdown(self):
        """Shut down the IPC server cleanly."""
        if self._socket is not None:
            self._socket.deleteLater()
            self._socket = None
        self._timer.stop()
        self._server.close()
        self._server.deleteLater()
        self._remove_server()
Exemplo n.º 30
0
class Application(QApplication):
    """Main Nuxeo Drive application controlled by a system tray icon + menu"""

    tray_icon = None
    icon_state = None

    def __init__(self, manager: "Manager", *args: Any) -> None:
        super().__init__(list(*args))
        self.manager = manager

        self.dialogs = dict()
        self.osi = self.manager.osi
        self.setWindowIcon(QIcon(self.get_window_icon()))
        self.setApplicationName(manager.app_name)
        self._init_translator()
        self.setQuitOnLastWindowClosed(False)
        self._delegator = None

        self.filters_dlg = None
        self._conflicts_modals = dict()
        self.current_notification = None
        self.default_tooltip = self.manager.app_name

        font = QFont("Helvetica, Arial, sans-serif", 12)
        self.setFont(font)
        self.ratio = sqrt(QFontMetricsF(font).height() / 12)

        self.init_gui()

        self.aboutToQuit.connect(self.manager.stop)
        self.manager.dropEngine.connect(self.dropped_engine)

        # This is a windowless application mostly using the system tray
        self.setQuitOnLastWindowClosed(False)

        self.setup_systray()

        # Direct Edit
        self.manager.direct_edit.directEditConflict.connect(
            self._direct_edit_conflict)
        self.manager.direct_edit.directEditError.connect(
            self._direct_edit_error)

        # Check if actions is required, separate method so it can be overridden
        self.init_checks()

        # Setup notification center for macOS
        if MAC:
            self._setup_notification_center()

        # Application update
        self.manager.updater.appUpdated.connect(self.quit)

        # Display release notes on new version
        if self.manager.old_version != self.manager.version:
            self.show_release_notes(self.manager.version)

        # Listen for nxdrive:// sent by a new instance
        self.init_nxdrive_listener()

    def init_gui(self) -> None:

        self.api = QMLDriveApi(self)
        self.conflicts_model = FileModel()
        self.errors_model = FileModel()
        self.engine_model = EngineModel()
        self.file_model = FileModel()
        self.ignoreds_model = FileModel()
        self.language_model = LanguageModel()

        self.add_engines(list(self.manager._engines.values()))
        self.engine_model.statusChanged.connect(self.update_status)
        self.language_model.addLanguages(Translator.languages())

        flags = Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint

        if WINDOWS:
            self.conflicts_window = QQuickView()
            self.settings_window = QQuickView()
            self.systray_window = SystrayWindow()

            self._fill_qml_context(self.conflicts_window.rootContext())
            self._fill_qml_context(self.settings_window.rootContext())
            self._fill_qml_context(self.systray_window.rootContext())
            self.systray_window.rootContext().setContextProperty(
                "systrayWindow", self.systray_window)

            self.conflicts_window.setSource(
                QUrl.fromLocalFile(find_resource("qml", "Conflicts.qml")))
            self.settings_window.setSource(
                QUrl.fromLocalFile(find_resource("qml", "Settings.qml")))
            self.systray_window.setSource(
                QUrl.fromLocalFile(find_resource("qml", "Systray.qml")))
            flags |= Qt.Popup
        else:
            self.app_engine = QQmlApplicationEngine()
            self._fill_qml_context(self.app_engine.rootContext())
            self.app_engine.load(
                QUrl.fromLocalFile(find_resource("qml", "Main.qml")))
            root = self.app_engine.rootObjects()[0]
            self.conflicts_window = root.findChild(QQuickWindow,
                                                   "conflictsWindow")
            self.settings_window = root.findChild(QQuickWindow,
                                                  "settingsWindow")
            self.systray_window = root.findChild(SystrayWindow,
                                                 "systrayWindow")
            if LINUX:
                flags |= Qt.Drawer

        self.systray_window.setFlags(flags)

        self.manager.newEngine.connect(self.add_engines)
        self.manager.initEngine.connect(self.add_engines)
        self.manager.dropEngine.connect(self.remove_engine)
        self._window_root(self.conflicts_window).changed.connect(
            self.refresh_conflicts)
        self._window_root(self.systray_window).appUpdate.connect(
            self.api.app_update)
        self._window_root(self.systray_window).getLastFiles.connect(
            self.get_last_files)
        self.api.setMessage.connect(
            self._window_root(self.settings_window).setMessage)

        if self.manager.get_engines():
            current_uid = self.engine_model.engines_uid[0]
            self.get_last_files(current_uid)
            self.update_status(self.engine_model.engines[current_uid])

        self.manager.updater.updateAvailable.connect(
            self._window_root(self.systray_window).updateAvailable)
        self.manager.updater.updateProgress.connect(
            self._window_root(self.systray_window).updateProgress)

    def add_engines(self, engines: Union["Engine", List["Engine"]]) -> None:
        if not engines:
            return

        engines = engines if isinstance(engines, list) else [engines]
        for engine in engines:
            self.engine_model.addEngine(engine)

    def remove_engine(self, uid: str) -> None:
        self.engine_model.removeEngine(uid)

    def _fill_qml_context(self, context: "QQmlContext") -> None:
        """ Fill the context of a QML element with the necessary resources. """
        context.setContextProperty("ConflictsModel", self.conflicts_model)
        context.setContextProperty("ErrorsModel", self.errors_model)
        context.setContextProperty("EngineModel", self.engine_model)
        context.setContextProperty("FileModel", self.file_model)
        context.setContextProperty("IgnoredsModel", self.ignoreds_model)
        context.setContextProperty("languageModel", self.language_model)
        context.setContextProperty("api", self.api)
        context.setContextProperty("application", self)
        context.setContextProperty("currentLanguage", self.current_language())
        context.setContextProperty("manager", self.manager)
        context.setContextProperty("updater", self.manager.updater)
        context.setContextProperty("ratio", self.ratio)
        context.setContextProperty("isFrozen", Options.is_frozen)
        context.setContextProperty("tl", Translator._singleton)
        context.setContextProperty(
            "nuxeoVersionText",
            f"{self.manager.app_name} {self.manager.version}")
        metrics = self.manager.get_metrics()
        context.setContextProperty(
            "modulesVersionText",
            (f'Python {metrics["python_version"]}, '
             f'Qt {metrics["qt_version"]}, '
             f'SIP {metrics["sip_version"]}'),
        )

        colors = {
            "darkBlue": "#1F28BF",
            "nuxeoBlue": "#0066FF",
            "lightBlue": "#00ADED",
            "teal": "#73D2CF",
            "purple": "#8400FF",
            "red": "#C02828",
            "orange": "#FF9E00",
            "darkGray": "#495055",
            "mediumGray": "#7F8284",
            "lightGray": "#BCBFBF",
            "lighterGray": "#F5F5F5",
        }

        for name, value in colors.items():
            context.setContextProperty(name, value)

    def _window_root(self, window):
        if WINDOWS:
            return window.rootObject()
        return window

    def translate(self, message: str, values: dict = None) -> str:
        return Translator.get(message, values)

    def _show_window(self, window: "QWindow") -> None:
        window.show()
        window.raise_()

    def _destroy_dialog(self) -> None:
        sender = self.sender()
        name = sender.objectName()
        self.dialogs.pop(name, None)

    def _create_unique_dialog(self, name: str, dialog: QDialog) -> None:
        dialog.setObjectName(name)
        dialog.destroyed.connect(self._destroy_dialog)
        self.dialogs[name] = dialog

    def _init_translator(self) -> None:
        locale = Options.force_locale or Options.locale
        Translator(
            self.manager,
            find_resource("i18n"),
            self.manager.get_config("locale", locale),
        )
        self.installTranslator(Translator._singleton)

    @staticmethod
    def get_window_icon() -> str:
        return find_icon("app_icon.svg")

    @pyqtSlot(str, str, str)
    def _direct_edit_conflict(self, filename: str, ref: str,
                              digest: str) -> None:
        log.trace("Entering _direct_edit_conflict for %r / %r", filename, ref)
        try:
            if filename in self._conflicts_modals:
                log.trace("Filename already in _conflicts_modals: %r",
                          filename)
                return
            log.trace("Putting filename in _conflicts_modals: %r", filename)
            self._conflicts_modals[filename] = True

            msg = QMessageBox()
            msg.setInformativeText(
                Translator.get("DIRECT_EDIT_CONFLICT_MESSAGE",
                               [short_name(filename)]))
            overwrite = msg.addButton(
                Translator.get("DIRECT_EDIT_CONFLICT_OVERWRITE"),
                QMessageBox.AcceptRole)
            msg.addButton(Translator.get("CANCEL"), QMessageBox.RejectRole)
            msg.setIcon(QMessageBox.Warning)
            if msg.clickedButton() == overwrite:
                self.manager.direct_edit.force_update(ref, digest)
            del self._conflicts_modals[filename]
        except:
            log.exception(
                "Error while displaying Direct Edit conflict modal dialog for %r",
                filename,
            )

    @pyqtSlot(str, list)
    def _direct_edit_error(self, message: str, values: List[str]) -> None:
        """ Display a simple Direct Edit error message. """

        msg = QMessageBox()
        msg.setWindowTitle("Direct Edit - {self.manager.app_name}")
        msg.setWindowIcon(QIcon(self.get_window_icon()))
        msg.setIcon(QMessageBox.Warning)
        msg.setTextFormat(Qt.RichText)
        msg.setText(self.translate(message, values))
        msg.exec_()

    @pyqtSlot()
    def _root_deleted(self) -> None:
        engine = self.sender()
        log.debug("Root has been deleted for engine: %s", engine.uid)

        msg = QMessageBox()
        msg.setIcon(QMessageBox.Critical)
        msg.setWindowIcon(QIcon(self.get_window_icon()))
        msg.setText(Translator.get("DRIVE_ROOT_DELETED",
                                   [engine.local_folder]))
        recreate = msg.addButton(Translator.get("DRIVE_ROOT_RECREATE"),
                                 QMessageBox.AcceptRole)
        disconnect = msg.addButton(Translator.get("DRIVE_ROOT_DISCONNECT"),
                                   QMessageBox.RejectRole)

        msg.exec_()
        res = msg.clickedButton()
        if res == disconnect:
            self.manager.unbind_engine(engine.uid)
        elif res == recreate:
            engine.reinit()
            engine.start()

    @pyqtSlot()
    def _no_space_left(self) -> None:
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowIcon(QIcon(self.get_window_icon()))
        msg.setText(Translator.get("NO_SPACE_LEFT_ON_DEVICE"))
        msg.addButton(Translator.get("OK"), QMessageBox.AcceptRole)
        msg.exec_()

    @pyqtSlot(str)
    def _root_moved(self, new_path: str) -> None:
        engine = self.sender()
        log.debug("Root has been moved for engine: %s to %r", engine.uid,
                  new_path)
        info = [engine.local_folder, new_path]

        msg = QMessageBox()
        msg.setIcon(QMessageBox.Critical)
        msg.setWindowIcon(QIcon(self.get_window_icon()))
        msg.setText(Translator.get("DRIVE_ROOT_MOVED", info))
        move = msg.addButton(Translator.get("DRIVE_ROOT_UPDATE"),
                             QMessageBox.AcceptRole)
        recreate = msg.addButton(Translator.get("DRIVE_ROOT_RECREATE"),
                                 QMessageBox.AcceptRole)
        disconnect = msg.addButton(Translator.get("DRIVE_ROOT_DISCONNECT"),
                                   QMessageBox.RejectRole)
        msg.exec_()
        res = msg.clickedButton()

        if res == disconnect:
            self.manager.unbind_engine(engine.uid)
        elif res == recreate:
            engine.reinit()
            engine.start()
        elif res == move:
            engine.set_local_folder(new_path)
            engine.start()

    @pyqtSlot(object)
    def dropped_engine(self, engine: "Engine") -> None:
        # Update icon in case the engine dropped was syncing
        self.change_systray_icon()

    @pyqtSlot()
    def change_systray_icon(self) -> None:
        # Update status has the precedence over other ones
        if self.manager.updater.status not in (
                UPDATE_STATUS_UNAVAILABLE_SITE,
                UPDATE_STATUS_UP_TO_DATE,
        ):
            self.set_icon_state("update")
            return

        syncing = conflict = False
        engines = self.manager.get_engines()
        invalid_credentials = paused = offline = True

        for engine in engines.values():
            syncing |= engine.is_syncing()
            invalid_credentials &= engine.has_invalid_credentials()
            paused &= engine.is_paused()
            offline &= engine.is_offline()
            conflict |= bool(engine.get_conflicts())

        if offline:
            new_state = "error"
            Action(Translator.get("OFFLINE"))
        elif invalid_credentials:
            new_state = "error"
            Action(Translator.get("AUTH_EXPIRED"))
        elif not engines:
            new_state = "disabled"
            Action.finish_action()
        elif paused:
            new_state = "paused"
            Action.finish_action()
        elif syncing:
            new_state = "syncing"
        elif conflict:
            new_state = "conflict"
        else:
            new_state = "idle"
            Action.finish_action()

        self.set_icon_state(new_state)

    def refresh_conflicts(self, uid: str) -> None:
        """ Update the content of the conflicts/errors window. """
        self.conflicts_model.empty()
        self.errors_model.empty()
        self.ignoreds_model.empty()

        self.conflicts_model.addFiles(self.api.get_conflicts(uid))
        self.errors_model.addFiles(self.api.get_errors(uid))
        self.ignoreds_model.addFiles(self.api.get_unsynchronizeds(uid))

    @pyqtSlot()
    def show_conflicts_resolution(self, engine: "Engine") -> None:
        """ Display the conflicts/errors window. """
        self.refresh_conflicts(engine.uid)
        self._window_root(self.conflicts_window).setEngine.emit(engine.uid)
        self.conflicts_window.show()
        self.conflicts_window.requestActivate()

    @pyqtSlot()
    def show_settings(self, section: str = "General") -> None:
        sections = {"General": 0, "Accounts": 1, "About": 2}
        self._window_root(self.settings_window).setSection.emit(
            sections[section])
        self.settings_window.show()
        self.settings_window.requestActivate()

    @pyqtSlot()
    def show_systray(self) -> None:
        icon = self.tray_icon.geometry()

        if not icon or icon.isEmpty():
            # On Ubuntu it's likely we can't retrieve the geometry.
            # We're simply displaying the systray in the top right corner.
            screen = self.desktop().screenGeometry()
            pos_x = screen.right() - self.systray_window.width() - 20
            pos_y = 30
        else:
            pos_x = max(0,
                        icon.x() + icon.width() - self.systray_window.width())
            pos_y = icon.y() - self.systray_window.height()
            if pos_y < 0:
                pos_y = icon.y() + icon.height()

        self.systray_window.setX(pos_x)
        self.systray_window.setY(pos_y)

        self.systray_window.show()
        self.systray_window.raise_()

    @pyqtSlot()
    def hide_systray(self):
        self.systray_window.hide()

    @pyqtSlot()
    def open_help(self) -> None:
        self.manager.open_help()

    @pyqtSlot()
    def destroyed_filters_dialog(self) -> None:
        self.filters_dlg = None

    @pyqtSlot()
    def show_filters(self, engine: "Engine") -> None:
        if self.filters_dlg:
            self.filters_dlg.close()
            self.filters_dlg = None

        from ..gui.folders_dialog import FiltersDialog

        self.filters_dlg = FiltersDialog(self, engine)
        self.filters_dlg.destroyed.connect(self.destroyed_filters_dialog)
        self.filters_dlg.show()

    def show_file_status(self) -> None:
        from ..gui.status_dialog import StatusDialog

        for _, engine in self.manager.get_engines().items():
            self.status = StatusDialog(engine.get_dao())
            self.status.show()
            break

    def show_activities(self) -> None:
        return
        # TODO: Create activities window
        self.activities.show()

    @pyqtSlot(str, object)
    def _open_authentication_dialog(self, url: str,
                                    callback_params: Dict[str, str]) -> None:
        self.api._callback_params = callback_params
        if Options.is_frozen:
            """
            Authenticate through the browser.

            This authentication requires the server's Nuxeo Drive addon to include
            NXP-25519. Instead of opening the server's login page in a WebKit view
            through the app, it opens in the browser and retrieves the login token
            by opening an nxdrive:// URL.
            """
            self.manager.open_local_file(url)
        else:
            self._web_auth_not_frozen(url)

    def _web_auth_not_frozen(self, url: str) -> None:
        """
        Open a dialog box to fill the credentials.
        Then a request will be done using the Python client to
        get a token.

        This is used when the application is not frozen as there is no custom
        protocol handler in this case.
        """

        from PyQt5.QtWidgets import QLineEdit
        from nuxeo.client import Nuxeo

        dialog = QDialog()
        dialog.setWindowTitle(
            self.translate("WEB_AUTHENTICATION_WINDOW_TITLE"))
        dialog.setWindowIcon(QIcon(self.get_window_icon()))
        dialog.resize(250, 100)

        layout = QVBoxLayout()

        username = QLineEdit("Administrator", parent=dialog)
        password = QLineEdit("Administrator", parent=dialog)
        password.setEchoMode(QLineEdit.Password)
        layout.addWidget(username)
        layout.addWidget(password)

        def auth() -> None:
            """Retrieve a token and create the account."""
            user = str(username.text())
            pwd = str(password.text())
            nuxeo = Nuxeo(host=url, auth=(user, pwd))
            try:
                token = nuxeo.client.request_auth_token(
                    device_id=self.manager.device_id,
                    app_name=APP_NAME,
                    permission=TOKEN_PERMISSION,
                    device=get_device(),
                )
            except Exception as exc:
                log.error(f"Connection error: {exc}")
                token = ""
            finally:
                del nuxeo
            self.api.handle_token(token, user)
            dialog.close()

        buttons = QDialogButtonBox()
        buttons.setStandardButtons(QDialogButtonBox.Cancel
                                   | QDialogButtonBox.Ok)
        buttons.accepted.connect(auth)
        buttons.rejected.connect(dialog.close)
        layout.addWidget(buttons)

        dialog.setLayout(layout)
        dialog.exec_()

    @pyqtSlot(object)
    def _connect_engine(self, engine: "Engine") -> None:
        engine.syncStarted.connect(self.change_systray_icon)
        engine.syncCompleted.connect(self.change_systray_icon)
        engine.invalidAuthentication.connect(self.change_systray_icon)
        engine.syncSuspended.connect(self.change_systray_icon)
        engine.syncResumed.connect(self.change_systray_icon)
        engine.offline.connect(self.change_systray_icon)
        engine.online.connect(self.change_systray_icon)
        engine.rootDeleted.connect(self._root_deleted)
        engine.rootMoved.connect(self._root_moved)
        engine.noSpaceLeftOnDevice.connect(self._no_space_left)
        self.change_systray_icon()

    @pyqtSlot()
    def _debug_toggle_invalid_credentials(self) -> None:
        sender = self.sender()
        engine = sender.data()
        engine.set_invalid_credentials(not engine.has_invalid_credentials(),
                                       reason="debug")

    @pyqtSlot()
    def _debug_show_file_status(self) -> None:
        from ..gui.status_dialog import StatusDialog

        sender = self.sender()
        engine = sender.data()
        self.status_dialog = StatusDialog(engine.get_dao())
        self.status_dialog.show()

    def _create_debug_engine_menu(self, engine: "Engine",
                                  parent: QMenu) -> QMenu:
        menu = QMenu(parent)
        action = QAction(Translator.get("DEBUG_INVALID_CREDENTIALS"), menu)
        action.setCheckable(True)
        action.setChecked(engine.has_invalid_credentials())
        action.setData(engine)
        action.triggered.connect(self._debug_toggle_invalid_credentials)
        menu.addAction(action)
        action = QAction(Translator.get("DEBUG_FILE_STATUS"), menu)
        action.setData(engine)
        action.triggered.connect(self._debug_show_file_status)
        menu.addAction(action)
        return menu

    def create_debug_menu(self, menu: QMenu) -> None:
        menu.addAction(Translator.get("DEBUG_WINDOW"), self.show_debug_window)
        for engine in self.manager.get_engines().values():
            action = QAction(engine.name, menu)
            action.setMenu(self._create_debug_engine_menu(engine, menu))
            action.setData(engine)
            menu.addAction(action)

    @pyqtSlot()
    def show_debug_window(self) -> None:
        return
        debug = self.dialogs.get("debug")
        # TODO: if not debug: Create debug window
        self._show_window(debug)

    def init_checks(self) -> None:
        if Options.debug:
            self.show_debug_window()

        for _, engine in self.manager.get_engines().items():
            self._connect_engine(engine)

        self.manager.newEngine.connect(self._connect_engine)
        self.manager.notification_service.newNotification.connect(
            self._new_notification)
        self.manager.updater.updateAvailable.connect(self._update_notification)

        if not self.manager.get_engines():
            self.show_settings()
        else:
            for engine in self.manager.get_engines().values():
                # Prompt for settings if needed
                if engine.has_invalid_credentials():
                    self.show_settings("Accounts_" + engine.uid)
                    break

        self.manager.start()

    @pyqtSlot()
    @if_frozen
    def _update_notification(self) -> None:
        self.change_systray_icon()

        # Display a notification
        status, version = self.manager.updater.status, self.manager.updater.version

        msg = (
            "AUTOUPDATE_UPGRADE",
            "AUTOUPDATE_DOWNGRADE")[status == UPDATE_STATUS_DOWNGRADE_NEEDED]
        description = Translator.get(msg, [version])
        flags = Notification.FLAG_BUBBLE | Notification.FLAG_UNIQUE
        if LINUX:
            description += " " + Translator.get("AUTOUPDATE_MANUAL")
            flags |= Notification.FLAG_SYSTRAY

        log.warning(description)
        notification = Notification(
            uuid="AutoUpdate",
            flags=flags,
            title=Translator.get("NOTIF_UPDATE_TITLE", [version]),
            description=description,
        )
        self.manager.notification_service.send_notification(notification)

    @pyqtSlot()
    def message_clicked(self) -> None:
        if self.current_notification:
            self.manager.notification_service.trigger_notification(
                self.current_notification.uid)

    def _setup_notification_center(self) -> None:
        from ..osi.darwin.pyNotificationCenter import (
            setup_delegator,
            NotificationDelegator,
        )

        if not self._delegator:
            self._delegator = NotificationDelegator.alloc().init()
            self._delegator._manager = self.manager
        setup_delegator(self._delegator)

    @pyqtSlot(object)
    def _new_notification(self, notif: Notification) -> None:
        if not notif.is_bubble():
            return

        if self._delegator is not None:
            # Use notification center
            from ..osi.darwin.pyNotificationCenter import notify

            return notify(notif.title,
                          None,
                          notif.description,
                          user_info={"uuid": notif.uid})

        icon = QSystemTrayIcon.Information
        if notif.level == Notification.LEVEL_WARNING:
            icon = QSystemTrayIcon.Warning
        elif notif.level == Notification.LEVEL_ERROR:
            icon = QSystemTrayIcon.Critical

        self.current_notification = notif
        self.tray_icon.showMessage(notif.title, notif.description, icon, 10000)

    def set_icon_state(self, state: str) -> bool:
        """
        Execute systray icon change operations triggered by state change.

        The synchronization thread can update the state info but cannot
        directly call QtGui widget methods. This should be executed by the main
        thread event loop, hence the delegation to this method that is
        triggered by a signal to allow for message passing between the 2
        threads.

        Return True of the icon has changed state.
        """

        if self.icon_state == state:
            # Nothing to update
            return False

        self.tray_icon.setToolTip(self.get_tooltip())
        self.tray_icon.setIcon(self.icons[state])
        self.icon_state = state
        return True

    def get_tooltip(self) -> str:
        actions = Action.get_actions()
        if not actions:
            return self.default_tooltip

        # Display only the first action for now
        for action in actions.values():
            if action and action.type and not action.type.startswith("_"):
                break
        else:
            return self.default_tooltip

        if isinstance(action, FileAction):
            if action.get_percent() is not None:
                return "%s - %s - %s - %d%%" % (
                    self.default_tooltip,
                    action.type,
                    action.filename,
                    action.get_percent(),
                )
            return "%s - %s - %s" % (self.default_tooltip, action.type,
                                     action.filename)
        elif action.get_percent() is not None:
            return "%s - %s - %d%%" % (
                self.default_tooltip,
                action.type,
                action.get_percent(),
            )

        return "%s - %s" % (self.default_tooltip, action.type)

    @if_frozen
    def show_release_notes(self, version: str) -> None:
        """ Display release notes of a given version. """

        beta = self.manager.get_beta_channel()
        log.debug("Showing release notes, version=%r beta=%r", version, beta)

        # For now, we do care about beta only
        if not beta:
            return

        url = ("https://api.github.com/repos/nuxeo/nuxeo-drive"
               "/releases/tags/release-" + version)

        if beta:
            version += " beta"

        try:
            content = requests.get(url)
        except requests.HTTPError as exc:
            if exc.response.status_code == 404:
                log.error("[%s] Release does not exist", version)
            else:
                log.exception(
                    "[%s] Network error while fetching release notes", version)
            return
        except:
            log.exception("[%s] Unknown error while fetching release notes",
                          version)
            return

        try:
            data = content.json()
        except ValueError:
            log.exception("[%s] Invalid release notes", version)
            return
        finally:
            del content

        try:
            html = markdown(data["body"])
        except KeyError:
            log.error("[%s] Release notes is missing its body", version)
            return
        except (UnicodeDecodeError, ValueError):
            log.exception("[%s] Release notes conversion error", version)
            return

        dialog = QDialog()
        dialog.setWindowTitle(
            f"{self.manager.app_name} {version} - Release notes")
        dialog.setWindowIcon(QIcon(self.get_window_icon()))

        dialog.resize(600, 400)

        notes = QTextEdit()
        notes.setStyleSheet("background-color: #eee; border: none;")
        notes.setReadOnly(True)
        notes.setHtml(html)

        buttons = QDialogButtonBox()
        buttons.setStandardButtons(QDialogButtonBox.Ok)
        buttons.clicked.connect(dialog.accept)

        layout = QVBoxLayout()
        layout.addWidget(notes)
        layout.addWidget(buttons)
        dialog.setLayout(layout)
        dialog.exec_()

    def show_metadata(self, file_path: str) -> None:
        self.manager.ctx_edit_metadata(file_path)

    def setup_systray(self) -> None:
        icons = {}
        for state in {
                "conflict",
                "disabled",
                "error",
                "idle",
                "notification",
                "paused",
                "syncing",
                "update",
        }:
            name = "{}{}.svg".format(state, "_light" if WINDOWS else "")
            icon = QIcon()
            icon.addFile(find_icon(name))
            if MAC:
                icon.addFile(find_icon("active.svg"), mode=QIcon.Selected)
            icons[state] = icon
        setattr(self, "icons", icons)

        self.tray_icon = DriveSystrayIcon(self)
        if not self.tray_icon.isSystemTrayAvailable():
            log.critical("There is no system tray available!")
        else:
            self.tray_icon.setToolTip(self.manager.app_name)
            self.set_icon_state("disabled")
            self.tray_icon.show()

    def event(self, event: QEvent) -> bool:
        """ Handle URL scheme events under macOS. """

        url = getattr(event, "url", None)
        if not url:
            # This is not an event for us!
            return super().event(event)
        try:
            final_url = unquote(event.url().toString())
            return self._handle_nxdrive_url(final_url)
        except:
            log.exception("Error handling URL event %r", url)
            return False

    def _handle_nxdrive_url(self, url: str) -> bool:
        """ Handle an nxdrive protocol URL. """

        info = parse_protocol_url(url)
        if not info:
            return False

        cmd = info["command"]
        path = info.get("filepath", None)
        manager = self.manager

        log.debug("Event URL=%s, info=%r", url, info)

        # Event fired by a context menu item
        func = {
            "access-online": manager.ctx_access_online,
            "copy-share-link": manager.ctx_copy_share_link,
            "edit-metadata": manager.ctx_edit_metadata,
        }.get(cmd, None)
        if func:
            func(path)
        elif "edit" in cmd:
            manager.direct_edit.edit(
                info["server_url"],
                info["doc_id"],
                user=info["user"],
                download_url=info["download_url"],
            )
        elif cmd == "trigger-watch":
            for engine in manager._engine_definitions:
                manager.osi.watch_folder(engine.local_folder)
        elif cmd == "token":
            self.api.handle_token(info["token"], info["username"])
        else:
            log.warning("Unknown event URL=%r, info=%r", url, info)
            return False
        return True

    @if_frozen
    def init_nxdrive_listener(self) -> None:
        """
        Set up a QLocalServer to listen to nxdrive protocol calls.

        On Windows, when an nxdrive:// URL is opened, it creates a new
        instance of Nuxeo Drive. As we want the already running instance to
        receive this call (particularly during the login process), we set
        up a QLocalServer in that instance to listen to the new ones who will
        send their data.
        The Qt implementation of QLocalSocket on Windows makes use of named
        pipes. We just need to connect a handler to the newConnection signal
        to process the URLs.
        """

        self._nxdrive_listener = QLocalServer()
        self._nxdrive_listener.newConnection.connect(self._handle_connection)
        self._nxdrive_listener.listen("com.nuxeo.drive.protocol")
        self.aboutToQuit.connect(self._nxdrive_listener.close)

    @if_frozen
    def _handle_connection(self) -> None:
        """ Retrieve the connection with other instances and handle the incoming data. """

        con: QLocalSocket = self._nxdrive_listener.nextPendingConnection()
        log.debug("Receiving socket connection for nxdrive protocol handling")
        if not con or not con.waitForConnected():
            log.error(f"Unable to open server socket: {con.errorString()}")
            return

        if con.waitForReadyRead():
            payload = con.readAll()
            url = force_decode(payload.data())
            self._handle_nxdrive_url(url)

        con.disconnectFromServer()
        con.waitForDisconnected()
        del con
        log.debug("Successfully closed server socket")

        # Bring settings window to front
        self.settings_window.show()

    def update_status(self, engine: "Engine") -> None:
        """
        Update the systray status for synchronization,
        conflicts/errors and software updates.
        """
        sync_state = error_state = update_state = ""

        update_state = self.manager.updater.status
        self.refresh_conflicts(engine.uid)

        # Check synchronization state
        if engine.is_paused():
            sync_state = "suspended"
        elif engine.is_syncing():
            sync_state = "syncing"

        # Check error state
        if engine.has_invalid_credentials():
            error_state = "auth_expired"
        elif self.conflicts_model.count:
            error_state = "conflicted"
        elif self.errors_model.count:
            error_state = "error"

        self._window_root(self.systray_window).setStatus.emit(
            sync_state, error_state, update_state)

    @pyqtSlot(str)
    def get_last_files(self, uid: str) -> None:
        files = self.api.get_last_files(uid, 10, "", None)
        self.file_model.empty()
        self.file_model.addFiles(files)

    def current_language(self) -> Optional[str]:
        lang = Translator.locale()
        for tag, name in self.language_model.languages:
            if tag == lang:
                return name
        return None
Exemplo n.º 31
0
class QtSingleApplication(QApplication):
    """
    This class makes sure that we can only start one Tribler application.
    When a user tries to open a second Tribler instance, the current active one will be brought to front.
    """

    messageReceived = pyqtSignal(unicode)

    def __init__(self, win_id, *argv):

        logfunc = logging.info
        logfunc(sys._getframe().f_code.co_name + '()')

        QApplication.__init__(self, *argv)

        self._id = win_id
        self._activation_window = None
        self._activate_on_message = False

        # Is there another instance running?
        self._outSocket = QLocalSocket()
        self._outSocket.connectToServer(self._id)
        self._isRunning = self._outSocket.waitForConnected()

        self._outStream = None
        self._inSocket = None
        self._inStream = None
        self._server = None

        if self._isRunning:
            # Yes, there is.
            self._outStream = QTextStream(self._outSocket)
            self._outStream.setCodec('UTF-8')
        else:
            # No, there isn't, at least not properly.
            # Cleanup any past, crashed server.
            error = self._outSocket.error()
            logfunc(LOGVARSTR % ('self._outSocket.error()', error))
            if error == QLocalSocket.ConnectionRefusedError:
                logfunc('received QLocalSocket.ConnectionRefusedError; ' + \
                        'removing server.')
                self.close()
                QLocalServer.removeServer(self._id)
            self._outSocket = None
            self._server = QLocalServer()
            self._server.listen(self._id)
            self._server.newConnection.connect(self._on_new_connection)

        logfunc(sys._getframe().f_code.co_name + '(): returning')

    def close(self):
        logfunc = logging.info
        logfunc(sys._getframe().f_code.co_name + '()')
        if self._inSocket:
            self._inSocket.disconnectFromServer()
        if self._outSocket:
            self._outSocket.disconnectFromServer()
        if self._server:
            self._server.close()
        logfunc(sys._getframe().f_code.co_name + '(): returning')

    def is_running(self):
        return self._isRunning

    def get_id(self):
        return self._id

    def activation_window(self):
        return self._activation_window

    def set_activation_window(self, activation_window, activate_on_message=True):
        self._activation_window = activation_window
        self._activate_on_message = activate_on_message

    def activate_window(self):
        if not self._activation_window:
            return
        self._activation_window.setWindowState(
            self._activation_window.windowState() & ~Qt.WindowMinimized)
        self._activation_window.raise_()

    def send_message(self, msg):
        if not self._outStream:
            return False
        self._outStream << msg << '\n'
        self._outStream.flush()
        return self._outSocket.waitForBytesWritten()

    def _on_new_connection(self):
        if self._inSocket:
            self._inSocket.readyRead.disconnect(self._on_ready_read)
        self._inSocket = self._server.nextPendingConnection()
        if not self._inSocket:
            return
        self._inStream = QTextStream(self._inSocket)
        self._inStream.setCodec('UTF-8')
        self._inSocket.readyRead.connect(self._on_ready_read)
        if self._activate_on_message:
            self.activate_window()

    def _on_ready_read(self):
        while True:
            msg = self._inStream.readLine()
            if not msg:
                break
            self.messageReceived.emit(msg)
Exemplo n.º 32
0
class SingleInstance:
    def __init__(self, application, files_to_open: Optional[List[str]]):
        self._application = application
        self._files_to_open = files_to_open

        self._single_instance_server = None

    # Starts a client that checks for a single instance server and sends the files that need to opened if the server
    # exists. Returns True if the single instance server is found, otherwise False.
    def startClient(self) -> bool:
        Logger.log(
            "i",
            "Checking for the presence of an ready running Cura instance.")
        single_instance_socket = QLocalSocket(self._application)
        Logger.log("d", "Full single instance server name: %s",
                   single_instance_socket.fullServerName())
        single_instance_socket.connectToServer("ultimaker-cura")
        single_instance_socket.waitForConnected(
            msecs=3000)  # wait for 3 seconds

        if single_instance_socket.state() != QLocalSocket.ConnectedState:
            return False

        # We only send the files that need to be opened.
        if not self._files_to_open:
            Logger.log("i", "No file need to be opened, do nothing.")
            return True

        if single_instance_socket.state() == QLocalSocket.ConnectedState:
            Logger.log(
                "i",
                "Connection has been made to the single-instance Cura socket.")

            # Protocol is one line of JSON terminated with a carriage return.
            # "command" field is required and holds the name of the command to execute.
            # Other fields depend on the command.

            payload = {"command": "clear-all"}
            single_instance_socket.write(
                bytes(json.dumps(payload) + "\n", encoding="ascii"))

            payload = {"command": "focus"}
            single_instance_socket.write(
                bytes(json.dumps(payload) + "\n", encoding="ascii"))

            for filename in self._files_to_open:
                payload = {
                    "command": "open",
                    "filePath": os.path.abspath(filename)
                }
                single_instance_socket.write(
                    bytes(json.dumps(payload) + "\n", encoding="ascii"))

            payload = {"command": "close-connection"}
            single_instance_socket.write(
                bytes(json.dumps(payload) + "\n", encoding="ascii"))

            single_instance_socket.flush()
            single_instance_socket.waitForDisconnected()
        return True

    def startServer(self) -> None:
        self._single_instance_server = QLocalServer()
        self._single_instance_server.newConnection.connect(
            self._onClientConnected)
        self._single_instance_server.listen("ultimaker-cura")

    def _onClientConnected(self):
        Logger.log("i",
                   "New connection recevied on our single-instance server")
        connection = self._single_instance_server.nextPendingConnection()

        if connection is not None:
            connection.readyRead.connect(
                lambda c=connection: self.__readCommands(c))

    def __readCommands(self, connection):
        line = connection.readLine()
        while len(line) != 0:  # There is also a .canReadLine()
            try:
                payload = json.loads(str(line, encoding="ascii").strip())
                command = payload["command"]

                # Command: Remove all models from the build plate.
                if command == "clear-all":
                    self._application.callLater(
                        lambda: self._application.deleteAll())

                # Command: Load a model file
                elif command == "open":
                    self._application.callLater(lambda f=payload["filePath"]:
                                                self._application._openFile(f))

                # Command: Activate the window and bring it to the top.
                elif command == "focus":
                    # Operating systems these days prevent windows from moving around by themselves.
                    # 'alert' or flashing the icon in the taskbar is the best thing we do now.
                    self._application.callLater(
                        lambda: self._application.getMainWindow().alert(0))

                # Command: Close the socket connection. We're done.
                elif command == "close-connection":
                    connection.close()

                else:
                    Logger.log(
                        "w",
                        "Received an unrecognized command " + str(command))
            except json.decoder.JSONDecodeError as ex:
                Logger.log("w", "Unable to parse JSON command '%s': %s", line,
                           repr(ex))
            line = connection.readLine()
Exemplo n.º 33
0
class IPCServer(QObject):
    """IPC server to which clients connect to.

    Attributes:
        ignored: Whether requests are ignored (in exception hook).
        _timer: A timer to handle timeouts.
        _server: A QLocalServer to accept new connections.
        _socket: The QLocalSocket we're currently connected to.
        _socketname: The socketname to use.
        _atime_timer: Timer to update the atime of the socket regularly.

    Signals:
        got_args: Emitted when there was an IPC connection and arguments were
                  passed.
        got_args: Emitted with the raw data an IPC connection got.
        got_invalid_data: Emitted when there was invalid incoming data.
    """

    got_args = pyqtSignal(list, str, str)
    got_raw = pyqtSignal(bytes)
    got_invalid_data = pyqtSignal()

    def __init__(self, socketname, parent=None):
        """Start the IPC server and listen to commands.

        Args:
            socketname: The socketname to use.
            parent: The parent to be used.
        """
        super().__init__(parent)
        self.ignored = False
        self._socketname = socketname

        self._timer = usertypes.Timer(self, 'ipc-timeout')
        self._timer.setInterval(READ_TIMEOUT)
        self._timer.timeout.connect(self.on_timeout)

        if utils.is_windows:  # pragma: no cover
            self._atime_timer = None
        else:
            self._atime_timer = usertypes.Timer(self, 'ipc-atime')
            self._atime_timer.setInterval(ATIME_INTERVAL)
            self._atime_timer.timeout.connect(self.update_atime)
            self._atime_timer.setTimerType(Qt.VeryCoarseTimer)

        self._server = QLocalServer(self)
        self._server.newConnection.connect(  # type: ignore[attr-defined]
            self.handle_connection)

        self._socket = None
        self._old_socket = None

        if utils.is_windows:  # pragma: no cover
            # As a WORKAROUND for a Qt bug, we can't use UserAccessOption on Unix. If we
            # do, we don't get an AddressInUseError anymore:
            # https://bugreports.qt.io/browse/QTBUG-48635
            #
            # Thus, we only do so on Windows, and handle permissions manually in
            # listen() on Linux.
            log.ipc.debug("Calling setSocketOptions")
            self._server.setSocketOptions(QLocalServer.UserAccessOption)
        else:  # pragma: no cover
            log.ipc.debug("Not calling setSocketOptions")

    def _remove_server(self):
        """Remove an existing server."""
        ok = QLocalServer.removeServer(self._socketname)
        if not ok:
            raise Error("Error while removing server {}!".format(
                self._socketname))

    def listen(self):
        """Start listening on self._socketname."""
        log.ipc.debug("Listening as {}".format(self._socketname))
        if self._atime_timer is not None:  # pragma: no branch
            self._atime_timer.start()
        self._remove_server()
        ok = self._server.listen(self._socketname)
        if not ok:
            if self._server.serverError() == QAbstractSocket.AddressInUseError:
                raise AddressInUseError(self._server)
            raise ListenError(self._server)

        if not utils.is_windows:  # pragma: no cover
            # WORKAROUND for QTBUG-48635, see the comment in __init__ for details.
            try:
                os.chmod(self._server.fullServerName(), 0o700)
            except FileNotFoundError:
                # https://github.com/qutebrowser/qutebrowser/issues/1530
                # The server doesn't actually exist even if ok was reported as
                # True, so report this as an error.
                raise ListenError(self._server)

    @pyqtSlot('QLocalSocket::LocalSocketError')
    def on_error(self, err):
        """Raise SocketError on fatal errors."""
        if self._socket is None:
            # Sometimes this gets called from stale sockets.
            log.ipc.debug("In on_error with None socket!")
            return
        self._timer.stop()
        log.ipc.debug("Socket 0x{:x}: error {}: {}".format(
            id(self._socket), self._socket.error(),
            self._socket.errorString()))
        if err != QLocalSocket.PeerClosedError:
            raise SocketError("handling IPC connection", self._socket)

    @pyqtSlot()
    def handle_connection(self):
        """Handle a new connection to the server."""
        if self.ignored:
            return
        if self._socket is not None:
            log.ipc.debug("Got new connection but ignoring it because we're "
                          "still handling another one (0x{:x}).".format(
                              id(self._socket)))
            return
        socket = self._server.nextPendingConnection()
        if socket is None:
            log.ipc.debug(  # type: ignore[unreachable]
                "No new connection to handle.")
            return
        log.ipc.debug("Client connected (socket 0x{:x}).".format(id(socket)))
        self._socket = socket
        self._timer.start()
        socket.readyRead.connect(  # type: ignore[attr-defined]
            self.on_ready_read)
        if socket.canReadLine():
            log.ipc.debug("We can read a line immediately.")
            self.on_ready_read()
        socket.error.connect(self.on_error)  # type: ignore[attr-defined]
        if socket.error() not in [
                QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError
        ]:
            log.ipc.debug("We got an error immediately.")
            self.on_error(socket.error())
        socket.disconnected.connect(  # type: ignore[attr-defined]
            self.on_disconnected)
        if socket.state() == QLocalSocket.UnconnectedState:
            log.ipc.debug("Socket was disconnected immediately.")
            self.on_disconnected()

    @pyqtSlot()
    def on_disconnected(self):
        """Clean up socket when the client disconnected."""
        log.ipc.debug("Client disconnected from socket 0x{:x}.".format(
            id(self._socket)))
        self._timer.stop()
        if self._old_socket is not None:
            self._old_socket.deleteLater()
        self._old_socket = self._socket
        self._socket = None
        # Maybe another connection is waiting.
        self.handle_connection()

    def _handle_invalid_data(self):
        """Handle invalid data we got from a QLocalSocket."""
        assert self._socket is not None
        log.ipc.error("Ignoring invalid IPC data from socket 0x{:x}.".format(
            id(self._socket)))
        self.got_invalid_data.emit()
        self._socket.error.connect(self.on_error)
        self._socket.disconnectFromServer()

    def _handle_data(self, data):
        """Handle data (as bytes) we got from on_ready_read."""
        try:
            decoded = data.decode('utf-8')
        except UnicodeDecodeError:
            log.ipc.error("invalid utf-8: {!r}".format(binascii.hexlify(data)))
            self._handle_invalid_data()
            return

        log.ipc.debug("Processing: {}".format(decoded))
        try:
            json_data = json.loads(decoded)
        except ValueError:
            log.ipc.error("invalid json: {}".format(decoded.strip()))
            self._handle_invalid_data()
            return

        for name in ['args', 'target_arg']:
            if name not in json_data:
                log.ipc.error("Missing {}: {}".format(name, decoded.strip()))
                self._handle_invalid_data()
                return

        try:
            protocol_version = int(json_data['protocol_version'])
        except (KeyError, ValueError):
            log.ipc.error("invalid version: {}".format(decoded.strip()))
            self._handle_invalid_data()
            return

        if protocol_version != PROTOCOL_VERSION:
            log.ipc.error("incompatible version: expected {}, got {}".format(
                PROTOCOL_VERSION, protocol_version))
            self._handle_invalid_data()
            return

        args = json_data['args']

        target_arg = json_data['target_arg']
        if target_arg is None:
            # https://www.riverbankcomputing.com/pipermail/pyqt/2016-April/037375.html
            target_arg = ''

        cwd = json_data.get('cwd', '')
        assert cwd is not None

        self.got_args.emit(args, target_arg, cwd)

    @pyqtSlot()
    def on_ready_read(self):
        """Read json data from the client."""
        if self._socket is None:  # pragma: no cover
            # This happens when doing a connection while another one is already
            # active for some reason.
            if self._old_socket is None:
                log.ipc.warning("In on_ready_read with None socket and "
                                "old_socket!")
                return
            log.ipc.debug("In on_ready_read with None socket!")
            socket = self._old_socket
        else:
            socket = self._socket

        if sip.isdeleted(socket):  # pragma: no cover
            log.ipc.warning("Ignoring deleted IPC socket")
            return

        self._timer.stop()
        while socket is not None and socket.canReadLine():
            data = bytes(socket.readLine())
            self.got_raw.emit(data)
            log.ipc.debug("Read from socket 0x{:x}: {!r}".format(
                id(socket), data))
            self._handle_data(data)

        if self._socket is not None:
            self._timer.start()

    @pyqtSlot()
    def on_timeout(self):
        """Cancel the current connection if it was idle for too long."""
        assert self._socket is not None
        log.ipc.error("IPC connection timed out "
                      "(socket 0x{:x}).".format(id(self._socket)))
        self._socket.disconnectFromServer()
        if self._socket is not None:  # pragma: no cover
            # on_socket_disconnected sets it to None
            self._socket.waitForDisconnected(CONNECT_TIMEOUT)
        if self._socket is not None:  # pragma: no cover
            # on_socket_disconnected sets it to None
            self._socket.abort()

    @pyqtSlot()
    def update_atime(self):
        """Update the atime of the socket file all few hours.

        From the XDG basedir spec:

        To ensure that your files are not removed, they should have their
        access time timestamp modified at least once every 6 hours of monotonic
        time or the 'sticky' bit should be set on the file.
        """
        path = self._server.fullServerName()
        if not path:
            log.ipc.error("In update_atime with no server path!")
            return

        log.ipc.debug("Touching {}".format(path))

        try:
            os.utime(path)
        except OSError:
            log.ipc.exception("Failed to update IPC socket, trying to "
                              "re-listen...")
            self._server.close()
            self.listen()

    @pyqtSlot()
    def shutdown(self):
        """Shut down the IPC server cleanly."""
        log.ipc.debug("Shutting down IPC (socket 0x{:x})".format(
            id(self._socket)))
        if self._socket is not None:
            self._socket.deleteLater()
            self._socket = None
        self._timer.stop()
        if self._atime_timer is not None:  # pragma: no branch
            self._atime_timer.stop()
            try:
                self._atime_timer.timeout.disconnect(self.update_atime)
            except TypeError:
                pass
        self._server.close()
        self._server.deleteLater()
        self._remove_server()
Exemplo n.º 34
0
class QtSingleApplication(QApplication):

    messageReceived = pyqtSignal(str)

    def __init__(self, id, *argv):

        super(QtSingleApplication, self).__init__(*argv)
        self._id = id
        self._activationWindow = None
        self._activateOnMessage = False

        # Is there another instance running?
        self._outSocket = QLocalSocket()
        self._outSocket.connectToServer(self._id)
        self._isRunning = self._outSocket.waitForConnected()

        if self._isRunning:
            # Yes, there is.
            self._outStream = QTextStream(self._outSocket)
            self._outStream.setCodec("UTF-8")
        else:
            # No, there isn't.
            self._outSocket = None
            self._outStream = None
            self._inSocket = None
            self._inStream = None
            self._server = QLocalServer()
            self._server.listen(self._id)
            self._server.newConnection.connect(self._onNewConnection)

    def isRunning(self):
        return self._isRunning

    def id(self):
        return self._id

    def activationWindow(self):
        return self._activationWindow

    def setActivationWindow(self, activationWindow, activateOnMessage=True):
        self._activationWindow = activationWindow
        self._activateOnMessage = activateOnMessage

    def activateWindow(self):
        if not self._activationWindow:
            return

        self._activationWindow.show()
        self._activationWindow.setWindowState(self._activationWindow.windowState() & ~Qt.WindowMinimized)
        self._activationWindow.raise_()
        self._activationWindow.activateWindow()

    def sendMessage(self, msg):
        if not self._outStream:
            return False
        self._outStream << msg << "\n"
        self._outStream.flush()
        return self._outSocket.waitForBytesWritten()

    def _onNewConnection(self):
        if self._inSocket:
            self._inSocket.readyRead.disconnect(self._onReadyRead)
        self._inSocket = self._server.nextPendingConnection()
        if not self._inSocket:
            return
        self._inStream = QTextStream(self._inSocket)
        self._inStream.setCodec("UTF-8")
        self._inSocket.readyRead.connect(self._onReadyRead)
        if self._activateOnMessage:
            self.activateWindow()

    def _onReadyRead(self):
        while True:
            msg = self._inStream.readLine()
            if not msg:
                break
            self.messageReceived.emit(msg)
Exemplo n.º 35
0
class MainForm(QDialog):
   def __init__(self, parent = None):
      QDialog.__init__(self, parent)
      
      # If a Nemu instance is already running, this is as far as we go
      self.connectToRunning()
      
      self.holdOpen = False
      self.menuItems = []
      self.allItems = []
      self.favorites = []
      self.currentItem = None
      self.menuFile = os.path.expanduser('~/.nemu/menu')
      self.favoritesFile = os.path.expanduser('~/.nemu/favorites')
      # NOTE: If you change this, also update migrate-settings
      self.settingsFile = os.path.expanduser('~/.nemu/settings')
      self.initSettings()

      self.server = QLocalServer()
      self.server.newConnection.connect(self.handleConnection)
      QLocalServer.removeServer('nemuSocket')
      self.server.listen('nemuSocket')
      
      self.configDir = os.path.expanduser('~/.nemu')
      if not os.path.isdir(self.configDir):
         os.mkdir(self.configDir)
      self.menuItems = self.loadConfig(self.menuFile, self.menuItems)
      self.favorites = self.loadConfig(self.favoritesFile, self.favorites)
      # Don't load directly into self.settings so we can add new default values as needed
      try:
         tempSettings = self.loadConfig(self.settingsFile, self.settings)
         for key, value in tempSettings.items():
            self.settings[key] = value
      except SystemError:
         print('ERROR: Failed to load settings. You may need to run migrate-settings.')
         raise
      # This should never happen, but unfortunately bugs do, so clean up orphaned items.
      # We need to do this because these items won't show up in the UI, but may interfere with
      # merges if they duplicate something that is being merged in.
      self.menuItems[:] = [i for i in self.menuItems if i.parent == None or i.parent in self.menuItems]
      # Look for broken icon paths
      needSave = False
      for i in self.menuItems + self.favorites:
          if not os.path.exists(i.icon):
              i.findIcon()
              needSave = True
      if needSave:
         self.saveMenu()


      for i in self.menuItems:
         if not hasattr(i, 'imported'):
            i.imported = False
      
      self.setupUI()
      
      self.setContextMenuPolicy(Qt.ActionsContextMenu)
      self.createMenu(self)
      
      self.refresh(False)
      
      if len(self.menuItems) == 0:
         self.firstRun()
      
      self.show()
      
      self.keepaliveTimer = QTimer(self)
      self.keepaliveTimer.timeout.connect(self.keepalive)
      self.keepaliveTimer.start(60000)
      
      
   def initSettings(self):
      self.settings = dict()
      self.settings['width'] = 400
      self.settings['height'] = 400
      self.settings['quit'] = False
      self.settings['imported'] = []
      self.settings['iconTheme'] = None
      
      
   def loadConfig(self, filename, default):
      if os.path.exists(filename):
         with open(filename, 'rb') as f:
            data = f.read().replace('PyQt4', 'PyQt5')
            return cPickle.loads(data)
      else:
         return default
      
      
   def setupUI(self):
      self.resize(self.settings['width'], self.settings['height'])
      self.setWindowFlags(Qt.FramelessWindowHint | Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint)
      #self.setWindowFlags(Qt.X11BypassWindowManagerHint)
      self.setWindowTitle('Nemu')
      self.setMouseTracking(True)
      
      iconPath = os.path.join(os.path.dirname(__file__), 'images')
      iconPath = os.path.join(iconPath, 'nemu.png')
      self.setWindowIcon(IconCache()[iconPath])
      
      self.place()
      
      self.buttonListLayout = QVBoxLayout(self)
      self.setMargins(self.buttonListLayout)
      
      self.buttonLayout = QHBoxLayout()
      self.setMargins(self.buttonLayout)
      
      # Settings and Filter box
      self.filterLayout = QHBoxLayout()
      self.settingsButton = QPushButton()
      self.settingsButton.setIcon(QIcon(iconPath))
      self.settingsButton.setMinimumHeight(35)
      self.settingsButton.clicked.connect(self.settingsClicked)
      self.filterLayout.addWidget(self.settingsButton, 0)
      
      self.filterLabel = QLabel("Filter")
      self.filterLayout.addWidget(self.filterLabel)
      
      self.filterBox = QLineEdit()
      self.filterBox.textChanged.connect(self.refresh)
      self.filterLayout.addWidget(self.filterBox)
      
      self.sizeGrip = QSizeGrip(self)
      self.sizeGrip.setMinimumSize(QSize(25, 25))
      self.filterLayout.addWidget(self.sizeGrip, 0, Qt.AlignRight | Qt.AlignTop)
      
      self.buttonListLayout.addLayout(self.filterLayout)
      
      # Top buttons and labels
      self.backButton = QPushButton('Favorites')
      self.backButton.setMinimumHeight(35)
      self.backButton.clicked.connect(self.backClicked)
      self.buttonLayout.addWidget(self.backButton, 1)
      
      self.currentLabel = QLabel()
      self.currentLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
      self.buttonLayout.addWidget(self.currentLabel, 1)
      
      self.buttonListLayout.addLayout(self.buttonLayout, 0)
      
      # Menu item display
      self.listSplitter = QSplitter()
      self.buttonListLayout.addWidget(self.listSplitter, 1)
      
      self.leftList = ListWidget(self.clearListMouseOver)
      self.listSplitter.addWidget(self.leftList)
      
      self.rightList = ListWidget(self.clearListMouseOver)
      self.listSplitter.addWidget(self.rightList)
      
      # Has to be done after adding widgets to the splitter or the size will get reset again
      if 'splitterState' in self.settings:
         self.listSplitter.restoreState(self.settings['splitterState'])
      
   def setMargins(self, layout, margin = 0):
      layout.setSpacing(margin)
      layout.setContentsMargins(margin, margin, margin, margin)
      
      
   def createMenu(self, widget):
      addFavoriteAction = QAction('Add to Favorites', self)
      addFavoriteAction.triggered.connect(self.addFavoriteClicked)
      widget.insertAction(None, addFavoriteAction)
      addAction = QAction("New...", self)
      addAction.triggered.connect(self.newClicked)
      widget.insertAction(None, addAction)
      editAction = QAction("Edit...", self)
      editAction.triggered.connect(self.editClicked)
      widget.insertAction(None, editAction)
      deleteAction = QAction("Delete", self)
      deleteAction.triggered.connect(self.deleteClicked)
      widget.insertAction(None, deleteAction)
      
      
   def hideOrClose(self):
      if self.settings['quit']:
         self.close()
      else:
         self.hide()
         
   def closeEvent(self, event):
      self.saveSettings()
      
   def hideEvent(self, event):
      self.releaseMouse()
      self.saveSettings()
      
   def mouseMoveEvent(self, event):
      if self.hasMouse():
         self.releaseMouse()
      
   def leaveEvent(self, event):
      # If we set holdOpen, it means that we've opened a dialog, so we shouldn't grab
      if not self.hasMouse():
         self.grabMouse()
      
   def mousePressEvent(self, event):
      if not self.hasMouse():
         self.hideOrClose()
         
   def hasMouse(self):
      return self.geometry().contains(QCursor.pos())
         

   def saveSettings(self):
      self.settings['splitterState'] = self.listSplitter.saveState()
      self.settings['width'] = self.width()
      self.settings['height'] = self.height()
      with open(self.settingsFile, 'wb') as f:
         cPickle.dump(self.settings, f)
         
   def place(self):
      desktop = qApp.desktop()
      screenSize = desktop.availableGeometry(QCursor.pos())
      self.move(screenSize.x(), screenSize.y() + screenSize.height() - self.height())
         
         
   def newClicked(self):
      form = AddForm()
      
      self.holdOpen = True
      form.exec_()
      self.checkMouse()
      self.holdOpen = False
      
      if form.accepted:
         item = MenuItem()
         item.name = form.name
         item.command = form.command
         item.working = form.working
         item.folder = form.folder
         item.icon = form.icon
         item.findIcon()
         
         clicked = self.getClicked()
         if clicked:
            parent = clicked.item.parent
         elif self.leftList.mouseOver:
            if self.currentItem != None:
               parent = self.currentItem.parent
            else:
               parent = None
         else:
            parent = self.currentItem
         item.parent = parent
         
         self.menuItems.append(item)
         self.refresh()
      
   def editClicked(self):
      form = AddForm()
      clicked = self.getClicked()
      if clicked == None:
         return
      item = clicked.item
      
      form.name = item.name
      form.command = item.command
      form.working = item.working
      form.folder = item.folder
      form.icon = item.icon
      form.populateFields()
      
      self.holdOpen = True
      form.exec_()
      self.checkMouse()
      self.holdOpen = False
      
      if form.accepted:
         item.name = form.name
         item.command = form.command
         item.working = form.working
         item.folder = form.folder
         item.icon = form.icon
         item.imported = False
         item.findIcon()
         self.refresh()
         
         
   def checkMouse(self):
      if not self.hasMouse():
         self.grabMouse()
      
      
   def deleteClicked(self):
      clicked = self.getClicked()
      if clicked == None:
         return
      self.delete(clicked.item)
      self.refresh()
      
   # Delete item and all of its children so we don't leave around orphaned items
   def delete(self, item):
      for i in self.menuItems:
         if i.parent == item:
            i.deleted = True
      
      if item in self.menuItems:
         item.deleted = True
         item.imported = False
      if item in self.favorites:
         self.favorites.remove(item)
      
      
   def addFavoriteClicked(self):
      newFavorite = copy.copy(self.getClicked().item)
      newFavorite.parent = None
      self.favorites.append(newFavorite)
      self.refresh()
      
      
   def getClicked(self):
      for i in self.allItems:
         if i.mouseOver:
            return i
            
   def clearMouseOver(self):
      for i in self.allItems:
         i.mouseOver = False
         
   def clearListMouseOver(self):
      self.leftList.mouseOver = False
      self.rightList.mouseOver = False
      
      
   def refresh(self, save = True):
      self.leftList.clear()
      self.rightList.clear()
      self.allItems = []
      sortedLeft = []
      sortedRight = []
      self.updateFilter()
      
      if self.currentItem != None:
         currParent = self.currentItem.parent
         for i in self.menuItems:
            if i.parent == currParent and not i.deleted and i.matchedFilter:
               sortedLeft.append(i)
      else:
         for i in self.favorites:
            sortedLeft.append(i)
      
      for i in self.menuItems:
         if i.parent == self.currentItem and not i.deleted and i.matchedFilter:
            sortedRight.append(i)
            
      sortedLeft.sort(key = lambda x: x.name)
      sortedLeft.sort(key = lambda x: not x.folder)
      sortedRight.sort(key = lambda x: x.name)
      sortedRight.sort(key = lambda x: not x.folder)
      for i in sortedLeft:
         self.leftList.add(self.createItem(i))
      for i in sortedRight:
         self.rightList.add(self.createItem(i))
         
      if save:
         self.saveMenu()

   def saveMenu(self):
      # Save the current menu status
      with open(self.menuFile, 'wb') as f:
         cPickle.dump(self.menuItems, f)
      with open(self.favoritesFile, 'wb') as f:
         cPickle.dump(self.favorites, f)

   def createItem(self, item):
      newItem = ListItem(item, self.clearMouseOver)
      newItem.clicked.connect(self.itemClicked)
      self.allItems.append(newItem)
      return newItem
      
   def updateFilter(self):
      filterValue = str(self.filterBox.text())
      
      for i in self.menuItems:
         i.checkFilter(filterValue)
            
      
   def itemClicked(self):
      sender = self.sender()
      if sender.item.folder:
         self.setCurrentItem(sender.item)
         self.refresh(False)
      else:
         flags = ['f', 'F', 'u', 'U', 'd', 'D', 'n', 'N', 'i', 'k', 'v', 'm']
         command = sender.item.command
         for i in flags:
            command = command.replace('%' + i, '')
         # %c needs a proper value in some cases
         command = command.replace('%c', '"%s"' % sender.item.name)
         working = sender.item.working
         if not os.path.isdir(working):
            working = None
            
         # Need to redirect stdout and stderr so if the process writes something it won't fail
         with open(os.path.devnull, 'w') as devnull:
            Popen(command + '&', stdout=devnull, stderr=devnull, shell=True, cwd=working)
         self.hideOrClose()
         
         
   def backClicked(self):
      if self.currentItem:
         self.setCurrentItem(self.currentItem.parent)
         self.refresh(False)
         
         
   def setCurrentItem(self, item):
      self.currentItem = item
      if item != None:
         self.currentLabel.setText(item.name)
         if item.parent != None:
            self.backButton.setText(item.parent.name)
         else:
            self.backButton.setText('Favorites')
      else:
         self.currentLabel.setText('')
         self.backButton.setText('Favorites')
         
         
   def settingsClicked(self):
      form = SettingsForm(self)
      form.quitCheck.setChecked(self.settings['quit'])
      theme = self.settings.get('iconTheme')
      if theme:
         form.themeCombo.setCurrentIndex(form.themeCombo.findText(theme))
      
      self.holdOpen = True
      form.exec_()
      self.checkMouse()
      self.holdOpen = False
      
      if form.accepted:
         self.settings['quit'] = form.quitCheck.isChecked()
      
      
   def firstRun(self):
      QMessageBox.information(self, 'First Time?', 'Your menu is currently empty.  It is recommended that you import an existing menu file.')
      self.settingsClicked()
      
      
   def connectToRunning(self):
      self.socket = QLocalSocket()
      self.socket.connectToServer('nemuSocket')
      self.socket.waitForConnected(1000)
      
      if self.socket.state() == QLocalSocket.ConnectedState:
         print 'Server found'
         if self.socket.waitForReadyRead(3000):
            line = self.socket.readLine()
            print line
         else:
            print self.socket.errorString()
         sys.exit()
      else:
         print 'No server running'
      
      
   def handleConnection(self):
      import datetime
      print "Got connection", datetime.datetime.now()
      
      connection = self.server.nextPendingConnection()
      connection.write('connected')
      del connection
      
      self.setCurrentItem(None)
      self.filterBox.setText('')
      self.refresh(False)
      self.show()
      print "Showed", datetime.datetime.now()
      return
      
      
   # Call periodically to keep data resident in memory (hopefully)
   def keepalive(self):
      if self.isHidden():
         self.refresh(False)
Exemplo n.º 36
0
class IDE(QMainWindow):
    """This class is like the Sauron's Ring:
    One ring to rule them all, One ring to find them,
    One ring to bring them all and in the darkness bind them.

    This Class knows all the containers, and its know by all the containers,
    but the containers don't need to know between each other, in this way we
    can keep a better api without the need to tie the behaviour between
    the widgets, and let them just consume the 'actions' they need."""

###############################################################################
# SIGNALS
###############################################################################
    goingDown = pyqtSignal()
    filesAndProjectsLoaded = pyqtSignal()

    __IDESERVICES = {}
    __IDECONNECTIONS = {}
    __IDESHORTCUTS = {}
    __IDEBARCATEGORIES = {}
    __IDEMENUS = {}
    __IDETOOLBAR = {}
    # CONNECTIONS structure:
    # ({'target': service_name, 'signal_name': string, 'slot': function_obj},)
    # On modify add: {connected: True}
    __instance = None
    __created = False

    def __init__(self, start_server=False):
        QMainWindow.__init__(self)
        self.setWindowTitle('NINJA-IDE {Ninja-IDE Is Not Just Another IDE}')
        self.setMinimumSize(750, 500)
        QToolTip.setFont(QFont(settings.FONT.family(), 10))
        # Load the size and the position of the main window
        self.load_window_geometry()
        # self.__project_to_open = 0
        # Editables
        self.__neditables = {}
        # Filesystem
        self.filesystem = nfilesystem.NVirtualFileSystem()
        # Interpreter service
        self.interpreter = interpreter_service.InterpreterService()
        # Sessions handler
        self._session_manager = session_manager.SessionsManager(self)
        IDE.register_service("session_manager", self._session_manager)
        self._session = None
        # Opacity
        self.opacity = settings.MAX_OPACITY
        # ToolBar
        # self.toolbar = QToolBar(self)
        # if settings.IS_MAC_OS:
        #     self.toolbar.setIconSize(QSize(36, 36))
        # else:
        #     self.toolbar.setIconSize(QSize(24, 24))
        # self.toolbar.setToolTip(translations.TR_IDE_TOOLBAR_TOOLTIP)
        # self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly)
        # # Set toggleViewAction text and tooltip
        # self.toggleView = self.toolbar.toggleViewAction()
        # self.toggleView.setText(translations.TR_TOOLBAR_VISIBILITY)
        # self.toggleView.setToolTip(translations.TR_TOOLBAR_VISIBILITY)
        # self.addToolBar(settings.TOOLBAR_AREA, self.toolbar)
        # if settings.HIDE_TOOLBAR:
        #     self.toolbar.hide()
        # Notificator
        self.notification = notification.Notification(self)

        # Plugin Manager
        # CHECK ACTIVATE PLUGINS SETTING
        # services = {
        #    'editor': plugin_services.MainService(),
        #    'toolbar': plugin_services.ToolbarService(self.toolbar),
        #    'menuApp': plugin_services.MenuAppService(self.pluginsMenu),
        #    'menuApp': plugin_services.MenuAppService(None),
        #    'explorer': plugin_services.ExplorerService(),
        #    'misc': plugin_services.MiscContainerService(self.misc)}
        # serviceLocator = plugin_manager.ServiceLocator(services)
        # serviceLocator = plugin_manager.ServiceLocator(None)
        # self.plugin_manager = plugin_manager.PluginManager(resources.PLUGINS,
        #                                                   serviceLocator)
        # self.plugin_manager.discover()
        # load all plugins!
        # self.plugin_manager.load_all()

        # Tray Icon
        # self.trayIcon = updates.TrayIconUpdates(self)
        # self.trayIcon.closeTrayIcon.connect(self._close_tray_icon)
        # self.trayIcon.show()

        # TODO:
        # key = Qt.Key_1
        # for i in range(10):
        #    if settings.IS_MAC_OS:
        #        short = ui_tools.TabShortcuts(
        #            QKeySequence(Qt.CTRL + Qt.ALT + key), self, i)
        #    else:
        #        short = ui_tools.TabShortcuts(
        #            QKeySequence(Qt.ALT + key), self, i)
        #    key += 1
        #    short.activated.connect(self._change_tab_index)
        # short = ui_tools.TabShortcuts(
        #       QKeySequence(Qt.ALT + Qt.Key_0), self, 10)
        # short.activated.connect(self._change_tab_index)

        # Register menu categories
        IDE.register_bar_category(translations.TR_MENU_FILE, 100)
        IDE.register_bar_category(translations.TR_MENU_EDIT, 110)
        IDE.register_bar_category(translations.TR_MENU_VIEW, 120)
        IDE.register_bar_category(translations.TR_MENU_SOURCE, 130)
        IDE.register_bar_category(translations.TR_MENU_PROJECT, 140)
        IDE.register_bar_category(translations.TR_MENU_EXTENSIONS, 150)
        IDE.register_bar_category(translations.TR_MENU_ABOUT, 160)
        # Register General Menu Items
        ui_tools.install_shortcuts(self, actions.ACTIONS_GENERAL, self)
        self.register_service("ide", self)
        self.register_service("interpreter", self.interpreter)
        # self.register_service('toolbar', self.toolbar)
        self.register_service("filesystem", self.filesystem)
        self.toolbar = IDE.get_service("toolbar")
        # Register signals connections
        connections = (
            {
                "target": "main_container",
                "signal_name": "fileSaved",
                "slot": self.show_message
            },
            {
                "target": "main_container",
                "signal_name": "currentEditorChanged",
                "slot": self.change_window_title
            },
            {
                "target": "main_container",
                "signal_name": "openPreferences",
                "slot": self.show_preferences
            },
            {
                "target": "main_container",
                "signal_name": "currentEditorChanged",
                "slot": self._change_item_in_project
            },
            {
                "target": "main_container",
                "signal_name": "allFilesClosed",
                "slot": self.change_window_title
            },
            {
                "target": "projects_explorer",
                "signal_name": "activeProjectChanged",
                "slot": self.change_window_title
            }
        )
        self.register_signals('ide', connections)
        # connections = (
        #    {'target': 'main_container',
        #     'signal_name': 'openPreferences()',
        #     'slot': self.show_preferences},
        #    {'target': 'main_container',
        #     'signal_name': 'allTabsClosed()',
        #     'slot': self._last_tab_closed},
        #    {'target': 'explorer_container',
        #     'signal_name': 'changeWindowTitle(QString)',
        #     'slot': self.change_window_title},
        #    {'target': 'explorer_container',
        #     'signal_name': 'projectClosed(QString)',
        #     'slot': self.close_project},
        #    )
        # Central Widget MUST always exists
        self.central = IDE.get_service('central_container')
        self.setCentralWidget(self.central)
        # Install Services
        for service_name in self.__IDESERVICES:
            self.install_service(service_name)
        IDE.__created = True
        # Place Status Bar
        main_container = IDE.get_service('main_container')
        status_bar = IDE.get_service('status_bar')
        main_container.add_status_bar(status_bar)
        # Load Menu Bar
        menu_bar = IDE.get_service('menu_bar')
        if menu_bar:
            # These two are the same service, I think that's ok
            menu_bar.load_menu(self)
            menu_bar.load_toolbar(self)

        # Start server if needed
        self.s_listener = None
        if start_server:
            self.s_listener = QLocalServer()
            self.s_listener.listen("ninja_ide")
            self.s_listener.newConnection.connect(self._process_connection)

        # Load interpreters
        self.load_interpreters()

        IDE.__instance = self

    def _change_item_in_project(self, filename):
        project_explorer = IDE.get_service("projects_explorer")
        if project_explorer is not None:
            project_explorer.set_current_item(filename)

    @classmethod
    def get_service(cls, service_name):
        """Return the instance of a registered service."""

        service = cls.__IDESERVICES.get(service_name, None)
        if service is None:
            logger.debug("Service '{}' unregistered".format(service_name))
        return service

    def get_menuitems(self):
        """Return a dictionary with the registered menu items."""
        return IDE.__IDEMENUS  #

    def get_bar_categories(self):
        """Get the registered Categories for the Application menus."""
        return IDE.__IDEBARCATEGORIES

    def get_toolbaritems(self):
        """Return a dictionary with the registered menu items."""
        return IDE.__IDETOOLBAR

    @classmethod
    def register_service(cls, service_name, obj):
        """Register a service providing the service name and the instance."""
        cls.__IDESERVICES[service_name] = obj
        if cls.__created:
            cls.__instance.install_service(service_name)

    def install_service(self, service_name):
        """ Activate the registered service """

        obj = IDE.__IDESERVICES.get(service_name, None)
        func = getattr(obj, 'install', None)
        if isinstance(func, collections.Callable):
            func()
        self._connect_signals()

    def place_me_on(self, name, obj, region="central", top=False):
        """Place a widget in some of the areas in the IDE.
        @name: id to access to that widget later if needed.
        @obj: the instance of the widget to be placed.
        @region: the area where to put the widget [central, lateral]
        @top: place the widget as the first item in the split."""
        self.central.add_to_region(name, obj, region, top)

    @classmethod
    def register_signals(cls, service_name, connections):
        """Register all the signals that a particular service wants to be
        attached of.
        @service_name: id of the service
        @connections: list of dictionaries for the connection with:
            - 'target': 'the_other_service_name',
            - 'signal_name': 'name of the signal in the other service',
            - 'slot': function object in this service"""
        cls.__IDECONNECTIONS[service_name] = connections
        if cls.__created:
            cls.__instance._connect_signals()

    def _connect_signals(self):
        """Connect the signals between the different services."""
        for service_name in IDE.__IDECONNECTIONS:
            connections = IDE.__IDECONNECTIONS[service_name]
            for connection in connections:
                if connection.get('connected', False):
                    continue
                target = IDE.__IDESERVICES.get(
                    connection['target'], None)
                slot = connection['slot']
                signal_name = connection['signal_name']
                if target and isinstance(slot, collections.Callable):
                    # FIXME:
                    sl = getattr(target, signal_name, None)

                    if sl is not None:
                        sl.connect(slot)
                        connection['connected'] = True

                    # print("Falta conectar {} a {}".format(signal_name,
                    #                                      slot.__name__))
                    # self.connect(target, SIGNAL(signal_name), slot)
                    # connection['connected'] = True

    @classmethod
    def register_shortcut(cls, shortcut_name, shortcut, action=None):
        """ Register a shortcut and action """

        cls.__IDESHORTCUTS[shortcut_name] = (shortcut, action)

    @classmethod
    def register_menuitem(cls, menu_action, section, weight):
        """Register a QAction or QMenu in the IDE to be loaded later in the
        menubar using the section(string) to define where is going to be
        contained, and the weight define the order where is going to be
        placed.
        @menu_action: QAction or QMenu
        @section: String (name)
        @weight: int"""
        cls.__IDEMENUS[menu_action] = (section, weight)

    @classmethod
    def register_toolbar(cls, action, section, weight):
        """Register a QAction in the IDE to be loaded later in the
        toolbar using the section(string) to define where is going to be
        contained, and the weight define the order where is going to be
        placed.
        @action: QAction
        @section: String (name)
        @weight: int"""
        cls.__IDETOOLBAR[action] = (section, weight)

    @classmethod
    def register_bar_category(cls, category_name, weight):
        """Register a Menu Category to be created with the proper weight.
        @category_name: string
        @weight: int"""
        cls.__IDEBARCATEGORIES[category_name] = weight

    @classmethod
    def update_shortcut(cls, shortcut_name):
        """Update all the shortcuts of the application."""
        short = resources.get_shortcut
        shortcut, action = cls.__IDESHORTCUTS.get(shortcut_name)
        if shortcut:
            shortcut.setKey(short(shortcut_name))
        if action:
            action.setShortcut(short(shortcut_name))

    def get_or_create_nfile(self, filename):
        """For convenience access to files from ide"""
        return self.filesystem.get_file(nfile_path=filename)

    def get_editable(self, nfile=None):
        return self.__neditables.get(nfile)

    def get_or_create_editable(self, filename="", nfile=None):
        if nfile is None:
            nfile = self.filesystem.get_file(nfile_path=filename)
        editable = self.__neditables.get(nfile)
        if editable is None:
            editable = neditable.NEditable(nfile)
            editable.fileClosing['PyQt_PyObject'].connect(
                self._unload_neditable)
            self.__neditables[nfile] = editable
        return editable

    def _unload_neditable(self, editable):
        self.__neditables.pop(editable.nfile)
        editable.nfile.deleteLater()
        editable.editor.deleteLater()
        editable.deleteLater()

    @property
    def opened_files(self):
        return tuple(self.__neditables.keys())

    def get_project_for_file(self, filename):
        project = None
        if filename:
            project = self.filesystem.get_project_for_file(filename)
        return project

    def create_project(self, path):
        nproj = nproject.NProject(path)
        self.filesystem.open_project(nproj)
        return nproj

    def close_project(self, project_path):
        self.filesystem.close_project(project_path)

    def get_projects(self):
        return self.filesystem.get_projects()

    def get_current_project(self):
        current_project = None
        projects = self.filesystem.get_projects()
        for project in projects:
            if projects[project].is_current:
                current_project = projects[project]
                break
        return current_project

    def get_interpreters(self):
        return self.interpreter.get_interpreters()

    def get_interpreter(self, path):
        return self.interpreter.get_interpreter(path)

    def set_interpreter(self, path):
        self.interpreter.set_interpreter(path)

    def load_interpreters(self):
        # ds = self.data_settings()
        # settings.PYTHON_EXEC = ds.value("ide/interpreter")
        self.interpreter.load()

    @classmethod
    def select_current(cls, widget):
        """Show the widget with a 4px lightblue border line."""
        widget.setProperty("highlight", True)
        widget.style().unpolish(widget)
        widget.style().polish(widget)

    @classmethod
    def unselect_current(cls, widget):
        """Remove the 4px lightblue border line from the widget."""
        widget.setProperty("highlight", False)
        widget.style().unpolish(widget)
        widget.style().polish(widget)

    def _close_tray_icon(self):
        """Close the System Tray Icon."""
        self.trayIcon.hide()
        self.trayIcon.deleteLater()

    # def _change_tab_index(self):
    #    """Change the tabs of the current TabWidget using alt+numbers."""
    #    widget = QApplication.focusWidget()
    #    shortcut_index = getattr(widget, 'shortcut_index', None)
    #    if shortcut_index:
    #        obj = self.sender()
    #        shortcut_index(obj.index)

    def _process_connection(self):
        """Read the ipc input from another instance of ninja."""
        connection = self.s_listener.nextPendingConnection()
        connection.waitForReadyRead()
        data = connection.readAll()
        connection.close()
        if data:
            files, projects = str(data).split(ipc.project_delimiter, 1)
            files = [(x.split(':')[0], int(x.split(':')[1]))
                     for x in files.split(ipc.file_delimiter)]
            projects = projects.split(ipc.project_delimiter)
            self.load_session_files_projects(files, [], projects, None)

    def fullscreen_mode(self):
        """Change to fullscreen mode."""
        if self.isFullScreen():
            self.showMaximized()
        else:
            self.showFullScreen()

    def change_toolbar_visibility(self):
        """Switch the toolbar visibility"""
        if self.toolbar.isVisible():
            self.toolbar.hide()
        else:
            self.toolbar.show()

    def change_toolsdock_visibility(self):
        """Switch the tools dock visibility"""
        tools_dock = IDE.get_service("tools_dock").buttons_widget
        if tools_dock.isVisible():
            tools_dock.hide()
        else:
            tools_dock.show()

    def load_external_plugins(self, paths):
        """Load external plugins, the ones added to ninja throw the cmd."""
        for path in paths:
            self.plugin_manager.add_plugin_dir(path)
        # load all plugins!
        self.plugin_manager.discover()
        self.plugin_manager.load_all()

    def _last_tab_closed(self):
        """
        Called when the last tasb is closed
        """
        self.explorer.cleanup_tabs()

    def show_preferences(self):
        """Open the Preferences Dialog."""
        pref = preferences.Preferences(self)
        # main_container = IDE.get_service("main_container")
        # if main_container:
        #    main_container.show_dialog(pref)
        # else:
        pref.setModal(True)
        pref.show()

    def load_session_files_projects(self, files, projects, current_file):
        """Load the files and projects from previous session."""
        # Load projects
        projects_explorer = IDE.get_service('projects_explorer')
        if projects_explorer is not None:
            projects_explorer.load_session_projects(projects)
        # Load files
        main_container = IDE.get_service('main_container')
        for path, cursor_pos in files:
            line, col = cursor_pos
            main_container.open_file(path, line, col)
        if current_file:
            main_container.open_file(current_file)
        self.filesAndProjectsLoaded.emit()
        # projects_explorer = IDE.get_service('projects_explorer')
        # if main_container and files:
        #    for fileData in files:
        #        if file_manager.file_exists(fileData[0]):
        #            mtime = os.stat(fileData[0]).st_mtime
        #            ignore_checkers = (mtime == fileData[2])
        #            line, col = fileData[1][0], fileData[1][1]
        #            main_container.open_file(fileData[0], line, col,
        #                                     ignore_checkers=ignore_checkers)
        #    if current_file:
        #        main_container.open_file(current_file)
        # if projects_explorer and projects:
        #    projects_explorer.load_session_projects(projects)

    # def _set_editors_project_data(self):
    #     self.__project_to_open -= 1
    #     if self.__project_to_open == 0:
    #         self.disconnect(self.explorer, SIGNAL("projectOpened(QString)"),
    #             self._set_editors_project_data)
    #         self.mainContainer.update_editor_project()

    # def open_file(self, filename):
    #     if filename:
    #         self.mainContainer.open_file(filename)

    # def open_project(self, project):
    #     if project:
    #         self.actions.open_project(project)

    def __get_session(self):
        return self._session

    def __set_session(self, sessionName):
        self._session = sessionName
        if self._session is not None:
            self.setWindowTitle(translations.TR_SESSION_IDE_HEADER %
                                {'session': self._session})
        else:
            self.setWindowTitle(
                'NINJA-IDE {Ninja-IDE Is Not Just Another IDE}')

    Session = property(__get_session, __set_session)

    def change_window_title(self, text=""):
        """Change the title of the Application

        display_name - [project] - {session} - NINJA-IDE
        """
        title = []
        main_container = self.get_service("main_container")
        neditor = main_container.get_current_editor()
        if neditor is not None:
            nfile = neditor.nfile
            title.append(nfile.display_name)
        nproject = self.get_current_project()
        if nproject is not None:
            title.append("[" + nproject.name + "]")

        session = self._session_manager.current_session
        if session is not None:
            title.append(translations.TR_SESSION_IDE_HEADER.format(session))

        title.append("NINJA-IDE")
        formated_list = ["{}" for _ in title]
        self.setWindowTitle(" - ".join(formated_list).format(*title))

    def wheelEvent(self, event):
        """Change the opacity of the application."""
        if event.modifiers() == Qt.ShiftModifier:
            if event.delta() == 120 and self.opacity < settings.MAX_OPACITY:
                self.opacity += 0.1
            elif event.delta() == -120 and self.opacity > settings.MIN_OPACITY:
                self.opacity -= 0.1
            self.setWindowOpacity(self.opacity)
            event.ignore()
        else:
            QMainWindow.wheelEvent(self, event)

    @classmethod
    def ninja_settings(cls):
        qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat)
        return qsettings

    @classmethod
    def editor_settings(cls):
        qsettings = nsettings.NSettings(resources.SETTINGS_PATH)
        main_container = cls.get_service("main_container")
        # Connect valueChanged signal to _editor_settings_changed slot
        qsettings.valueChanged.connect(main_container._editor_settings_changed)
        return qsettings

    @classmethod
    def data_settings(cls):
        qsettings = QSettings(
            resources.DATA_SETTINGS_PATH, QSettings.IniFormat)
        return qsettings

    # @classmethod
    # def ninja_settings(cls, qobject=None):
    #    qsettings = nsettings.NSettings(resources.SETTINGS_PATH, qobject,
    #                                    prefix="ns")
    #    if cls.__created:
    #        qsettings.valueChanged['PyQt_PyObject',
    #                               'QString',
    #                               'PyQt_PyObject'].connect(
    #                                   cls.__instance._settings_value_changed)
    #    return qsettings

    # @classmethod
    # def data_settings(cls):
    #    qsettings = nsettings.NSettings(resources.DATA_SETTINGS_PATH,
    #                                    prefix="ds")
    #    if cls.__created:
    #        qsettings.valueChanged['PyQt_PyObject',
    #                               'QString',
    #                               'PyQt_PyObject'].connect(
    #                                   cls.__instance._settings_value_changed)
    #    return qsettings

    # def _settings_value_changed(self, qobject, key, value):
        # signal_name = "%s(PyQt_PyObject)" % key.replace("/", "_")
        # signal_name = "%s" % key.replace("/", "_")
    #    print(qobject, key)
        # callback = getattr(self, signal_name, None)
        # if hasattr(callback, "__call__"):
        #    callback()
        # print(signal_name, value)
        # self.emit(SIGNAL(signal_name), value)
        # print("Falta emitir {}".format(signal_name))

    def save_settings(self):
        """
        Save the settings before the application is closed with QSettings.

        Info saved: files and projects opened,
        windows state(size and position).
        """

        data_settings = IDE.data_settings()
        ninja_settings = IDE.ninja_settings()
        # Remove swap files
        # for editable in self.__neditables.values():
        #     if editable.swap_file is not None:
        #         # A new file does not have a swap file
        #         editable.swap_file._file_closed()

        if data_settings.value("ide/loadFiles", True):
            # Get opened projects
            projects_obj = self.filesystem.get_projects()
            projects = [projects_obj[project].path for project in projects_obj]
            data_settings.setValue("lastSession/projects", projects)
            # Opened files
            files_info = []
            if self.opened_files:
                for nfile in self.opened_files:
                    if nfile.is_new_file:
                        continue
                    editable = self.get_editable(nfile)
                    files_info.append((
                        nfile.file_path, editable.editor.cursor_position))
            data_settings.setValue("lastSession/openedFiles", files_info)

        main_container = self.get_service("main_container")
        neditor = main_container.get_current_editor()
        # Current opened file
        current_file = ''
        if neditor is not None:
            current_file = neditor.file_path
        data_settings.setValue('lastSession/currentFile', current_file)
        # Save toolbar visibility
        # ninja_settings.setValue('window/hide_toolbar',
        #                         not self.toolbar.isVisible())
        # Save window state
        if self.isMaximized():
            ninja_settings.setValue("window/maximized", True)
        else:
            ninja_settings.setValue("window/maximized", False)
            ninja_settings.setValue("window/size", self.size())
            ninja_settings.setValue("window/pos", self.pos())
        # qsettings = IDE.ninja_settings()
        # data_qsettings = IDE.data_settings()
        # main_container = self.get_service("main_container")
        # editor_widget = None
        # if main_container:
        #    editor_widget = main_container.get_current_editor()
        # current_file = ''
        # if editor_widget is not None:
        #    current_file = editor_widget.file_path
        # if qsettings.value('preferences/general/loadFiles', True, type=bool):
        #    openedFiles = self.filesystem.get_files()
        #    projects_obj = self.filesystem.get_projects()
        #    projects = [projects_obj[proj].path for proj in projects_obj]
        #    data_qsettings.setValue('lastSession/projects', projects)
        #    files_info = []
        #    for path in openedFiles:
        #        editable = self.__neditables.get(openedFiles[path])
        #        if editable is not None and editable.is_dirty:
        #            stat_value = 0
        #        else:
        #            stat_value = os.stat(path).st_mtime
        #        files_info.append([path,
        #                          editable.editor.cursor_position,
        #                          stat_value])
        #    data_qsettings.setValue('lastSession/openedFiles', files_info)
        #    if current_file is not None:
        #        data_qsettings.setValue(
        # 'lastSession/currentFile', current_file)
        recent_files = main_container.last_opened_files
        data_settings.setValue("lastSession/recentFiles", recent_files)
        #     "lastSession/recentFiles", list(main_container.last_opened_files))
        #    data_qsettings.setValue('lastSession/recentFiles',
        #                            settings.LAST_OPENED_FILES)
        # qsettings.setValue('preferences/editor/bookmarks',
        #                   settings.BOOKMARKS)
        # qsettings.setValue('preferences/editor/breakpoints',
        #                   settings.BREAKPOINTS)

        # Session
        # if self._session is not None:
        #     val = QMessageBox.question(
        #         self,
        #         translations.TR_SESSION_ACTIVE_IDE_CLOSING_TITLE,
        #         (translations.TR_SESSION_ACTIVE_IDE_CLOSING_BODY %
        #             {'session': self.Session}),
        #         QMessageBox.Yes, QMessageBox.No)
        #     if val == QMessageBox.Yes:
        #         session_manager.SessionsManager.save_session_data(
        #             self.Session, self)
        # qsettings.setValue('preferences/general/toolbarArea',
        # self.toolBarArea(self.toolbar))
        interpreter = self.interpreter.current.exec_path
        data_settings.setValue("ide/interpreter", interpreter)

    def activate_profile(self):
        """Show the Session Manager dialog."""
        # profilesLoader = session_manager.SessionsManager(self)
        # profilesLoader.show()
        # pass
        self._session_manager.show()

    def deactivate_profile(self):
        """Close the Session Session."""
        # self.Session = None
        pass

    def load_window_geometry(self):
        """Load from QSettings the window size of Ninja IDE"""
        qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat)
        if qsettings.value("window/maximized", True, type=bool):
            self.setWindowState(Qt.WindowMaximized)
        else:
            self.resize(qsettings.value("window/size", QSizeF(800, 600)))
            self.move(qsettings.value("window/pos", QPointF(100, 100)))

    def _get_unsaved_files(self):
        """Return an array with the path of the unsaved files."""
        unsaved = []
        files = self.opened_files
        for f in files:
            editable = self.__neditables.get(f)
            if editable is not None and editable.editor.is_modified:
                unsaved.append(f)
        return unsaved

    def _save_unsaved_files(self, files):
        """Save the files from the paths in the array."""
        for f in files:
            editable = self.get_or_create_editable(nfile=f)
            editable.ignore_checkers = True
            editable.save_content()

    def closeEvent(self, event):
        """Saves some global settings before closing."""
        # if self.s_listener:
        #    self.s_listener.close()
        # _unsaved_files = self._get_unsaved_files()
        # if settings.CONFIRM_EXIT and _unsaved_files:
        #     dialog = unsaved_files.UnsavedFilesDialog(_unsaved_files, self)
        #     if dialog.exec_() == QDialog.Rejected:
        #         event.ignore()
        #         return
        # else:
        #     self._save_unsaved_files(_unsaved_files)
        self.save_settings()
        self.goingDown.emit()
        # close python documentation server (if running)
        # main_container.close_python_doc()
        # Shutdown PluginManager
        # self.plugin_manager.shutdown()
        # completion_daemon.shutdown_daemon()
        super(IDE, self).closeEvent(event)

    # def notify_plugin_errors(self):
    #     # TODO: Check if the Plugin Error dialog can be improved
    #     errors = self.plugin_manager.errors
    #     if errors:
    #         plugin_error_dialog = traceback_widget.PluginErrorDialog()
    #         for err_tuple in errors:
    #             plugin_error_dialog.add_traceback(err_tuple[0], err_tuple[1])
    #         # show the dialog
    #         plugin_error_dialog.exec_()

    def show_message(self, message, duration=1800):
        """Show status message."""
        self.notification.set_message(message, duration)
        self.notification.show()

    # def show_plugins_store(self):
    #     """Open the Plugins Manager to install/uninstall plugins."""
    #     store = plugins_store.PluginsStore(self)
    #     main_container = IDE.get_service("main_container")
    #     if main_container:
    #         main_container.show_dialog(store)
    #     else:
    #         store.show()

    # def show_languages(self):
    #     """Open the Language Manager to install/uninstall languages."""
    #     manager = language_manager.LanguagesManagerWidget(self)
    #     manager.show()

    # def show_schemes(self):
    #     """Open the Schemes Manager to install/uninstall schemes."""
    #     manager = schemes_manager.SchemesManagerWidget(self)
    #     manager.show()

    def show_about_qt(self):
        """Show About Qt Dialog."""
        QMessageBox.aboutQt(self, translations.TR_ABOUT_QT)

    def show_about_ninja(self):
        """Show About NINJA-IDE Dialog."""
        about = about_ninja.AboutNinja(self)
        about.show()
Exemplo n.º 37
0
class SingleInstance:
    def __init__(self, application: QtApplication, files_to_open: Optional[List[str]]) -> None:
        self._application = application
        self._files_to_open = files_to_open

        self._single_instance_server = None

    # Starts a client that checks for a single instance server and sends the files that need to opened if the server
    # exists. Returns True if the single instance server is found, otherwise False.
    def startClient(self) -> bool:
        Logger.log("i", "Checking for the presence of an ready running Cura instance.")
        single_instance_socket = QLocalSocket(self._application)
        Logger.log("d", "Full single instance server name: %s", single_instance_socket.fullServerName())
        single_instance_socket.connectToServer("ultimaker-cura")
        single_instance_socket.waitForConnected(msecs = 3000)  # wait for 3 seconds

        if single_instance_socket.state() != QLocalSocket.ConnectedState:
            return False

        # We only send the files that need to be opened.
        if not self._files_to_open:
            Logger.log("i", "No file need to be opened, do nothing.")
            return True

        if single_instance_socket.state() == QLocalSocket.ConnectedState:
            Logger.log("i", "Connection has been made to the single-instance Cura socket.")

            # Protocol is one line of JSON terminated with a carriage return.
            # "command" field is required and holds the name of the command to execute.
            # Other fields depend on the command.

            payload = {"command": "clear-all"}
            single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))

            payload = {"command": "focus"}
            single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))

            for filename in self._files_to_open:
                payload = {"command": "open", "filePath": os.path.abspath(filename)}
                single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))

            payload = {"command": "close-connection"}
            single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))

            single_instance_socket.flush()
            single_instance_socket.waitForDisconnected()
        return True

    def startServer(self) -> None:
        self._single_instance_server = QLocalServer()
        if self._single_instance_server:
            self._single_instance_server.newConnection.connect(self._onClientConnected)
            self._single_instance_server.listen("ultimaker-cura")
        else:
            Logger.log("e", "Single instance server was not created.")

    def _onClientConnected(self) -> None:
        Logger.log("i", "New connection recevied on our single-instance server")
        connection = None #type: Optional[QLocalSocket]
        if self._single_instance_server:
            connection = self._single_instance_server.nextPendingConnection()

        if connection is not None:
            connection.readyRead.connect(lambda c = connection: self.__readCommands(c))

    def __readCommands(self, connection: QLocalSocket) -> None:
        line = connection.readLine()
        while len(line) != 0:    # There is also a .canReadLine()
            try:
                payload = json.loads(str(line, encoding = "ascii").strip())
                command = payload["command"]

                # Command: Remove all models from the build plate.
                if command == "clear-all":
                    self._application.callLater(lambda: self._application.deleteAll())

                # Command: Load a model file
                elif command == "open":
                    self._application.callLater(lambda f = payload["filePath"]: self._application._openFile(f))

                # Command: Activate the window and bring it to the top.
                elif command == "focus":
                    # Operating systems these days prevent windows from moving around by themselves.
                    # 'alert' or flashing the icon in the taskbar is the best thing we do now.
                    main_window = self._application.getMainWindow()
                    if main_window is not None:
                        self._application.callLater(lambda: main_window.alert(0)) # type: ignore # I don't know why MyPy complains here

                # Command: Close the socket connection. We're done.
                elif command == "close-connection":
                    connection.close()

                else:
                    Logger.log("w", "Received an unrecognized command " + str(command))
            except json.decoder.JSONDecodeError as ex:
                Logger.log("w", "Unable to parse JSON command '%s': %s", line, repr(ex))
            line = connection.readLine()
Exemplo n.º 38
0
class _s_IDE(QMainWindow):
###############################################################################
# SIGNALS
#
# goingDown()
###############################################################################
    goingDown = pyqtSignal()
    def __init__(self, start_server=False):
        super(_s_IDE, self).__init__()
        self.setWindowTitle('NINJA-IDE {Ninja-IDE Is Not Just Another IDE}')
        self.setMinimumSize(700, 500)
        #Load the size and the position of the main window
        self.load_window_geometry()
        self.__project_to_open = 0

        #Start server if needed
        self.s_listener = None
        if start_server:
            self.s_listener = QLocalServer()
            self.s_listener.listen("ninja_ide")
            self.s_listener.newConnection.connect(self._process_connection)

        #Profile handler
        self.profile = None
        #Opacity
        self.opacity = settings.MAX_OPACITY

        #Define Actions object before the UI
        self.actions = actions.Actions()
        #StatusBar
        self.status = status_bar.StatusBar(self)
        self.status.hide()
        self.setStatusBar(self.status)
        #Main Widget - Create first than everything else
        self.central = central_widget.CentralWidget(self)
        self.load_ui(self.central)
        self.setCentralWidget(self.central)

        #ToolBar
        self.toolbar = QToolBar(self)
        self.toolbar.setToolTip(_translate("_s_IDE", "Press and Drag to Move"))
        self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self.addToolBar(settings.TOOLBAR_AREA, self.toolbar)
        if settings.HIDE_TOOLBAR:
            self.toolbar.hide()

        #Install Shortcuts after the UI has been initialized
        self.actions.install_shortcuts(self)
        self.mainContainer.currentTabChanged[str].connect(self.actions.update_explorer)

        #Menu
        menubar = self.menuBar()
        file_ = menubar.addMenu(_translate("_s_IDE", "&File"))
        edit = menubar.addMenu(_translate("_s_IDE", "&Edit"))
        view = menubar.addMenu(_translate("_s_IDE", "&View"))
        source = menubar.addMenu(_translate("_s_IDE", "&Source"))
        project = menubar.addMenu(_translate("_s_IDE", "&Project"))
        self.pluginsMenu = menubar.addMenu(_translate("_s_IDE", "&Addins"))
        about = menubar.addMenu(_translate("_s_IDE", "Abou&t"))

        #The order of the icons in the toolbar is defined by this calls
        self._menuFile = menu_file.MenuFile(file_, self.toolbar, self)
        self._menuView = menu_view.MenuView(view, self.toolbar, self)
        self._menuEdit = menu_edit.MenuEdit(edit, self.toolbar)
        self._menuSource = menu_source.MenuSource(source)
        self._menuProject = menu_project.MenuProject(project, self.toolbar)
        self._menuPlugins = menu_plugins.MenuPlugins(self.pluginsMenu)
        self._menuAbout = menu_about.MenuAbout(about)

        self.load_toolbar()

        #Plugin Manager
        services = {
            'editor': plugin_services.MainService(),
            'toolbar': plugin_services.ToolbarService(self.toolbar),
            'menuApp': plugin_services.MenuAppService(self.pluginsMenu),
            'explorer': plugin_services.ExplorerService(),
            'misc': plugin_services.MiscContainerService(self.misc)}
        serviceLocator = plugin_manager.ServiceLocator(services)
        self.plugin_manager = plugin_manager.PluginManager(resources.PLUGINS,
            serviceLocator)
        self.plugin_manager.discover()
        #load all plugins!
        self.plugin_manager.load_all()

        #Tray Icon
        self.trayIcon = updates.TrayIconUpdates(self)
        self.trayIcon.show()

        self._menuFile.openFile[str].connect(self.mainContainer.open_file)
        self.mainContainer.fileSaved[str].connect(self.show_status_message)
        self.mainContainer.recentTabsModified[list].connect(self._menuFile.update_recent_files)#'QStringList'
        self.mainContainer.currentTabChanged[str].connect(self.actions.update_migration_tips)
        self.mainContainer.updateFileMetadata.connect(self.actions.update_migration_tips)
        self.mainContainer.migrationAnalyzed.connect(self.actions.update_migration_tips)

    def _process_connection(self):
        connection = self.s_listener.nextPendingConnection()
        connection.waitForReadyRead()
        data = connection.readAll()
        connection.close()
        if data:
            files, projects = str(data).split(ipc.project_delimiter, 1)
            files = [(x.split(':')[0], int(x.split(':')[1]))
                for x in files.split(ipc.file_delimiter)]
            projects = projects.split(ipc.project_delimiter)
            self.load_session_files_projects(files, [], projects, None)

    def load_toolbar(self):
        self.toolbar.clear()
        toolbar_items = {}
        toolbar_items.update(self._menuFile.toolbar_items)
        toolbar_items.update(self._menuView.toolbar_items)
        toolbar_items.update(self._menuEdit.toolbar_items)
        toolbar_items.update(self._menuSource.toolbar_items)
        toolbar_items.update(self._menuProject.toolbar_items)

        for item in settings.TOOLBAR_ITEMS:
            if item == 'separator':
                self.toolbar.addSeparator()
            else:
                tool_item = toolbar_items.get(item, None)
                if tool_item is not None:
                    self.toolbar.addAction(tool_item)
        #load action added by plugins, This is a special case when reload
        #the toolbar after save the preferences widget
        for toolbar_action in settings.get_toolbar_item_for_plugins():
            self.toolbar.addAction(toolbar_action)

    def load_external_plugins(self, paths):
        for path in paths:
            self.plugin_manager.add_plugin_dir(path)
        #load all plugins!
        self.plugin_manager.discover()
        self.plugin_manager.load_all()

    def show_status_message(self, message):
        self.status.showMessage(message, 2000)

    def load_ui(self, centralWidget):
        #Set Application Font for ToolTips
        QToolTip.setFont(QFont(settings.FONT_FAMILY, 10))
        #Create Main Container to manage Tabs
        self.mainContainer = main_container.MainContainer(self)
        self.mainContainer.currentTabChanged[str].connect(self.change_window_title)
        self.mainContainer.locateFunction[str, str, bool].connect(self.actions.locate_function)
        self.mainContainer.navigateCode[bool, int].connect(self.actions.navigate_code_history)
        self.mainContainer.addBackItemNavigation.connect(self.actions.add_back_item_navigation)
        self.mainContainer.updateFileMetadata.connect(self.actions.update_explorer)
        self.mainContainer.updateLocator[str].connect(self.actions.update_explorer)
        self.mainContainer.openPreferences.connect(self._show_preferences)
        self.mainContainer.dontOpenStartPage.connect(self._dont_show_start_page_again)
        self.mainContainer.currentTabChanged[str].connect(self.status.handle_tab_changed)
        # When close the last tab cleanup
        self.mainContainer.allTabsClosed.connect(self._last_tab_closed)
        # Update symbols
        self.mainContainer.updateLocator[str].connect(self.status.explore_file_code)
        #Create Explorer Panel
        self.explorer = explorer_container.ExplorerContainer(self)
        self.central.splitterCentralRotated.connect(self.explorer.rotate_tab_position)
        self.explorer.updateLocator.connect(self.status.explore_code)
        self.explorer.goToDefinition[int].connect(self.actions.editor_go_to_line)
        self.explorer.projectClosed[str].connect(self.actions.close_files_from_project)
        #Create Misc Bottom Container
        self.misc = misc_container.MiscContainer(self)
        self.mainContainer.findOcurrences[str].connect(self.misc.show_find_occurrences)

        centralWidget.insert_central_container(self.mainContainer)
        centralWidget.insert_lateral_container(self.explorer)
        centralWidget.insert_bottom_container(self.misc)
        if self.explorer.count() == 0:
            centralWidget.change_explorer_visibility(force_hide=True)
        self.mainContainer.cursorPositionChange[int, int].connect(self.central.lateralPanel.update_line_col)
        # TODO: Change current symbol on move
        #self.connect(self.mainContainer,
            #SIGNAL("cursorPositionChange(int, int)"),
            #self.explorer.update_current_symbol)
        self.mainContainer.enabledFollowMode[bool].connect(self.central.enable_follow_mode_scrollbar)

        if settings.SHOW_START_PAGE:
            self.mainContainer.show_start_page()

    def _last_tab_closed(self):
        """
        Called when the last tasb is closed
        """
        self.explorer.cleanup_tabs()

    def _show_preferences(self):
        pref = preferences.PreferencesWidget(self.mainContainer)
        pref.show()

    def _dont_show_start_page_again(self):
        settings.SHOW_START_PAGE = False
        qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat)
        qsettings.beginGroup('preferences')
        qsettings.beginGroup('general')
        qsettings.setValue('showStartPage', settings.SHOW_START_PAGE)
        qsettings.endGroup()
        qsettings.endGroup()
        self.mainContainer.actualTab.close_tab()

    def load_session_files_projects(self, filesTab1, filesTab2, projects,
        current_file, recent_files=None):
        self.__project_to_open = len(projects)
        self.explorer.projectOpened[str].connect(self._set_editors_project_data)
        self.explorer.open_session_projects(projects, notIDEStart=False)
        self.mainContainer.open_files(filesTab1, notIDEStart=False)
        self.mainContainer.open_files(filesTab2, mainTab=False,
            notIDEStart=False)
        if current_file:
            self.mainContainer.open_file(current_file, notStart=False)
        if recent_files is not None:
            self._menuFile.update_recent_files(recent_files)

    def _set_editors_project_data(self):
        self.__project_to_open -= 1
        if self.__project_to_open == 0:
            self.explorer.projectOpened[str].disconnect(self._set_editors_project_data)
            self.mainContainer.update_editor_project()

    def open_file(self, filename):
        if filename:
            self.mainContainer.open_file(filename)

    def open_project(self, project):
        if project:
            self.actions.open_project(project)

    def __get_profile(self):
        return self.profile

    def __set_profile(self, profileName):
        self.profile = profileName
        if self.profile is not None:
            self.setWindowTitle('NINJA-IDE (PROFILE: %s)' % self.profile)
        else:
            self.setWindowTitle(
                'NINJA-IDE {Ninja-IDE Is Not Just Another IDE}')

    Profile = property(__get_profile, __set_profile)

    def change_window_title(self, title):
        if self.profile is None:
            self.setWindowTitle('NINJA-IDE - %s' % title)
        else:
            self.setWindowTitle('NINJA-IDE (PROFILE: %s) - %s' % (
                self.profile, title))
        currentEditor = self.mainContainer.get_actual_editor()
        if currentEditor is not None:
            line = currentEditor.textCursor().blockNumber() + 1
            col = currentEditor.textCursor().columnNumber()
            self.central.lateralPanel.update_line_col(line, col)

    def wheelEvent(self, event):
        if event.modifiers() == Qt.ShiftModifier:
            if event.delta() == 120 and self.opacity < settings.MAX_OPACITY:
                self.opacity += 0.1
            elif event.delta() == -120 and self.opacity > settings.MIN_OPACITY:
                self.opacity -= 0.1
            self.setWindowOpacity(self.opacity)
            event.ignore()
        else:
            QMainWindow.wheelEvent(self, event)

    def save_settings(self):
        """Save the settings before the application is closed with QSettings.

        Info saved: Tabs and projects opened, windows state(size and position).
        """
        qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat)
        editor_widget = self.mainContainer.get_actual_editor()
        current_file = ''
        if editor_widget is not None:
            current_file = editor_widget.ID
        if qsettings.value('preferences/general/loadFiles', True, type=bool):
            openedFiles = self.mainContainer.get_opened_documents()
            projects_obj = self.explorer.get_opened_projects()
            projects = [p.path for p in projects_obj]
            qsettings.setValue('openFiles/projects',
                projects)
            if len(openedFiles) > 0:
                qsettings.setValue('openFiles/mainTab', openedFiles[0])
            if len(openedFiles) == 2:
                qsettings.setValue('openFiles/secondaryTab', openedFiles[1])
            qsettings.setValue('openFiles/currentFile', current_file)
            qsettings.setValue('openFiles/recentFiles',
                self.mainContainer._tabMain.get_recent_files_list())
        qsettings.setValue('preferences/editor/bookmarks', settings.BOOKMARKS)
        qsettings.setValue('preferences/editor/breakpoints',
            settings.BREAKPOINTS)
        qsettings.setValue('preferences/general/toolbarArea',
            self.toolBarArea(self.toolbar))
        #Save if the windows state is maximixed
        if(self.isMaximized()):
            qsettings.setValue("window/maximized", True)
        else:
            qsettings.setValue("window/maximized", False)
            #Save the size and position of the mainwindow
            qsettings.setValue("window/size", self.size())
            qsettings.setValue("window/pos", self.pos())
        #Save the size of de splitters
        qsettings.setValue("window/central/areaSize",
            self.central.get_area_sizes())
        qsettings.setValue("window/central/mainSize",
            self.central.get_main_sizes())
        #Save the toolbar visibility
        if not self.toolbar.isVisible() and self.menuBar().isVisible():
            qsettings.setValue("window/hide_toolbar", True)
        else:
            qsettings.setValue("window/hide_toolbar", False)
        #Save Misc state
        qsettings.setValue("window/show_misc", self.misc.isVisible())
        #Save Profiles
        if self.profile is not None:
            self.actions.save_profile(self.profile)
        else:
            qsettings.setValue('ide/profiles', settings.PROFILES)

    def load_window_geometry(self):
        """Load from QSettings the window size of de Ninja IDE"""
        qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat)
        if qsettings.value("window/maximized", True, type=bool):
            self.setWindowState(Qt.WindowMaximized)
        else:
            self.resize(qsettings.value("window/size",
                QSizeF(800, 600).toSize(), type='QSize'))
            self.move(qsettings.value("window/pos",
                QPointF(100, 100).toPoint(), type='QPoint'))

    def closeEvent(self, event):
        if self.s_listener:
            self.s_listener.close()
        if (settings.CONFIRM_EXIT and
                self.mainContainer.check_for_unsaved_tabs()):
            unsaved_files = self.mainContainer.get_unsaved_files()
            txt = '\n'.join(unsaved_files)
            val = QMessageBox.question(self,
                _translate("_s_IDE", "Some changes were not saved"),
                (_translate("_s_IDE", "%s\n\nDo you want to save them?") % txt),
                QMessageBox.Yes, QMessageBox.No, QMessageBox.Cancel)
            if val == QMessageBox.Yes:
                #Saves all open files
                self.mainContainer.save_all()
            if val == QMessageBox.Cancel:
                event.ignore()
        self.goingDown.emit()
        self.save_settings()
        completion_daemon.shutdown_daemon()
        #close python documentation server (if running)
        self.mainContainer.close_python_doc()
        #Shutdown PluginManager
        self.plugin_manager.shutdown()

    def notify_plugin_errors(self):
        errors = self.plugin_manager.errors
        if errors:
            plugin_error_dialog = traceback_widget.PluginErrorDialog()
            for err_tuple in errors:
                plugin_error_dialog.add_traceback(err_tuple[0], err_tuple[1])
            #show the dialog
            plugin_error_dialog.exec_()

    def show_python_detection(self):
        suggested = settings.detect_python_path()
        if suggested:
            dialog = python_detect_dialog.PythonDetectDialog(suggested, self)
            dialog.show()
Exemplo n.º 39
0
class QtSingleApplication(QApplication):
    """
    Adapted from https://stackoverflow.com/a/12712362/11038610

    Published by Johan Rade under 2-clause BSD license, opensource.org/licenses/BSD-2-Clause
    """
    message_received_event = pyqtSignal(str)

    def __init__(self, id, *argv):

        super().__init__(*argv)
        self._id = id

        # Is there another instance running?
        self._outSocket = QLocalSocket()
        self._outSocket.connectToServer(self._id)
        self._isRunning = self._outSocket.waitForConnected()

        if self._isRunning:
            # Yes, there is.
            self._outStream = QTextStream(self._outSocket)
            self._outStream.setCodec('UTF-8')
        else:
            # No, there isn't.
            self._outSocket = None
            self._outStream = None
            self._inSocket = None
            self._inStream = None
            self._server = QLocalServer()
            self._server.removeServer(self._id)
            self._server.listen(self._id)
            self._server.newConnection.connect(self._onNewConnection)

    def isRunning(self):
        return self._isRunning

    def id(self):
        return self._id

    def sendMessage(self, msg):
        if not self._outStream:
            return False
        self._outStream << msg << '\n'
        self._outStream.flush()
        return self._outSocket.waitForBytesWritten()

    def _onNewConnection(self):
        if self._inSocket:
            self._inSocket.readyRead.disconnect(self._onReadyRead)
        self._inSocket = self._server.nextPendingConnection()
        if not self._inSocket:
            return
        self._inStream = QTextStream(self._inSocket)
        self._inStream.setCodec('UTF-8')
        self._inSocket.readyRead.connect(self._onReadyRead)

    def _onReadyRead(self):
        while True:
            msg = self._inStream.readLine()
            if not msg:
                break
            self.message_received_event.emit(msg)
Exemplo n.º 40
0
class IDE(QMainWindow):
    """This class is like the Sauron's Ring:
    One ring to rule them all, One ring to find them,
    One ring to bring them all and in the darkness bind them.

    This Class knows all the containers, and its know by all the containers,
    but the containers don't need to know between each other, in this way we
    can keep a better api without the need to tie the behaviour between
    the widgets, and let them just consume the 'actions' they need."""

###############################################################################
# SIGNALS
#
# goingDown()
###############################################################################

    __IDESERVICES = {}
    __IDECONNECTIONS = {}
    __IDESHORTCUTS = {}
    __IDEBARCATEGORIES = {}
    __IDEMENUS = {}
    __IDETOOLBAR = {}
    # CONNECTIONS structure:
    # ({'target': service_name, 'signal_name': string, 'slot': function_obj},)
    # On modify add: {connected: True}
    __instance = None
    __created = False


    MessageStatusChanged = pyqtSignal(str)

    goingDown = pyqtSignal()
    # # ns_preferences_editor_font = pyqtSignal()
    # ns_preferences_editor_showTabsAndSpaces = pyqtSignal()
    # ns_preferences_editor_showIndentationGuide = pyqtSignal()
    # ns_preferences_editor_indent = pyqtSignal()
    # ns_preferences_editor_marginLine = pyqtSignal()#podría tener un argumento
    # ns_preferences_editor_showLineNumbers = pyqtSignal()
    # ns_preferences_editor_showMigrationTips = pyqtSignal()
    # ns_preferences_editor_checkStyle = pyqtSignal()
    # ns_preferences_editor_errors = pyqtSignal()
    # ds_lastSession_projects = pyqtSignal()
    # ds_lastSession_openedFiles = pyqtSignal()
    # ds_lastSession_currentFile = pyqtSignal()
    # ds_lastSession_recentFiles = pyqtSignal()
    # ns_preferences_editor_bookmarks = pyqtSignal()
    # ns_preferences_editor_breakpoints = pyqtSignal()
    # ns_window_maximized = pyqtSignal()
    # ns_preferences_general_loadFiles = pyqtSignal()
    # ns_preferences_general_activatePlugins = pyqtSignal()
    # ns_preferences_general_notifyUpdates = pyqtSignal()
    # ns_preferences_general_showStartPage = pyqtSignal(bool)
    # ns_preferences_general_confirmExit = pyqtSignal(bool)
    # ns_preferences_general_workspace = pyqtSignal()
    ns_preferences_general_supportedExtensions = pyqtSignal("QStringList")
    #ns_preferences_general_notification_position = pyqtSignal(int)
    #...
    ns_preferences_general_loadFiles = pyqtSignal(bool)# dato: 'True'

    ns_preferences_general_activatePlugins = pyqtSignal(bool)# dato: 'True'

    ns_preferences_general_notifyUpdates = pyqtSignal(bool)# dato: 'True'

    ns_preferences_general_showStartPage = pyqtSignal(bool)# dato: 'True'

    ns_preferences_general_confirmExit = pyqtSignal(bool)# dato: 'True'

    ns_preferences_general_workspace = pyqtSignal(str)# dato: ''

    #ns_preferences_general_supportedExtensions = pyqtSignal(list)# dato: '['.py', '.pyw', '.html', '.jpg','.png', '.ui', '.css', '.json', '.js', '.ini']'

    ns_preferences_general_notification_position = pyqtSignal(int)# dato: '0'

    ns_preferences_general_notification_color = pyqtSignal(str)# dato: '#000'

    ns_pythonPath = pyqtSignal(str)# dato: 'D:\Python34\python.exe'

    ns_executionOptions = pyqtSignal(str)# dato: ''

    ns_Show_Code_Nav = pyqtSignal(str)# dato: 'Ctrl+3'

    ns_Follow_mode = pyqtSignal(str)# dato: 'Ctrl+F10'

    ns_Change_Tab = pyqtSignal(str)# dato: 'Ctrl+PgDown'

    ns_Change_Tab_Reverse = pyqtSignal(str)# dato: 'Ctrl+PgUp'

    ns_Close_file = pyqtSignal(str)# dato: 'Ctrl+W'

    ns_Close_Split = pyqtSignal(str)# dato: 'Shift+F9'

    ns_Comment = pyqtSignal(str)# dato: 'Ctrl+G'

    ns_Complete_Declarations = pyqtSignal(str)# dato: 'Alt+Return'

    ns_copy = pyqtSignal(str)# dato: 'Ctrl+C'

    ns_History_Copy = pyqtSignal(str)# dato: 'Ctrl+Alt+C'

    ns_New_project = pyqtSignal(str)# dato: 'Ctrl+Shift+N'

    ns_New_file = pyqtSignal(str)# dato: 'Ctrl+N'

    ns_cut = pyqtSignal(str)# dato: 'Ctrl+X'

    ns_Debug = pyqtSignal(str)# dato: 'F7'

    ns_Duplicate = pyqtSignal(str)# dato: 'Ctrl+R'

    ns_Run_file = pyqtSignal(str)# dato: 'Ctrl+F6'

    ns_Run_project = pyqtSignal(str)# dato: 'F6'

    ns_expand_file_combo = pyqtSignal(str)# dato: 'Ctrl+Tab'

    ns_expand_symbol_combo = pyqtSignal(str)# dato: 'Ctrl+2'

    ns_Find = pyqtSignal(str)# dato: 'Ctrl+F'

    ns_Find_replace = pyqtSignal(str)# dato: 'Ctrl+H'

    ns_Find_in_files = pyqtSignal(str)# dato: 'Ctrl+L'

    ns_Find_next = pyqtSignal(str)# dato: 'Ctrl+F3'

    ns_Find_previous = pyqtSignal(str)# dato: 'Shift+F3'

    ns_Find_with_word = pyqtSignal(str)# dato: 'Ctrl+Shift+F'

    ns_Full_screen = pyqtSignal(str)# dato: 'Ctrl+F11'

    ns_Go_to_definition = pyqtSignal(str)# dato: 'Ctrl+Return'

    ns_Hide_all = pyqtSignal(str)# dato: 'F11'

    ns_Hide_editor = pyqtSignal(str)# dato: 'F3'

    ns_Hide_explorer = pyqtSignal(str)# dato: 'F2'

    ns_Hide_misc = pyqtSignal(str)# dato: 'F4'

    ns_Highlight_Word = pyqtSignal(str)# dato: 'Ctrl+Down'

    ns_Import = pyqtSignal(str)# dato: 'Ctrl+I'

    ns_Indent_less = pyqtSignal(str)# dato: 'Shift+Tab'

    ns_Indent_more = pyqtSignal(str)# dato: 'Tab'

    ns_Add_Bookmark_or_Breakpoint = pyqtSignal(str)# dato: 'Ctrl+B'

    ns_Title_comment = pyqtSignal(str)# dato: ''

    ns_Horizontal_line = pyqtSignal(str)# dato: ''

    ns_Move_down = pyqtSignal(str)# dato: 'Alt+Down'

    ns_Move_up = pyqtSignal(str)# dato: 'Alt+Up'

    ns_Move_Tab_to_left = pyqtSignal(str)# dato: 'Ctrl+Shift+9'

    ns_Move_Tab_to_right = pyqtSignal(str)# dato: 'Ctrl+Shift+0'

    ns_Navigate_back = pyqtSignal(str)# dato: 'Alt+Left'

    ns_Navigate_forward = pyqtSignal(str)# dato: 'Alt+Right'

    ns_Open_file = pyqtSignal(str)# dato: 'Ctrl+O'

    ns_Open_project = pyqtSignal(str)# dato: 'Ctrl+Shift+O'

    ns_Open_recent_closed = pyqtSignal(str)# dato: 'Ctrl+Shift+T'

    ns_paste = pyqtSignal(str)# dato: 'Ctrl+V'

    ns_History_Paste = pyqtSignal(str)# dato: 'Ctrl+Alt+V'

    ns_Print_file = pyqtSignal(str)# dato: 'Ctrl+P'

    ns_Redo = pyqtSignal(str)# dato: 'Ctrl+Y'

    ns_Reload_file = pyqtSignal(str)# dato: 'F5'

    ns_Remove_line = pyqtSignal(str)# dato: 'Ctrl+E'

    ns_Save_file = pyqtSignal(str)# dato: 'Ctrl+S'

    ns_Save_project = pyqtSignal(str)# dato: 'Ctrl+Shift+S'

    ns_Code_locator = pyqtSignal(str)# dato: 'Ctrl+K'

    ns_Show_Paste_History = pyqtSignal(str)# dato: 'Ctrl+4'

    ns_File_Opener = pyqtSignal(str)# dato: 'Ctrl+Alt+O'

    ns_Help = pyqtSignal(str)# dato: 'F1'

    ns_Show_Selector = pyqtSignal(str)# dato: 'Ctrl+`'

    ns_Split_assistance = pyqtSignal(str)# dato: 'F10'

    ns_change_tab_visibility = pyqtSignal(str)# dato: 'Shift+F1'

    ns_Split_horizontal = pyqtSignal(str)# dato: 'F9'

    ns_Split_vertical = pyqtSignal(str)# dato: 'Ctrl+F9'

    ns_Stop_execution = pyqtSignal(str)# dato: 'Ctrl+Shift+F6'

    ns_Uncomment = pyqtSignal(str)# dato: 'Ctrl+Shift+G'

    ns_undo = pyqtSignal(str)# dato: 'Ctrl+Z'

    ns_preferences_interface_showProjectExplorer = pyqtSignal(bool)# dato: 'True'

    ns_preferences_interface_showSymbolsList = pyqtSignal(bool)# dato: 'True'

    ns_preferences_interface_showWebInspector = pyqtSignal(bool)# dato: 'False'

    ns_preferences_interface_showErrorsList = pyqtSignal(bool)# dato: 'True'

    ns_preferences_interface_showMigrationList = pyqtSignal(bool)# dato: 'True'

    ns_preferences_interface_language = pyqtSignal(str)# dato: 'English'

    ns_preferences_editor_font = pyqtSignal(QFont)# dato: '<PyQt5.QtGui.QFont object at 0x089D32F0>'

    ns_preferences_editor_minimapMaxOpacity = pyqtSignal(float)# dato: '0.8'

    ns_preferences_editor_minimapMinOpacity = pyqtSignal(float)# dato: '0.1'

    ns_preferences_editor_minimapSizeProportion = pyqtSignal(float)# dato: '0.17'

    ns_preferences_editor_minimapShow = pyqtSignal(bool)# dato: 'False'

    ns_preferences_editor_scheme = pyqtSignal(str)# dato: 'default'

    ns_preferences_editor_useTabs = pyqtSignal(bool)# dato: 'False'

    ns_preferences_editor_marginLine = pyqtSignal(int)# dato: '80'

    ns_preferences_editor_showMarginLine = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_indent = pyqtSignal(int)# dato: '4'

    ns_preferences_editor_platformEndOfLine = pyqtSignal(bool)# dato: 'False'

    ns_preferences_editor_errorsUnderlineBackground = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_errors = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_errorsInLine = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_checkStyle = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_showMigrationTips = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_checkStyleInline = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_centerOnScroll = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_removeTrailingSpaces = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_allowWordWrap = pyqtSignal(bool)# dato: 'False'

    ns_preferences_editor_showTabsAndSpaces = pyqtSignal(bool)# dato: 'False'

    ns_preferences_editor_showIndentationGuide = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_checkForDocstrings = pyqtSignal(bool)# dato: 'False'

    ns_preferences_editor_showLineNumbers = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_parentheses = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_brackets = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_keys = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_simpleQuotes = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_doubleQuotes = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_codeCompletion = pyqtSignal(bool)# dato: 'True'

    ns_preferences_editor_completeDeclarations = pyqtSignal(bool)# dato: 'True'

    ns_preferences_theme_skin = pyqtSignal(str)# dato: 'Default'

    ds_lastSession_projects = pyqtSignal(list)# dato: '[]'

    ds_lastSession_openedFiles = pyqtSignal(list)# dato: '[]'

    ds_lastSession_currentFile = pyqtSignal(str)# dato: ''

    ds_lastSession_recentFiles = pyqtSignal(list)# dato: '[]'

    ns_preferences_editor_bookmarks = pyqtSignal(dict)# dato: '{}'

    ns_preferences_editor_breakpoints = pyqtSignal(dict)# dato: '{}'

    ns_window_maximized = pyqtSignal(bool)# dato: 'True'

    ns_window_central_baseSplitterSize = pyqtSignal(QByteArray)# dato: 'b'\x00\x00\x00\xff\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x03\x84\x00\x00\x00\xc8\x01\xff\xff\xff\xff\x01\x00\x00\x00\x01\x01''

    ns_window_central_insideSplitterSize = pyqtSignal(QByteArray)# dato: 'b'\x00\x00\x00\xff\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x01B\x00\x00\x00\xa8\x01\xff\xff\xff\xff\x01\x00\x00\x00\x02\x01''

    ns_window_central_lateralVisible = pyqtSignal(bool)# dato: 'True'

    ns_window_hide_toolbar = pyqtSignal(bool)# dato: 'False'

    ns_tools_dock_visible = pyqtSignal(bool)# dato: 'True'

    #...
    ds_recentProjects = pyqtSignal(dict)
    ns_window_size = pyqtSignal(QSize)
    ns_window_pos = pyqtSignal(QPoint)

    def __init__(self, start_server=False):
        super(IDE, self).__init__()
        self.setWindowTitle('NINJA-IDE {Ninja-IDE Is Not Just Another IDE}')
        self.setMinimumSize(750, 500)
        QToolTip.setFont(QFont(settings.FONT.family(), 10))
        #Load the size and the position of the main window
        self.load_window_geometry()
        self.__project_to_open = 0

        IDE.__instance = self

        wid = QWidget()#adjustSize
        wid.setContentsMargins(0, 0, 0, 0)
        box = QHBoxLayout(wid)
        box.setContentsMargins(0, 0, 0, 0)
        # l1 = QLabel("Info Here")
        # l1.setObjectName("Info")
        # l1.setStyleSheet("background-color: rgb(88, 255, 85);")
        # box.addWidget(l1)
        space = QSpacerItem(10,10, QSizePolicy.Expanding)#, QSizePolicy.Maximum)
        box.addSpacerItem(space)
        l2 = QLabel("Tab Size: "+str(settings.INDENT))#int(qsettings.value('preferences/editor/indent', 4, type=int))))
        l2.setObjectName("Det1")

        font = l2.font()
        font.setPointSize(8)
        l2.setFont(font)
        box.addWidget(l2)

        box.addSpacing(50)

        l3 = QLabel("Python")
        l3.setObjectName("Det2")
        font.setPointSize(9)
        l3.setFont(font)
        box.addWidget(l3)

        box.addSpacing(30)

        status = self.statusBar()
        status.setMaximumHeight(20)
        status.addPermanentWidget(wid)
        # wid.show()
        # self.__wid = wid
        status.reformat()
        status.showMessage("Info Here")
        status.setStyleSheet("background-color: rgb(85, 85, 85);")

        #Editables
        self.__neditables = {}
        #Filesystem
        self.filesystem = nfilesystem.NVirtualFileSystem()

        #Sessions handler
        self._session = None
        #Opacity
        self.opacity = settings.MAX_OPACITY

        #ToolBar
        self.toolbar = QToolBar(self)
        if settings.IS_MAC_OS:
            self.toolbar.setIconSize(QSize(36, 36))
        else:
            self.toolbar.setIconSize(QSize(24, 24))
        self.toolbar.setToolTip(translations.TR_IDE_TOOLBAR_TOOLTIP)
        self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly)
        # Set toggleViewAction text and tooltip
        self.toggleView = self.toolbar.toggleViewAction()
        self.toggleView.setText(translations.TR_TOOLBAR_VISIBILITY)
        self.toggleView.setToolTip(translations.TR_TOOLBAR_VISIBILITY)
        self.addToolBar(settings.TOOLBAR_AREA, self.toolbar)
        if settings.HIDE_TOOLBAR:
            self.toolbar.hide()
        #Notificator
        self.notification = notification.Notification(self)

        self.statusBar().messageChanged[str].connect(self.MessageStatusChanged.emit)

        #Plugin Manager
        # CHECK ACTIVATE PLUGINS SETTING
        #services = {
            #'editor': plugin_services.MainService(),
            #'toolbar': plugin_services.ToolbarService(self.toolbar),
            ##'menuApp': plugin_services.MenuAppService(self.pluginsMenu),
            #'menuApp': plugin_services.MenuAppService(None),
            #'explorer': plugin_services.ExplorerService(),
            #'misc': plugin_services.MiscContainerService(self.misc)}
        #serviceLocator = plugin_manager.ServiceLocator(services)
        serviceLocator = plugin_manager.ServiceLocator(None)
        self.plugin_manager = plugin_manager.PluginManager(resources.PLUGINS,
                                                           serviceLocator)
        self.plugin_manager.discover()
        #load all plugins!
        self.plugin_manager.load_all()

        #Tray Icon
        self.trayIcon = updates.TrayIconUpdates(self)
        self.trayIcon.closeTrayIcon.connect(self._close_tray_icon)
        self.trayIcon.show()

        key = Qt.Key_1
        for i in range(10):
            if settings.IS_MAC_OS:
                short = ui_tools.TabShortcuts(
                    QKeySequence(Qt.CTRL + Qt.ALT + key), self, i)
            else:
                short = ui_tools.TabShortcuts(
                    QKeySequence(Qt.ALT + key), self, i)
            key += 1
            short.activated.connect(self._change_tab_index)
        short = ui_tools.TabShortcuts(QKeySequence(Qt.ALT + Qt.Key_0), self, 10)
        short.activated.connect(self._change_tab_index)

        # Register menu categories
        IDE.register_bar_category(translations.TR_MENU_FILE, 100)
        IDE.register_bar_category(translations.TR_MENU_EDIT, 110)
        IDE.register_bar_category(translations.TR_MENU_VIEW, 120)
        IDE.register_bar_category(translations.TR_MENU_SOURCE, 130)
        IDE.register_bar_category(translations.TR_MENU_PROJECT, 140)
        IDE.register_bar_category(translations.TR_MENU_EXTENSIONS, 150)
        IDE.register_bar_category(translations.TR_MENU_ABOUT, 160)
        # Register General Menu Items
        ui_tools.install_shortcuts(self, actions.ACTIONS_GENERAL, self)

        self.register_service('ide', self)
        self.register_service('toolbar', self.toolbar)
        self.register_service('filesystem', self.filesystem)
        #Register signals connections
        connections = (
            {'target': 'main_container',
             'signal_name': 'fileSaved',#(QString)
             'slot': self.show_message},
            {'target': 'main_container',
             'signal_name': 'currentEditorChanged',#(QString)
             'slot': self.change_window_title},
            {'target': 'main_container',
             'signal_name': 'openPreferences',#()
             'slot': self.show_preferences},
            {'target': 'main_container',
             'signal_name': 'allTabsClosed',#()
             'slot': self._last_tab_closed},
            {'target': 'explorer_container',
             'signal_name': 'changeWindowTitle',#(QString)
             'slot': self.change_window_title},
            {'target': 'explorer_container',
             'signal_name': 'projectClosed',#(QString)
             'slot': self.close_project},
            )
        self.register_signals('ide', connections)
        # Central Widget MUST always exists
        self.central = IDE.get_service('central_container')
        print("self.central:", self.central)
        self.setCentralWidget(self.central)
        # Install Services
        for service_name in self.__IDESERVICES:
            self.install_service(service_name)
        IDE.__created = True
        # Place Status Bar
        main_container = IDE.get_service('main_container')
        status_bar = IDE.get_service('status_bar')
        main_container.add_status_bar(status_bar)
        # Load Menu Bar
        menu_bar = IDE.get_service('menu_bar')
        if menu_bar:
            menu_bar.load_menu(self)
            #These two are the same service, I think that's ok
            menu_bar.load_toolbar(self)

        #Start server if needed
        self.s_listener = None
        if start_server:
            self.s_listener = QLocalServer()
            self.s_listener.listen("ninja_ide")
            self.s_listener.newConnection.connect(self._process_connection)


    @classmethod
    def hasCreated(clss):
        return clss.__created

    @classmethod
    def getInstance(clss):
        return clss.__instance

    @classmethod
    def get_service(cls, service_name):
        """Return the instance of a registered service."""
        return cls.__IDESERVICES.get(service_name, None)

    def get_menuitems(self):
        """Return a dictionary with the registered menu items."""
        return IDE.__IDEMENUS

    def get_bar_categories(self):
        """Get the registered Categories for the Application menus."""
        return IDE.__IDEBARCATEGORIES

    def get_toolbaritems(self):
        """Return a dictionary with the registered menu items."""
        return IDE.__IDETOOLBAR

    @classmethod
    def register_service(cls, service_name, obj):
        """Register a service providing the service name and the instance."""
        cls.__IDESERVICES[service_name] = obj
        if cls.hasCreated():
            cls.getInstance().install_service(service_name)

    def install_service(self, service_name):
        """Activate the registered service."""
        obj = IDE.__IDESERVICES.get(service_name, None)
        func = getattr(obj, 'install', None)
        if isinstance(func, collections.Callable):
            func()
        self._connect_signals()

    def place_me_on(self, name, obj, region="central", top=False):
        """Place a widget in some of the areas in the IDE.
        @name: id to access to that widget later if needed.
        @obj: the instance of the widget to be placed.
        @region: the area where to put the widget [central, lateral]
        @top: place the widget as the first item in the split."""
        self.central.add_to_region(name, obj, region, top)

    @classmethod
    def register_signals(cls, service_name, connections):
        """Register all the signals that a particular service wants to be
        attached of.
        @service_name: id of the service
        @connections: list of dictionaries for the connection with:
            - 'target': 'the_other_service_name',
            - 'signal_name': 'name of the signal in the other service',
            - 'slot': function object in this service"""
        cls.__IDECONNECTIONS[service_name] = connections
        if cls.hasCreated():
            cls.getInstance()._connect_signals()

    def _connect_signals(self):
        """Connect the signals between the different services."""
        for service_name in IDE.__IDECONNECTIONS:
            connections = IDE.__IDECONNECTIONS[service_name]
            for connection in connections:
                if connection.get('connected', False):
                    continue
                target = IDE.__IDESERVICES.get(
                    connection['target'], None)
                slot = connection['slot']
                signal_name = connection['signal_name']
                if target and isinstance(slot, collections.Callable):
                    getattr(target, signal_name).connect(slot)
                    connection['connected'] = True

    @classmethod
    def register_shortcut(cls, shortcut_name, shortcut, action=None):
        """Register a shortcut and action."""
        cls.__IDESHORTCUTS[shortcut_name] = (shortcut, action)

    @classmethod
    def register_menuitem(cls, menu_action, section, weight):
        """Register a QAction or QMenu in the IDE to be loaded later in the
        menubar using the section(string) to define where is going to be
        contained, and the weight define the order where is going to be
        placed.
        @menu_action: QAction or QMenu
        @section: String (name)
        @weight: int"""
        cls.__IDEMENUS[menu_action] = (section, weight)

    @classmethod
    def register_toolbar(cls, action, section, weight):
        """Register a QAction in the IDE to be loaded later in the
        toolbar using the section(string) to define where is going to be
        contained, and the weight define the order where is going to be
        placed.
        @action: QAction
        @section: String (name)
        @weight: int"""
        cls.__IDETOOLBAR[action] = (section, weight)

    @classmethod
    def register_bar_category(cls, category_name, weight):
        """Register a Menu Category to be created with the proper weight.
        @category_name: string
        @weight: int"""
        cls.__IDEBARCATEGORIES[category_name] = weight

    @classmethod
    def update_shortcut(cls, shortcut_name):
        """Update all the shortcuts of the application."""
        short = resources.get_shortcut
        shortcut, action = cls.__IDESHORTCUTS.get(shortcut_name)
        if shortcut:
            shortcut.setKey(short(shortcut_name))
        if action:
            action.setShortcut(short(shortcut_name))

    def get_or_create_nfile(self, filename):
        """For convenience access to files from ide"""
        return self.filesystem.get_file(nfile_path=filename)

    def get_or_create_editable(self, filename="", nfile=None):
        if nfile is None:
            nfile = self.filesystem.get_file(nfile_path=filename)
        editable = self.__neditables.get(nfile)
        if editable is None:
            editable = neditable.NEditable(nfile)
            editable.fileClosing.connect(self._unload_neditable)
            self.__neditables[nfile] = editable
        return editable

    def _unload_neditable(self, editable):
        self.__neditables.pop(editable.nfile)
        editable.nfile.deleteLater()
        editable.editor.deleteLater()
        editable.deleteLater()

    @property
    def opened_files(self):
        return tuple(self.__neditables.keys())

    def get_project_for_file(self, filename):
        project = None
        if filename:
            project = self.filesystem.get_project_for_file(filename)
        return project

    def create_project(self, path):
        nproj = nproject.NProject(path)
        self.filesystem.open_project(nproj)
        return nproj

    def close_project(self, project_path):
        self.filesystem.close_project(project_path)

    def get_projects(self):
        return self.filesystem.get_projects()

    def get_current_project(self):
        current_project = None
        projects = self.filesystem.get_projects()
        for project in projects:
            if projects[project].is_current:
                current_project = projects[project]
                break
        return current_project

    def showMessageStatus(self, msg):
        QTimer.singleShot(1, Qt.PreciseTimer, lambda: self.statusBar().showMessage(msg))
        # self.statusBar().showMessage(msg)

    @classmethod
    def select_current(cls, widget):
        """Show the widget with a 4px lightblue border line."""
        widget.setProperty("highlight", True)
        widget.style().unpolish(widget)
        widget.style().polish(widget)

    @classmethod
    def unselect_current(cls, widget):
        """Remove the 4px lightblue border line from the widget."""
        widget.setProperty("highlight", False)
        widget.style().unpolish(widget)
        widget.style().polish(widget)

    def _close_tray_icon(self):
        """Close the System Tray Icon."""
        self.trayIcon.hide()
        self.trayIcon.deleteLater()

    def _change_tab_index(self):
        """Change the tabs of the current TabWidget using alt+numbers."""
        widget = QApplication.focusWidget()
        shortcut_index = getattr(widget, 'shortcut_index', None)
        if shortcut_index:
            obj = self.sender()
            shortcut_index(obj.index)

    def _process_connection(self):
        """Read the ipc input from another instance of ninja."""
        connection = self.s_listener.nextPendingConnection()
        connection.waitForReadyRead()
        data = connection.readAll()
        connection.close()
        if data:
            files, projects = str(data).split(ipc.project_delimiter, 1)
            files = [(x.split(':')[0], int(x.split(':')[1]))
                     for x in files.split(ipc.file_delimiter)]
            projects = projects.split(ipc.project_delimiter)
            self.load_session_files_projects(files, [], projects, None)

    def fullscreen_mode(self):
        """Change to fullscreen mode."""
        if self.isFullScreen():
            self.showMaximized()
        else:
            self.showFullScreen()

    def change_toolbar_visibility(self):
        """Switch the toolbar visibility"""
        if self.toolbar.isVisible():
            self.toolbar.hide()
        else:
            self.toolbar.show()

    def load_external_plugins(self, paths):
        """Load external plugins, the ones added to ninja throw the cmd."""
        for path in paths:
            self.plugin_manager.add_plugin_dir(path)
        #load all plugins!
        self.plugin_manager.discover()
        self.plugin_manager.load_all()

    def _last_tab_closed(self):
        """
        Called when the last tasb is closed
        """
        self.explorer.cleanup_tabs()

    def show_preferences(self):
        """Open the Preferences Dialog."""
        pref = preferences.Preferences(self)
        main_container = IDE.get_service("main_container")
        print("\n\npreferences!!")
        if main_container:
            main_container.show_dialog(pref)
            print("\n\nmain_container---")
        else:
            pref.show()
            print("\n\nNONE---")

    def load_session_files_projects(self, files, projects,
                                    current_file, recent_files=None):
        """Load the files and projects from previous session."""
        main_container = IDE.get_service('main_container')
        projects_explorer = IDE.get_service('projects_explorer')
        if main_container and files:
            for fileData in files:
                if file_manager.file_exists(fileData[0]):
                    mtime = os.stat(fileData[0]).st_mtime
                    ignore_checkers = (mtime == fileData[2])
                    line, col = fileData[1][0], fileData[1][1]
                    main_container.open_file(fileData[0], line, col,
                                             ignore_checkers=ignore_checkers)
            #if current_file:
                #main_container.open_file(current_file)
        if projects_explorer and projects:
            projects_explorer.load_session_projects(projects)
            #if recent_files is not None:
                #menu_file = IDE.get_service('menu_file')
                #menu_file.update_recent_files(recent_files)

    #def _set_editors_project_data(self):
        #self.__project_to_open -= 1
        #if self.__project_to_open == 0:
            #self.disconnect(self.explorer, SIGNAL("projectOpened(QString)"),
                #self._set_editors_project_data)
            #self.mainContainer.update_editor_project()

    #def open_file(self, filename):
        #if filename:
            #self.mainContainer.open_file(filename)

    #def open_project(self, project):
        #if project:
            #self.actions.open_project(project)

    def __get_session(self):
        return self._session

    def __set_session(self, sessionName):
        self._session = sessionName
        if self._session is not None:
            self.setWindowTitle(translations.TR_SESSION_IDE_HEADER %
                                {'session': self._session})
        else:
            self.setWindowTitle(
                'NINJA-IDE {Ninja-IDE Is Not Just Another IDE}')

    Session = property(__get_session, __set_session)

    def change_window_title(self, title):
        """Change the title of the Application."""
        if self._session is None:
            self.setWindowTitle('NINJA-IDE - %s' % title)
        else:
            self.setWindowTitle((translations.TR_SESSION_IDE_HEADER %
                                {'session': self._session}) + ' - %s' % title)

    def wheelEvent(self, event):
        """Change the opacity of the application."""
        if event.modifiers() == Qt.ShiftModifier:
            if event.delta() == 120 and self.opacity < settings.MAX_OPACITY:
                self.opacity += 0.1
            elif event.delta() == -120 and self.opacity > settings.MIN_OPACITY:
                self.opacity -= 0.1
            self.setWindowOpacity(self.opacity)
            event.ignore()
        else:
            super(IDE, self).wheelEvent(event)

    @classmethod
    def ninja_settings(cls):
        qsettings = nsettings.NSettings(resources.SETTINGS_PATH,
                                        prefix="ns")
        if cls.hasCreated():
            qsettings.valueChanged.connect(cls.getInstance()._settings_value_changed)
        return qsettings

    @classmethod
    def data_settings(cls):
        qsettings = nsettings.NSettings(resources.DATA_SETTINGS_PATH,
                                        prefix="ds")
        if cls.hasCreated():
            qsettings.valueChanged.connect(cls.getInstance()._settings_value_changed)
        return qsettings

    def _settings_value_changed(self, key, value):
        # signal_name = "%s(PyQt_PyObject)" % key.replace("/", "_")
        # self.emit(SIGNAL(signal_name), value)
        key = key.replace("/", "_").replace("-", "_")
        try:
            getattr(self, key).emit(value)
        except TypeError as reason:
            print("\n:::", key, value, type(value))
            print("\n\nerrors:-:", reason)
            getattr(self, key).emit()
        except AttributeError:
            print("\n:::", key, value, type(value))

        # if not value:
        #     try:
        #         getattr(self, key.replace("/", "_")).emit(value)
        #     except TypeError:
        #         getattr(self, key.replace("/", "_")).emit()

        #     return

        # try:
        #     getattr(self, key.replace("/", "_")).emit(value)
        # except TypeError as e:
        #     print("\n\nerrors", e)
        #     getattr(self, key.replace("/", "_")).emit()
        ##getattr(self, key.replace("/", "_").replace("-", "_")).emit(value)


    def save_settings(self):
        """Save the settings before the application is closed with QSettings.

        Info saved: Tabs and projects opened, windows state(size and position).
        """
        qsettings = IDE.ninja_settings()
        data_qsettings = IDE.data_settings()
        main_container = self.get_service("main_container")
        editor_widget = None
        if main_container:
            editor_widget = main_container.get_current_editor()
        current_file = ''
        if editor_widget is not None:
            current_file = editor_widget.file_path
        if qsettings.value('preferences/general/loadFiles', True, type=bool):
            openedFiles = self.filesystem.get_files()
            projects_obj = self.filesystem.get_projects()
            projects = [projects_obj[proj].path for proj in projects_obj]
            data_qsettings.setValue('lastSession/projects', projects)
            files_info = []
            for path in openedFiles:
                if not openedFiles[path]._exists():
                    print("\n\ncontinue", path)
                    continue
                editable = self.__neditables.get(openedFiles[path])
                if editable is not None and editable.is_dirty:
                    stat_value = 0
                else:
                    stat_value = os.stat(path).st_mtime
                files_info.append([path,
                                  editable.editor.getCursorPosition(),
                                  stat_value])
            data_qsettings.setValue('lastSession/openedFiles', files_info)
            if current_file is not None:
                data_qsettings.setValue('lastSession/currentFile', current_file)
            data_qsettings.setValue('lastSession/recentFiles',
                                    settings.LAST_OPENED_FILES)
        qsettings.setValue('preferences/editor/bookmarks',
                           settings.BOOKMARKS)
        qsettings.setValue('preferences/editor/breakpoints',
                           settings.BREAKPOINTS)

        # Session
        if self._session is not None:
            val = QMessageBox.question(
                self,
                translations.TR_SESSION_ACTIVE_IDE_CLOSING_TITLE,
                (translations.TR_SESSION_ACTIVE_IDE_CLOSING_BODY %
                    {'session': self.Session}),
                QMessageBox.Yes, QMessageBox.No)
            if val == QMessageBox.Yes:
                session_manager.SessionsManager.save_session_data(
                    self.Session, self)
        #qsettings.setValue('preferences/general/toolbarArea',
            #self.toolBarArea(self.toolbar))
        #Save if the windows state is maximixed
        if(self.isMaximized()):
            qsettings.setValue("window/maximized", True)
        else:
            qsettings.setValue("window/maximized", False)
            #Save the size and position of the mainwindow
            qsettings.setValue("window/size", self.size())
            qsettings.setValue("window/pos", self.pos())
        self.central.save_configuration()

        #Save the toolbar visibility
        qsettings.setValue("window/hide_toolbar", not self.toolbar.isVisible())

        #else:
            #qsettings.setValue("window/hide_toolbar", False)
        #Save Misc state
        #qsettings.setValue("window/show_region1", self.misc.isVisible())
        #Save Profiles
        #if self.profile is not None:
            #self.actions.save_profile(self.profile)
        #else:
            #qsettings.setValue('ide/profiles', settings.PROFILES)

    def activate_profile(self):
        """Show the Session Manager dialog."""
        profilesLoader = session_manager.SessionsManager(self)
        profilesLoader.show()

    def deactivate_profile(self):
        """Close the Session Session."""
        self.Session = None

    def load_window_geometry(self):
        """Load from QSettings the window size of Ninja IDE"""
        qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat)
        if qsettings.value("window/maximized", True, type=bool):
            self.setWindowState(Qt.WindowMaximized)
        else:
            self.resize(qsettings.value(
                "window/size",
                QSize(800, 600), type='QSize'))
            self.move(qsettings.value(
                "window/pos",
                QPoint(100, 100), type='QPoint'))

    def _get_unsaved_files(self):
        """Return an array with the path of the unsaved files."""
        unsaved = []
        files = self.opened_files
        for f in files:
            editable = self.__neditables.get(f)
            print("\n\neditable::", editable, getattr(editable, "editor", "-"))
            if editable is not None and  editable.editor is not None and editable.editor.is_modified:
                unsaved.append(f)
        return unsaved

    def _save_unsaved_files(self, files):
        """Save the files from the paths in the array."""
        for f in files:
            editable = self.get_or_create_editable(f)
            editable.ignore_checkers = True
            editable.save_content()

    def closeEvent(self, event):
        """Saves some global settings before closing."""
        if self.s_listener:
            self.s_listener.close()
        main_container = self.get_service("main_container")
        unsaved_files = self._get_unsaved_files()
        if (settings.CONFIRM_EXIT and unsaved_files):
            txt = '\n'.join([nfile.file_name for nfile in unsaved_files])
            val = QMessageBox.question(
                self,
                translations.TR_IDE_CONFIRM_EXIT_TITLE,
                (translations.TR_IDE_CONFIRM_EXIT_BODY % {'files': txt}),
                QMessageBox.Yes | QMessageBox.No, QMessageBox.Cancel)
            if val == QMessageBox.Yes:
                #Saves all open files
                self._save_unsaved_files(unsaved_files)
            if val == QMessageBox.Cancel:
                event.ignore()
                return
        self.save_settings()
        self.goingDown.emit()
        #close python documentation server (if running)
        main_container.close_python_doc()
        #Shutdown PluginManager
        self.plugin_manager.shutdown()
        #completion_daemon.shutdown_daemon()
        super(IDE, self).closeEvent(event)

    def notify_plugin_errors(self):
        #TODO: Check if the Plugin Error dialog can be improved
        errors = self.plugin_manager.errors
        if errors:
            plugin_error_dialog = traceback_widget.PluginErrorDialog()
            for err_tuple in errors:
                plugin_error_dialog.add_traceback(err_tuple[0], err_tuple[1])
            #show the dialog
            plugin_error_dialog.exec_()

    def show_message(self, message, duration=3000):
        """Show status message."""
        self.notification.set_message(message, duration)
        self.notification.show()

    def show_plugins_store(self):
        """Open the Plugins Manager to install/uninstall plugins."""
        store = plugins_store.PluginsStore(self)
        main_container = IDE.get_service("main_container")
        print("\nshow_plugins_store")
        if main_container:
            print("\nshow_plugins_store::main_container")
            main_container.show_dialog(store)
        else:
            store.show()

    def show_languages(self):
        """Open the Language Manager to install/uninstall languages."""
        manager = language_manager.LanguagesManagerWidget(self)
        manager.show()

    def show_schemes(self):
        """Open the Schemes Manager to install/uninstall schemes."""
        manager = schemes_manager.SchemesManagerWidget(self)
        manager.show()

    def show_about_qt(self):
        """Show About Qt Dialog."""
        QMessageBox.aboutQt(self, translations.TR_ABOUT_QT)

    def show_about_ninja(self):
        """Show About NINJA-IDE Dialog."""
        about = about_ninja.AboutNinja(self)
        about.show()

    def show_python_detection(self):
        """Show Python detection dialog for windows."""
        #TODO: Notify the user when no python version could be found
        suggested = settings.detect_python_path()
        if suggested:
            dialog = python_detect_dialog.PythonDetectDialog(suggested, self)
            dialog.show()
Exemplo n.º 41
0
class IPCServer(QObject):

    """IPC server to which clients connect to.

    Attributes:
        ignored: Whether requests are ignored (in exception hook).
        _timer: A timer to handle timeouts.
        _server: A QLocalServer to accept new connections.
        _socket: The QLocalSocket we're currently connected to.
        _socketname: The socketname to use.
        _socketopts_ok: Set if using setSocketOptions is working with this
                        OS/Qt version.
        _atime_timer: Timer to update the atime of the socket regularly.

    Signals:
        got_args: Emitted when there was an IPC connection and arguments were
                  passed.
        got_args: Emitted with the raw data an IPC connection got.
        got_invalid_data: Emitted when there was invalid incoming data.
    """

    got_args = pyqtSignal(list, str, str)
    got_raw = pyqtSignal(bytes)
    got_invalid_data = pyqtSignal()

    def __init__(self, socketname, parent=None):
        """Start the IPC server and listen to commands.

        Args:
            socketname: The socketname to use.
            parent: The parent to be used.
        """
        super().__init__(parent)
        self.ignored = False
        self._socketname = socketname

        self._timer = usertypes.Timer(self, "ipc-timeout")
        self._timer.setInterval(READ_TIMEOUT)
        self._timer.timeout.connect(self.on_timeout)

        if os.name == "nt":  # pragma: no coverage
            self._atime_timer = None
        else:
            self._atime_timer = usertypes.Timer(self, "ipc-atime")
            self._atime_timer.setInterval(ATIME_INTERVAL)
            self._atime_timer.timeout.connect(self.update_atime)
            self._atime_timer.setTimerType(Qt.VeryCoarseTimer)

        self._server = QLocalServer(self)
        self._server.newConnection.connect(self.handle_connection)

        self._socket = None
        self._socketopts_ok = os.name == "nt"
        if self._socketopts_ok:  # pragma: no cover
            # If we use setSocketOptions on Unix with Qt < 5.4, we get a
            # NameError while listening...
            log.ipc.debug("Calling setSocketOptions")
            self._server.setSocketOptions(QLocalServer.UserAccessOption)
        else:  # pragma: no cover
            log.ipc.debug("Not calling setSocketOptions")

    def _remove_server(self):
        """Remove an existing server."""
        ok = QLocalServer.removeServer(self._socketname)
        if not ok:
            raise Error("Error while removing server {}!".format(self._socketname))

    def listen(self):
        """Start listening on self._socketname."""
        log.ipc.debug("Listening as {}".format(self._socketname))
        if self._atime_timer is not None:  # pragma: no branch
            self._atime_timer.start()
        self._remove_server()
        ok = self._server.listen(self._socketname)
        if not ok:
            if self._server.serverError() == QAbstractSocket.AddressInUseError:
                raise AddressInUseError(self._server)
            else:
                raise ListenError(self._server)
        if not self._socketopts_ok:  # pragma: no cover
            # If we use setSocketOptions on Unix with Qt < 5.4, we get a
            # NameError while listening.
            # (see b135569d5c6e68c735ea83f42e4baf51f7972281)
            #
            # Also, we don't get an AddressInUseError with Qt 5.5:
            # https://bugreports.qt.io/browse/QTBUG-48635
            #
            # This means we only use setSocketOption on Windows...
            os.chmod(self._server.fullServerName(), 0o700)

    @pyqtSlot(int)
    def on_error(self, err):
        """Raise SocketError on fatal errors."""
        if self._socket is None:
            # Sometimes this gets called from stale sockets.
            log.ipc.debug("In on_error with None socket!")
            return
        self._timer.stop()
        log.ipc.debug("Socket error {}: {}".format(self._socket.error(), self._socket.errorString()))
        if err != QLocalSocket.PeerClosedError:
            raise SocketError("handling IPC connection", self._socket)

    @pyqtSlot()
    def handle_connection(self):
        """Handle a new connection to the server."""
        if self.ignored:
            return
        if self._socket is not None:
            log.ipc.debug("Got new connection but ignoring it because we're " "still handling another one.")
            return
        socket = self._server.nextPendingConnection()
        if socket is None:
            log.ipc.debug("No new connection to handle.")
            return
        log.ipc.debug("Client connected.")
        self._timer.start()
        self._socket = socket
        socket.readyRead.connect(self.on_ready_read)
        if socket.canReadLine():
            log.ipc.debug("We can read a line immediately.")
            self.on_ready_read()
        socket.error.connect(self.on_error)
        if socket.error() not in (QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError):
            log.ipc.debug("We got an error immediately.")
            self.on_error(socket.error())
        socket.disconnected.connect(self.on_disconnected)
        if socket.state() == QLocalSocket.UnconnectedState:
            log.ipc.debug("Socket was disconnected immediately.")
            self.on_disconnected()

    @pyqtSlot()
    def on_disconnected(self):
        """Clean up socket when the client disconnected."""
        log.ipc.debug("Client disconnected.")
        self._timer.stop()
        if self._socket is None:
            log.ipc.debug("In on_disconnected with None socket!")
        else:
            self._socket.deleteLater()
            self._socket = None
        # Maybe another connection is waiting.
        self.handle_connection()

    def _handle_invalid_data(self):
        """Handle invalid data we got from a QLocalSocket."""
        log.ipc.error("Ignoring invalid IPC data.")
        self.got_invalid_data.emit()
        self._socket.error.connect(self.on_error)
        self._socket.disconnectFromServer()

    @pyqtSlot()
    def on_ready_read(self):
        """Read json data from the client."""
        if self._socket is None:
            # This happens when doing a connection while another one is already
            # active for some reason.
            log.ipc.warning("In on_ready_read with None socket!")
            return
        self._timer.start()
        while self._socket is not None and self._socket.canReadLine():
            data = bytes(self._socket.readLine())
            self.got_raw.emit(data)
            log.ipc.debug("Read from socket: {}".format(data))

            try:
                decoded = data.decode("utf-8")
            except UnicodeDecodeError:
                log.ipc.error("invalid utf-8: {}".format(binascii.hexlify(data)))
                self._handle_invalid_data()
                return

            log.ipc.debug("Processing: {}".format(decoded))
            try:
                json_data = json.loads(decoded)
            except ValueError:
                log.ipc.error("invalid json: {}".format(decoded.strip()))
                self._handle_invalid_data()
                return

            for name in ("args", "target_arg"):
                if name not in json_data:
                    log.ipc.error("Missing {}: {}".format(name, decoded.strip()))
                    self._handle_invalid_data()
                    return

            try:
                protocol_version = int(json_data["protocol_version"])
            except (KeyError, ValueError):
                log.ipc.error("invalid version: {}".format(decoded.strip()))
                self._handle_invalid_data()
                return

            if protocol_version != PROTOCOL_VERSION:
                log.ipc.error("incompatible version: expected {}, " "got {}".format(PROTOCOL_VERSION, protocol_version))
                self._handle_invalid_data()
                return

            cwd = json_data.get("cwd", None)
            self.got_args.emit(json_data["args"], json_data["target_arg"], cwd)

    @pyqtSlot()
    def on_timeout(self):
        """Cancel the current connection if it was idle for too long."""
        log.ipc.error("IPC connection timed out.")
        self._socket.disconnectFromServer()
        if self._socket is not None:  # pragma: no cover
            # on_socket_disconnected sets it to None
            self._socket.waitForDisconnected(CONNECT_TIMEOUT)
        if self._socket is not None:  # pragma: no cover
            # on_socket_disconnected sets it to None
            self._socket.abort()

    @pyqtSlot()
    def update_atime(self):
        """Update the atime of the socket file all few hours.

        From the XDG basedir spec:

        To ensure that your files are not removed, they should have their
        access time timestamp modified at least once every 6 hours of monotonic
        time or the 'sticky' bit should be set on the file.
        """
        path = self._server.fullServerName()
        if not path:
            log.ipc.error("In update_atime with no server path!")
            return
        log.ipc.debug("Touching {}".format(path))
        os.utime(path)

    def shutdown(self):
        """Shut down the IPC server cleanly."""
        log.ipc.debug("Shutting down IPC")
        if self._socket is not None:
            self._socket.deleteLater()
            self._socket = None
        self._timer.stop()
        if self._atime_timer is not None:  # pragma: no branch
            self._atime_timer.stop()
            try:
                self._atime_timer.timeout.disconnect(self.update_atime)
            except TypeError:
                pass
        self._server.close()
        self._server.deleteLater()
        self._remove_server()
Exemplo n.º 42
0
class QSingleApplication(QApplication):

    messageReceived = pyqtSignal(str)

    def __init__(self, *args, **kwargs):
        super(QSingleApplication, self).__init__(*args, **kwargs)
        appid = QApplication.applicationFilePath().lower().split("/")[-1]
        self._socketName = "qtsingleapp-" + appid
        print("socketName", self._socketName)
        self._activationWindow = None
        self._activateOnMessage = False
        self._socketServer = None
        self._socketIn = None
        self._socketOut = None
        self._running = False

        # 先尝试连接
        self._socketOut = QLocalSocket(self)
        self._socketOut.connectToServer(self._socketName)
        self._socketOut.error.connect(self.handleError)
        self._running = self._socketOut.waitForConnected()

        if not self._running:  # 程序未运行
            self._socketOut.close()
            del self._socketOut
            self._socketServer = QLocalServer(self)
            self._socketServer.listen(self._socketName)
            self._socketServer.newConnection.connect(self._onNewConnection)
            self.aboutToQuit.connect(self.removeServer)

    def handleError(self, message):
        print("handleError message: ", message)

    def isRunning(self):
        return self._running

    def activationWindow(self):
        return self._activationWindow

    def setActivationWindow(self, activationWindow, activateOnMessage=True):
        self._activationWindow = activationWindow
        self._activateOnMessage = activateOnMessage

    def activateWindow(self):
        if not self._activationWindow:
            return
        self._activationWindow.setWindowState(
            self._activationWindow.windowState() & ~Qt.WindowMinimized)
        self._activationWindow.raise_()
        self._activationWindow.activateWindow()

    def sendMessage(self, message, msecs=5000):
        if not self._socketOut:
            return False
        if not isinstance(message, bytes):
            message = str(message).encode()
        self._socketOut.write(message)
        if not self._socketOut.waitForBytesWritten(msecs):
            raise RuntimeError("Bytes not written within %ss" %
                               (msecs / 1000.))
        return True

    def _onNewConnection(self):
        if self._socketIn:
            self._socketIn.readyRead.disconnect(self._onReadyRead)
        self._socketIn = self._socketServer.nextPendingConnection()
        if not self._socketIn:
            return
        self._socketIn.readyRead.connect(self._onReadyRead)
        if self._activateOnMessage:
            self.activateWindow()

    def _onReadyRead(self):
        while 1:
            message = self._socketIn.readLine()
            if not message:
                break
            print("Message received: ", message)
            self.messageReceived.emit(message.data().decode())

    def removeServer(self):
        self._socketServer.close()
        self._socketServer.removeServer(self._socketName)
Exemplo n.º 43
0
class IPCServer(QObject):

    """IPC server to which clients connect to.

    Attributes:
        ignored: Whether requests are ignored (in exception hook).
        _timer: A timer to handle timeouts.
        _server: A QLocalServer to accept new connections.
        _socket: The QLocalSocket we're currently connected to.
    """

    def __init__(self, parent=None):
        """Start the IPC server and listen to commands."""
        super().__init__(parent)
        self.ignored = False
        self._remove_server()
        self._timer = usertypes.Timer(self, 'ipc-timeout')
        self._timer.setInterval(READ_TIMEOUT)
        self._timer.timeout.connect(self.on_timeout)
        self._server = QLocalServer(self)
        ok = self._server.listen(SOCKETNAME)
        if not ok:
            raise IPCError("Error while listening to IPC server: {} "
                           "(error {})".format(self._server.errorString(),
                                               self._server.serverError()))
        self._server.newConnection.connect(self.handle_connection)
        self._socket = None

    def _remove_server(self):
        """Remove an existing server."""
        ok = QLocalServer.removeServer(SOCKETNAME)
        if not ok:
            raise IPCError("Error while removing server {}!".format(
                SOCKETNAME))

    @pyqtSlot(int)
    def on_error(self, error):
        """Convenience method which calls _socket_error on an error."""
        self._timer.stop()
        log.ipc.debug("Socket error {}: {}".format(
            self._socket.error(), self._socket.errorString()))
        if error != QLocalSocket.PeerClosedError:
            _socket_error("handling IPC connection", self._socket)

    @pyqtSlot()
    def handle_connection(self):
        """Handle a new connection to the server."""
        if self.ignored:
            return
        if self._socket is not None:
            log.ipc.debug("Got new connection but ignoring it because we're "
                          "still handling another one.")
            return
        socket = self._server.nextPendingConnection()
        if socket is None:
            log.ipc.debug("No new connection to handle.")
            return
        log.ipc.debug("Client connected.")
        self._timer.start()
        self._socket = socket
        socket.readyRead.connect(self.on_ready_read)
        if socket.canReadLine():
            log.ipc.debug("We can read a line immediately.")
            self.on_ready_read()
        socket.error.connect(self.on_error)
        if socket.error() not in (QLocalSocket.UnknownSocketError,
                                  QLocalSocket.PeerClosedError):
            log.ipc.debug("We got an error immediately.")
            self.on_error(socket.error())
        socket.disconnected.connect(self.on_disconnected)
        if socket.state() == QLocalSocket.UnconnectedState:
            log.ipc.debug("Socket was disconnected immediately.")
            self.on_disconnected()

    @pyqtSlot()
    def on_disconnected(self):
        """Clean up socket when the client disconnected."""
        log.ipc.debug("Client disconnected.")
        self._timer.stop()
        self._socket.deleteLater()
        self._socket = None
        # Maybe another connection is waiting.
        self.handle_connection()

    @pyqtSlot()
    def on_ready_read(self):
        """Read json data from the client."""
        if self._socket is None:
            # this happened once and I don't know why
            log.ipc.warn("In on_ready_read with None socket!")
            return
        self._timer.start()
        while self._socket is not None and self._socket.canReadLine():
            data = bytes(self._socket.readLine())
            log.ipc.debug("Read from socket: {}".format(data))
            try:
                decoded = data.decode('utf-8')
            except UnicodeDecodeError:
                log.ipc.error("Ignoring invalid IPC data.")
                log.ipc.debug("invalid data: {}".format(
                    binascii.hexlify(data)))
                return
            log.ipc.debug("Processing: {}".format(decoded))
            try:
                json_data = json.loads(decoded)
            except ValueError:
                log.ipc.error("Ignoring invalid IPC data.")
                log.ipc.debug("invalid json: {}".format(decoded.strip()))
                return
            try:
                args = json_data['args']
            except KeyError:
                log.ipc.error("Ignoring invalid IPC data.")
                log.ipc.debug("no args: {}".format(decoded.strip()))
                return
            cwd = json_data.get('cwd', None)
            app = objreg.get('app')
            app.process_pos_args(args, via_ipc=True, cwd=cwd)

    @pyqtSlot()
    def on_timeout(self):
        """Cancel the current connection if it was idle for too long."""
        log.ipc.error("IPC connection timed out.")
        self._socket.close()

    def shutdown(self):
        """Shut down the IPC server cleanly."""
        if self._socket is not None:
            self._socket.deleteLater()
            self._socket = None
        self._timer.stop()
        self._server.close()
        self._server.deleteLater()
        self._remove_server()
Exemplo n.º 44
0
class Eddy(QApplication):
    """
    This class implements the main Qt application.
    """
    messageReceived = pyqtSignal(str)

    def __init__(self, argv):
        """
        Initialize Eddy.
        :type argv: list
        """
        super().__init__(argv)

        parser = ArgumentParser()
        parser.add_argument('--nosplash', dest='nosplash', action='store_true')
        parser.add_argument('--tests', dest='tests', action='store_true')

        options, args = parser.parse_known_args(args=argv)

        self.inSocket = None
        self.inStream = None
        self.outSocket = QLocalSocket()
        self.outSocket.connectToServer(APPID)
        self.outStream = None
        self.isRunning = self.outSocket.waitForConnected()
        self.mainwindow = None
        self.pendingOpen = []
        self.server = None

        # We do not initialize a new instance of Eddy if there is a process running
        # and we are not executing the tests suite: we'll create a socket instead so we can
        # exchange messages between the 2 processes (this one and the already running one).
        if self.isRunning and not options.tests:
            self.outStream = QTextStream(self.outSocket)
            self.outStream.setCodec('UTF-8')
        else:
            self.server = QLocalServer()
            self.server.listen(APPID)
            self.outSocket = None
            self.outStream = None

            connect(self.server.newConnection, self.newConnection)
            connect(self.messageReceived, self.readMessage)

            ############################################################################################################
            #                                                                                                          #
            #   PERFORM EDDY INITIALIZATION                                                                            #
            #                                                                                                          #
            ############################################################################################################

            # Draw the splashscreen.
            self.splashscreen = None
            if not options.nosplash:
                self.splashscreen = SplashScreen(min_splash_time=4)
                self.splashscreen.show()

            # Setup layout.
            self.setStyle(Clean('Fusion'))
            with open(expandPath('@eddy/ui/clean.qss')) as sheet:
                self.setStyleSheet(sheet.read())

            # Create the main window.
            self.mainwindow = MainWindow()

            # Close the splashscreen.
            if self.splashscreen:
                self.splashscreen.wait(self.splashscreen.remaining)
                self.splashscreen.close()

            # Display the mainwindow.
            self.mainwindow.show()

            if Platform.identify() is Platform.Darwin:
                # On MacOS files being opened are handled as a QFileOpenEvent but since we don't
                # have a Main Window initialized we store them locally and we open them here.
                for filepath in self.pendingOpen:
                    self.openFile(filepath)
                self.pendingOpen = []
            else:
                # Perform document opening if files have been added to sys.argv. This is not
                # executed on Mac OS since this is already handled as a QFileOpenEvent instance.
                for filepath in argv:
                    self.openFile(filepath)

    ####################################################################################################################
    #                                                                                                                  #
    #   EVENTS                                                                                                         #
    #                                                                                                                  #
    ####################################################################################################################

    def event(self, event):
        """
        Executed when an event is received.
        :type event: T <= QEvent | QFileOpenEvent
        """
        if event.type() == QEvent.FileOpen:
            self.pendingOpen = [event.file()]
            return True
        return super().event(event)

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def activate(self):
        """
        Activate the application by raising the main window.
        """
        if self.mainwindow:
            self.mainwindow.setWindowState((self.mainwindow.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive)
            self.mainwindow.activateWindow()
            self.mainwindow.raise_()

    def openFile(self, filepath):
        """
        Open the given file in the activation window.
        :type filepath: str
        :rtype: bool
        """
        if self.mainwindow:
            if not isEmpty(filepath) and os.path.isfile(filepath) and filepath.endswith(Filetype.Graphol.extension):
                self.mainwindow.openFile(filepath)
                return True
        return False

    def sendMessage(self, message):
        """
        Send a message to the other alive Eddy's process.
        :type message: str
        :rtype: bool
        """
        if self.outStream:
            self.outStream = self.outStream << message << '\n'
            self.outStream.flush()
            return self.outSocket.waitForBytesWritten()
        return False

    ####################################################################################################################
    #                                                                                                                  #
    #   SLOTS                                                                                                          #
    #                                                                                                                  #
    ####################################################################################################################

    @pyqtSlot()
    def newConnection(self):
        """
        Executed whenever a message is received.
        """
        if self.inSocket:
            # Disconnect previously connected signal slot.
            disconnect(self.inSocket.readyRead, self.readyRead)

        # Create a new socket.
        self.inSocket = self.server.nextPendingConnection()

        if self.inSocket:
            self.inStream = QTextStream(self.inSocket)
            self.inStream.setCodec('UTF-8')
            connect(self.inSocket.readyRead, self.readyRead)
            self.activate()

    @pyqtSlot()
    def readyRead(self):
        """
        Executed whenever we need to read a message.
        """
        while True:
            message = self.inStream.readLine()
            if isEmpty(message):
                break
            self.messageReceived.emit(message)

    @pyqtSlot(str)
    def readMessage(self, message):
        """
        Read a received message.
        :type message: str
        """
        for filepath in message.split(' '):
            self.openFile(filepath)
Exemplo n.º 45
0
class BlenderLauncher(QMainWindow, BaseWindow, Ui_MainWindow):
    def __init__(self, app):
        super().__init__()
        self.setupUi(self)

        # Server
        self.server = QLocalServer()
        self.server.listen("blender-launcher-server")
        self.server.newConnection.connect(self.new_connection)

        # Global scope
        self.app = app
        self.favorite = None
        self.status = "None"
        self.app_state = AppState.IDLE
        self.cashed_builds = []
        self.manager = PoolManager(200)
        self.timer = None

        # Setup window
        self.setWindowTitle("Blender Launcher")
        self.app.setWindowIcon(QIcon(":resources/icons/tray.ico"))

        # Setup font
        QFontDatabase.addApplicationFont(
            ":/resources/fonts/OpenSans-SemiBold.ttf")
        self.font = QFont("Open Sans SemiBold", 10)
        self.font.setHintingPreference(QFont.PreferNoHinting)
        self.app.setFont(self.font)

        # Setup style
        file = QFile(":/resources/styles/global.qss")
        file.open(QFile.ReadOnly | QFile.Text)
        self.style_sheet = QTextStream(file).readAll()
        self.app.setStyleSheet(self.style_sheet)

        # Check library folder
        if is_library_folder_valid() is False:
            self.dlg = DialogWindow(
                self, title="Information",
                text="First, choose where Blender\nbuilds will be stored",
                accept_text="Continue", cancel_text=None, icon=DialogIcon.INFO)
            self.dlg.accepted.connect(self.set_library_folder)
        else:
            self.draw()

    def set_library_folder(self):
        library_folder = Path.cwd().as_posix()
        new_library_folder = QFileDialog.getExistingDirectory(
            self, "Select Library Folder", library_folder)

        if new_library_folder:
            set_library_folder(new_library_folder)
            self.draw()

    def draw(self):
        self.HeaderLayout = QHBoxLayout()
        self.HeaderLayout.setContentsMargins(1, 1, 1, 0)
        self.HeaderLayout.setSpacing(0)
        self.CentralLayout.addLayout(self.HeaderLayout)

        self.SettingsButton = \
            QPushButton(QIcon(":resources/icons/settings.svg"), "")
        self.SettingsButton.setIconSize(QSize(20, 20))
        self.SettingsButton.setFixedSize(36, 32)
        self.WikiButton = \
            QPushButton(QIcon(":resources/icons/wiki.svg"), "")
        self.WikiButton.setIconSize(QSize(20, 20))
        self.WikiButton.setFixedSize(36, 32)
        self.MinimizeButton = \
            QPushButton(QIcon(":resources/icons/minimize.svg"), "")
        self.MinimizeButton.setIconSize(QSize(20, 20))
        self.MinimizeButton.setFixedSize(36, 32)
        self.CloseButton = \
            QPushButton(QIcon(":resources/icons/close.svg"), "")
        self.CloseButton.setIconSize(QSize(20, 20))
        self.CloseButton.setFixedSize(36, 32)
        self.HeaderLabel = QLabel("Blender Launcher")
        self.HeaderLabel.setAlignment(Qt.AlignCenter)

        self.HeaderLayout.addWidget(self.SettingsButton, 0, Qt.AlignLeft)
        self.HeaderLayout.addWidget(self.WikiButton, 0, Qt.AlignLeft)
        self.HeaderLayout.addWidget(self.HeaderLabel, 1)
        self.HeaderLayout.addWidget(self.MinimizeButton, 0, Qt.AlignRight)
        self.HeaderLayout.addWidget(self.CloseButton, 0, Qt.AlignRight)

        self.SettingsButton.setProperty("HeaderButton", True)
        self.WikiButton.setProperty("HeaderButton", True)
        self.MinimizeButton.setProperty("HeaderButton", True)
        self.CloseButton.setProperty("HeaderButton", True)
        self.CloseButton.setProperty("CloseButton", True)

        # Tab layout
        self.TabWidget = QTabWidget()
        self.CentralLayout.addWidget(self.TabWidget)

        self.LibraryTab = QWidget()
        self.LibraryTabLayout = QVBoxLayout()
        self.LibraryTabLayout.setContentsMargins(0, 0, 0, 0)
        self.LibraryTab.setLayout(self.LibraryTabLayout)
        self.TabWidget.addTab(self.LibraryTab, "Library")

        self.DownloadsTab = QWidget()
        self.DownloadsTabLayout = QVBoxLayout()
        self.DownloadsTabLayout.setContentsMargins(0, 0, 0, 0)
        self.DownloadsTab.setLayout(self.DownloadsTabLayout)
        self.TabWidget.addTab(self.DownloadsTab, "Downloads")

        self.LibraryToolBox = BaseToolBoxWidget(self)

        self.LibraryStableListWidget = \
            self.LibraryToolBox.add_list_widget("Stable Releases")
        self.LibraryDailyListWidget = \
            self.LibraryToolBox.add_list_widget("Daily Builds")
        self.LibraryExperimentalListWidget = \
            self.LibraryToolBox.add_list_widget("Experimental Branches")
        self.LibraryCustomListWidget = \
            self.LibraryToolBox.add_list_widget("Custom Builds")
        self.LibraryTab.layout().addWidget(self.LibraryToolBox)

        self.DownloadsToolBox = BaseToolBoxWidget(self)

        self.DownloadsStableListWidget = \
            self.DownloadsToolBox.add_list_widget("Stable Releases")
        self.DownloadsDailyListWidget = \
            self.DownloadsToolBox.add_list_widget("Daily Builds")
        self.DownloadsExperimentalListWidget = \
            self.DownloadsToolBox.add_list_widget("Experimental Branches")
        self.DownloadsTab.layout().addWidget(self.DownloadsToolBox)

        self.LibraryToolBox.setCurrentIndex(get_default_library_page())

        # Connect buttons
        self.SettingsButton.clicked.connect(self.show_settings_window)
        self.WikiButton.clicked.connect(lambda: webbrowser.open(
            "https://github.com/DotBow/Blender-Launcher/wiki"))
        self.MinimizeButton.clicked.connect(self.showMinimized)
        self.CloseButton.clicked.connect(self.close)

        self.StatusBar.setFont(self.font)
        self.statusbarLabel = QLabel()
        self.statusbarVersion = QLabel(self.app.applicationVersion())
        self.StatusBar.addPermanentWidget(self.statusbarLabel, 1)
        self.StatusBar.addPermanentWidget(self.statusbarVersion)

        # Draw library
        self.draw_library()

        # Setup tray icon context Menu
        quit_action = QAction("Quit", self)
        quit_action.triggered.connect(self.quit)
        hide_action = QAction("Hide", self)
        hide_action.triggered.connect(self.hide)
        show_action = QAction("Show", self)
        show_action.triggered.connect(self._show)
        launch_favorite_action = QAction(
            QIcon(":resources/icons/favorite.svg"), "Blender", self)
        launch_favorite_action.triggered.connect(self.launch_favorite)

        tray_menu = QMenu()
        tray_menu.setFont(self.font)
        tray_menu.addAction(launch_favorite_action)
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)

        # Setup tray icon
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QIcon(":resources/icons/tray.ico"))
        self.tray_icon.setToolTip("Blender Launcher")
        self.tray_icon.activated.connect(self.tray_icon_activated)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

        # Forse style update
        self.style().unpolish(self.app)
        self.style().polish(self.app)

        # Show window
        if get_launch_minimized_to_tray() is False:
            self._show()

    def _show(self):
        self.activateWindow()
        self.show()
        self.set_status()

    def launch_favorite(self):
        try:
            self.favorite.launch()
        except Exception:
            self.dlg = DialogWindow(
                self, text="Favorite build not found!",
                accept_text="OK", cancel_text=None)

    def tray_icon_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self._show()
        elif reason == QSystemTrayIcon.MiddleClick:
            self.launch_favorite()

    def quit(self):
        download_widgets = []

        download_widgets.extend(self.DownloadsStableListWidget.items())
        download_widgets.extend(self.DownloadsDailyListWidget.items())
        download_widgets.extend(self.DownloadsExperimentalListWidget.items())

        for widget in download_widgets:
            if widget.state == DownloadState.DOWNLOADING:
                self.dlg = DialogWindow(
                    self, title="Warning", text="Download task in progress!<br>\
                    Are you sure you want to quit?",
                    accept_text="Yes", cancel_text="No",
                    icon=DialogIcon.WARNING)

                self.dlg.accepted.connect(self.quit2)
                return

        self.quit2()

    def quit2(self):
        if self.timer is not None:
            self.timer.cancel()

        self.tray_icon.hide()
        self.app.quit()

    def draw_library(self, clear=False):
        self.set_status("Reading local builds")

        if clear:
            self.timer.cancel()
            self.scraper.quit()
            self.DownloadsStableListWidget.clear()
            self.DownloadsDailyListWidget.clear()
            self.DownloadsExperimentalListWidget.clear()

        self.favorite = None

        self.LibraryStableListWidget.clear()
        self.LibraryDailyListWidget.clear()
        self.LibraryExperimentalListWidget.clear()

        self.library_drawer = LibraryDrawer(self)
        self.library_drawer.build_found.connect(self.draw_to_library)
        self.library_drawer.finished.connect(self.draw_downloads)
        self.library_drawer.start()

    def draw_downloads(self):
        self.app_state = AppState.CHECKINGBUILDS
        self.set_status("Checking for new builds")
        self.scraper = Scraper(self, self.manager)
        self.scraper.links.connect(self.draw_new_builds)
        self.scraper.error.connect(self.connection_error)
        self.scraper.start()

    def connection_error(self):
        set_locale()
        utcnow = strftime(('%H:%M'), localtime())
        self.set_status("Connection Error at " + utcnow)
        self.app_state = AppState.IDLE

        self.timer = threading.Timer(600.0, self.draw_downloads)
        self.timer.start()

    def draw_new_builds(self, builds):
        self.cashed_builds.clear()
        self.cashed_builds.extend(builds)

        library_widgets = []
        download_widgets = []

        library_widgets.extend(self.LibraryStableListWidget.items())
        library_widgets.extend(self.LibraryDailyListWidget.items())
        library_widgets.extend(self.LibraryExperimentalListWidget.items())

        download_widgets.extend(self.DownloadsStableListWidget.items())
        download_widgets.extend(self.DownloadsDailyListWidget.items())
        download_widgets.extend(self.DownloadsExperimentalListWidget.items())

        for widget in download_widgets:
            if widget.build_info in builds:
                builds.remove(widget.build_info)
            elif widget.state != DownloadState.DOWNLOADING:
                widget.destroy()

        for widget in library_widgets:
            if widget.build_info in builds:
                builds.remove(widget.build_info)

        for build_info in builds:
            self.draw_to_downloads(build_info)

        set_locale()
        utcnow = strftime(('%H:%M'), localtime())
        self.set_status("Last check at " + utcnow)
        self.app_state = AppState.IDLE

        self.timer = threading.Timer(600.0, self.draw_downloads)
        self.timer.start()

    def draw_from_cashed(self, build_info):
        if self.app_state == AppState.IDLE:
            if build_info in self.cashed_builds:
                i = self.cashed_builds.index(build_info)
                self.draw_to_downloads(self.cashed_builds[i])

    def draw_to_downloads(self, build_info):
        branch = build_info.branch

        if branch == 'stable':
            list_widget = self.DownloadsStableListWidget
        elif branch == 'daily':
            list_widget = self.DownloadsDailyListWidget
        else:
            list_widget = self.DownloadsExperimentalListWidget

        item = BaseListWidgetItem(build_info.commit_time)
        widget = DownloadWidget(self, list_widget, item, build_info)
        item.setSizeHint(widget.sizeHint())
        list_widget.addItem(item)
        list_widget.setItemWidget(item, widget)

    def draw_to_library(self, path):
        category = Path(path).parent.name

        if category == 'stable':
            list_widget = self.LibraryStableListWidget
        elif category == 'daily':
            list_widget = self.LibraryDailyListWidget
        elif category == 'experimental':
            list_widget = self.LibraryExperimentalListWidget
        elif category == 'custom':
            list_widget = self.LibraryCustomListWidget
        else:
            return

        item = BaseListWidgetItem()
        widget = LibraryWidget(self, item, path, list_widget)
        list_widget.insertItem(0, item)
        list_widget.setItemWidget(item, widget)

    def set_status(self, status=None):
        if status is not None:
            self.status = status

        self.statusbarLabel.setText("Status: {0}".format(self.status))

    def show_settings_window(self):
        self.settings_window = SettingsWindow(self)

    def clear_temp(self):
        temp_folder = Path(get_library_folder()) / ".temp"
        self.remover = Remover(temp_folder)
        self.remover.start()

    def closeEvent(self, event):
        event.ignore()
        self.hide()

    def new_connection(self):
        self._show()
Exemplo n.º 46
0
class SingleApplication(QtWidgets.QApplication):
    '''
    Inheriting from QApplication, executing main App instead.
    Watching whether the app is already running.
    If so, quit befor execution.
    '''
    messageReceived = QtCore.pyqtSignal(str)

    def __init__(self, id, *argv):

        super(SingleApplication, self).__init__(*argv)
        self._id = id
        self._activationWindow = None
        self._activateOnMessage = False

        # Check if another instance is running?
        self._outSocket = QLocalSocket()
        self._outSocket.connectToServer(self._id)
        self._isRunning = self._outSocket.waitForConnected()

        if self._isRunning:
            self._outStream = QtCore.QTextStream(self._outSocket)
            self._outStream.setCodec('UTF-8')
        else:
            self._outSocket = None
            self._outStream = None
            self._inSocket = None
            self._inStream = None
            self._server = QLocalServer()
            self._server.removeServer(self._id)  # if existing after crash-exit
            self._server.listen(self._id)
            self._server.newConnection.connect(self._onNewConnection)

    def isRunning(self):
        return self._isRunning

    def id(self):
        return self._id

    def activationWindow(self):
        return self._activationWindow

    def setActivationWindow(self, activationWindow, activateOnMessage=True):
        self._activationWindow = activationWindow
        self._activateOnMessage = activateOnMessage

    def activateWindow(self):
        if not self._activationWindow:
            return
        self._activationWindow.setWindowState(
            self._activationWindow.windowState() & ~QtCore.Qt.WindowMinimized)
        self._activationWindow.show()
        self._activationWindow.activateWindow()

    def sendMessage(self, msg):
        if not self._outStream:
            return False
        self._outStream << msg << '\n'
        self._outStream.flush()
        return self._outSocket.waitForBytesWritten()

    def _onNewConnection(self):
        if self._inSocket:
            self._inSocket.readyRead.disconnect(self._onReadyRead)
        self._inSocket = self._server.nextPendingConnection()
        if not self._inSocket:
            return
        self._inStream = QtCore.QTextStream(self._inSocket)
        self._inStream.setCodec('UTF-8')
        self._inSocket.readyRead.connect(self._onReadyRead)
        if self._activateOnMessage:
            self.activateWindow()

    def _onReadyRead(self):
        while True:
            msg = self._inStream.readLine()
            if not msg:
                break
            self.messageReceived.emit(msg)