Exemple #1
0
class sliderField(dialogField):
	"""A field made up of an integer slider."""

	def __init__(self, name, value=None, min=0, max=10):
		"""Initializes the field.

		name -- the name of the field
		value -- the initial position of the slider
		min -- the minimum value allowed
		max -- the maximum value allowed

		"""
		super(sliderField, self).__init__(name, value)
		self.min = min
		self.max = max

	def _cleanValue(self, value):
		if value <= self.max and value >= self.min:
			return value
		raise validationError(translate('sliderField', 'You must enter a valid choice for the {0} field.').format(self.name))

	def _createWidget(self, parent):
		widget = QSlider(parent)
		widget.setOrientation(Qt.Horizontal)
		widget.setMinimum(self.min)
		widget.setMaximum(self.max)
		widget.setValue(self.value)
		widget.setTickInterval(1)
		widget.setTickPosition(QSlider.TicksAbove)
		widget.setPageStep(1)
		widget.valueChanged.connect(self.emitEvil)
		return widget

	def emitEvil(self, trash):
		self.evil.emit()

	def _getWidgetValue(self, widget):
		return (widget.value())

	evil = signal(doc=
		"""Ponies!"""
	)
Exemple #2
0
class dropDownField(dialogField):
	"""A field made up of several independent choices."""

	def __init__(self, name, choices, value=None):
		"""Initializes the field.

		name -- the name of the field
		choices -- the list of choices allowed
		value -- the initial value to store

		"""
		super(dropDownField, self).__init__(name, value)
		self.choices = choices

	def _cleanValue(self, value):
		if len(self.choices) <= 0:
			return ''
		if value in self.choices:
			return value
		raise validationError(translate('dropDownField', 'You must enter a valid choice for the {0} field.').format(self.name))

	def _createWidget(self, parent):
		index = -1
		widget = QComboBox(parent)
		for choice in self.choices:
			if choice == self.value:
				index = widget.count()
			widget.addItem(choice)
		if index >= 0:
			widget.setCurrentIndex(index)
		widget.currentIndexChanged.connect(self.emitEvil)
		return widget

	def emitEvil(self, trash):
		self.evil.emit()

	def _getWidgetValue(self, widget):
		return UNICODE_STRING(widget.currentText())

	evil = signal(doc=
		"""Ponies!"""
	)
class chatWidget(QDockWidget):
    def __init__(self, mainWindow):
        super(QDockWidget, self).__init__(mainWindow)
        self.setToolTip(
            self.tr("A widget for out-of-character chat and system messages."))
        self.setWindowTitle(self.tr("OOC Chat / System"))
        self.widgetEditor = QTextBrowser(mainWindow)
        self.widgetLineInput = chatLineEdit(mainWindow)
        self.widgetLineInput.setToolTip(
            self.tr(
                "Type text here and press Enter or Return to transmit it."))
        self.widget = QWidget(mainWindow)
        self.widgetEditor.setReadOnly(True)
        self.widgetEditor.setOpenLinks(False)
        self.layout = QBoxLayout(2)
        self.layout.addWidget(self.widgetEditor)
        self.layout.addWidget(self.widgetLineInput)
        self.widget.setLayout(self.layout)
        self.setWidget(self.widget)
        self.setObjectName("Chat Widget")
        self.timestamp = False
        self.timestampformat = "[%H:%M:%S]"
        self.messageCache = []

        try:
            js = jsonload(ospath.join(SAVE_DIR, "ui_settings.rgs"))
        except:
            pass
        try:
            self.toggleTimestamp(
                loadString('chatWidget.timestamp', js.get('timestamp')))
        except:
            pass
        try:
            self.timestampformat = loadString('chatWidget.timestampformat',
                                              js.get('timestampformat'))
        except:
            pass

        mainWindow.addDockWidget(Qt.BottomDockWidgetArea, self)

        self.widgetEditor.anchorClicked.connect(self.anchorClicked)
        self.widgetLineInput.returnPressed.connect(self.processInput)

    def toggleDarkBackgroundSupport(self, dark):
        if dark:
            self.widgetEditor.document().setDefaultStyleSheet(
                "a {color: cyan; }")
        else:
            self.widgetEditor.document().setDefaultStyleSheet(
                "a {color: blue; }")
        self.refreshMessages()

    def refreshMessages(self):
        '''Clear the text display and re-add all messages with current style settings etc.'''
        self.widgetEditor.clear()
        for message in self.messageCache:
            self.widgetEditor.append(message)

    def anchorClicked(self, url):
        '''If the url appears to be one of the /tell links in a player name, load it to the input.'''
        if "/tell" in UNICODE_STRING(url):
            self.widgetLineInput.setText(url.toString())
        else:
            QDesktopServices.openUrl(QUrl(url))

    def toggleTimestamp(self, newsetting):
        if newsetting == "On":
            self.timestamp = True
        else:
            self.timestamp = False

    def insertMessage(self, mes):
        self.scroll = (self.widgetEditor.verticalScrollBar().value() ==
                       self.widgetEditor.verticalScrollBar().maximum())
        if self.timestamp:
            message = " ".join((strftime(self.timestampformat,
                                         localtime()), mes))
            self.messageCache.append(message)
            self.widgetEditor.append(message)
        else:
            self.messageCache.append(mes)
            self.widgetEditor.append(mes)
        if self.scroll:
            self.widgetEditor.verticalScrollBar().setValue(
                self.widgetEditor.verticalScrollBar().maximum())
        try:
            try:
                self.logfile = open(
                    ospath.join(LOG_DIR, strftime("%b_%d_%Y.log",
                                                  localtime())), 'a')
                self.logfile.write(mes + "\n")
            finally:
                self.logfile.close()
        except:
            pass

    def processTags(self, message):
        message = message.replace("<", "&lt;").replace(">", "&gt;")
        for validTag in ("i", "b", "u", "s"):
            message = message.replace("".join(("[", validTag, "]")), "".join(
                ("<", validTag, ">")))
            message = message.replace("".join(("[", "/", validTag, "]")),
                                      "".join(("<", "/", validTag, ">")))
        message = sub(r"\[url\](.*?)\[/url\]", r"<a href=\1>\1</a>", message)
        message = message.replace(
            "/>",
            ">")  #prevents anchor from closing with trailing slash in URL
        return message

    def processInput(self):
        self.newmes = UNICODE_STRING(self.widgetLineInput.text())
        self.newmes = self.processTags(self.newmes)
        self.widgetLineInput.clear()
        self.widgetLineInput.addMessage(self.newmes)
        self.chatInput.emit(self.newmes)

    chatInput = signal(BASE_STRING,
                       doc="""Called when chat input is received.

		text -- the message entered

		""")
Exemple #4
0
class userListWidget(QDockWidget):
    """The list of connected users."""
    def __init__(self, mainWindow):
        """Initializes the user list."""
        super(QDockWidget, self).__init__(mainWindow)
        self.setToolTip(self.tr("People presently playing."))
        self.setWindowTitle(self.tr("Connected Users"))
        self.widget = QWidget(mainWindow)
        self.listOfUsers = userListList(mainWindow, self)
        self.internalList = []
        self.layout = QGridLayout()
        self.layout.addWidget(self.listOfUsers, 0, 0, 1, 2)
        self.widget.setLayout(self.layout)
        self.widget.setMaximumWidth(
            200)  #Arbitrary; keeps it from taking over 1/3 of the screen
        self.setWidget(self.widget)
        self.setObjectName("User List Widget")
        self.gmname = None
        self.localname = None
        mainWindow.addDockWidget(Qt.BottomDockWidgetArea, self)

    def addUser(self, name, host=False):
        self.internalList.append((name, host))
        nametmp = name
        if host:
            if name == self.localname:
                self.kickbutton = QPushButton(self.tr("Kick"))
                self.kickbutton.setToolTip(
                    self.tr("Disconnect the selected user."))
                self.layout.addWidget(self.kickbutton, 1, 0)
                self.kickbutton.clicked.connect(self.requestKick)
                self.banbutton = QPushButton(self.tr("Manage Banlist"))
                self.banbutton.setToolTip(
                    self.tr("View and edit a list of banned IPs."))
                self.layout.addWidget(self.banbutton, 1, 1)
                self.banbutton.clicked.connect(self.openBanDialog)
            nametmp = "[Host] " + nametmp
        if self.gmname == name:
            nametmp = "[GM] " + nametmp
        self.listOfUsers.addItem(nametmp)

    def removeUser(self, name):
        for i, item in enumerate(self.internalList):
            if item[0] == name:
                self.internalList.pop(i)
                self.listOfUsers.takeItem(i)

    def getUsers(self):
        return self.internalList

    def clearUserList(self):
        self.internalList = []
        self.listOfUsers.clear()

    def refreshDisplay(self):
        self.listOfUsers.clear()
        for item in self.internalList:
            nametmp = item[0]
            if item[1]:
                nametmp = "[Host] " + nametmp
            if self.gmname == item[0]:
                nametmp = "[GM] " + nametmp
            self.listOfUsers.addItem(nametmp)

    def setGM(self, new):
        self.gmname = new
        self.refreshDisplay()

    def provideOptions(self, ID):
        if self.gmname != self.localname:
            return
        name = self.internalList[ID][0]
        #self.setGM(name)
        self.selectGM.emit(name)

    def requestKick(self):
        name = self.internalList[self.listOfUsers.currentRow()][0]
        if name == self.localname:
            return
        self.kickPlayer.emit(name)

    def openBanDialog(self):
        banDialog().exec_()
        self.requestBanlistUpdate.emit()

    selectGM = signal(
        BASE_STRING,
        doc=
        """Called to request a menu be summoned containing actions targeting the selected player.
			Sorry for the misleading legacy name.""")

    kickPlayer = signal(BASE_STRING,
                        doc="""Called to request player kicking.""")

    requestBanlistUpdate = signal(
        doc="""Called to request that the banlist be updated.""")
class statefulSocket(object):
    """A socket wrapper that manages its connection state."""

    _debugcounter = 0

    def __init__(self, name="S-UNK", socket=None, hostname=None, port=None):
        """Initializes the socket, connecting or wrapping a connection."""

        self.clientside = bool(hostname)
        self.state = QAbstractSocket.UnconnectedState
        self.debugid = statefulSocket._debugcounter
        statefulSocket._debugcounter += 1
        self.imbueName(name)
        self.active = False

        self.ready = False

        self.sentfile = None
        self.receivedfile = None

        if self.clientside:
            assert (hostname and port)
            assert (not socket)

            self.socket = QTcpSocket(mainWindow)

        else:
            assert (not hostname and not port)
            assert (socket)
            assert (socket.state() == QAbstractSocket.ConnectedState)

            self.socket = socket
            self.state = QAbstractSocket.ConnectedState

        # Attach signals
        self.socket.error.connect(self._error)
        self.socket.hostFound.connect(self._hostFound)
        self.socket.disconnected.connect(self._disconnected)
        self.socket.stateChanged.connect(self._stateChanged)
        self.socket.readyRead.connect(self._readyRead)
        self.socket.bytesWritten.connect(self._bytesWritten)

        # Connect client
        if self.clientside:
            self.socket.connectToHost(hostname, port)

    def imbueName(self, name):
        """Changes the name of this object."""
        self.context = "{0}-{1}".format(name, self.debugid)

    def activate(self):
        """Activates this socket."""
        assert (not self.ready)
        self.ready = True
        self.active = True
        print("[{0}] Activated!".format(self.context))

    @property
    def busy(self):
        """Whether the socket is busy sending data already."""
        return (not self.ready or self.sentfile is not None
                or self.receivedfile is not None)

    @property
    def closed(self):
        """Whether the socket is already closed."""
        return (self.state == QAbstractSocket.UnconnectedState)

    def close(self):
        """Disconnect all network activity."""
        if not self.closed:
            self.ready = False
            self.state = QAbstractSocket.UnconnectedState
            if self.sentfile:
                self.sentfile.file.close()
                self.sentfile = None
            if self.receivedfile:
                self.receivedfile.file.close()
                self.receivedfile = None
            if self.clientside:
                self.socket.disconnectFromHost()
            else:
                self.socket.close()
            print("[{0}] Closed connection.".format(self.context))

    def _closeWithPrejudice(self):
        """Close the socket, reporting a disconnection message if not already closed."""
        if self.closed:
            return
        oldState = self.state
        self.close()
        self.disconnected.emit(
            self, self.disconnectionError(oldState, self.socket.error()))

    def _respondToSocketError(self, header, result, length=0, text=None):
        """Responds to a read or write error.

		header -- "Read Error" or "Write Error"
		result -- the number of bytes read or negative to indicate error
		length -- the length of the text if text is not provided
		text -- if human-readable, the message text

		"""
        if text:
            length = len(text)
            ERROR_LENGTH = 12
            if len(text) > ERROR_LENGTH:
                text = text[:ERROR_LENGTH]
            text = "'{0}' ".format(text)
        else:
            text = ''

        assert (length > 0)
        assert (length != result)

        if result < 0:
            message = "[{context}] {header}: could not process message {sample}length {length}"
        else:
            message = "[{context}] {header}: partial message {sample}length {partial}/{length}"
        #sample = statefulSocket._sampleText(serial)
        print(
            message.format(context=self.context,
                           header=header,
                           sample=text,
                           length=length,
                           partial=result))

        self._closeWithPrejudice()

    def disconnectionError(self, previousState, err):
        """Find a human-readable error message for when the connection fails."""

        s = QAbstractSocket

        # Messages for what happens when we weren't yet connected
        if self.clientside:
            if previousState != s.ConnectedState:
                # ConnectionRefusedError could mean either refused or timed out
                # SocketTimeoutError is probably not relevant here
                if err == s.ConnectionRefusedError or err == s.SocketTimeoutError:
                    return fake.translate(
                        'socket', 'The connection was refused or timed out.')
                # Couldn't look up the IP or url that the user specified (didn't reach DNS)
                if err == s.HostNotFoundError:
                    return fake.translate(
                        'socket',
                        'The system could not find the specified host address.'
                    )
                # Local firewall or privileges denied access to sockets
                if err == s.SocketAccessError:
                    return fake.translate(
                        'socket',
                        'The program was denied access to network hardware.')
                # Lot of stuff that probably won't apply; SSL, Proxies, etc
                # Mostly they mean there's a cable unplugged or something
                return fake.translate(
                    'socket', 'The program could not find the specified host.')
            # Connected but not ready
            if not self.active:
                return fake.translate('socket',
                                      'The server refused the connection.')

        # Client gracefully quit
        if err == s.RemoteHostClosedError:
            if self.clientside:
                return fake.translate('socket',
                                      'The server closed the connection.')
            return fake.translate('socket',
                                  'The client closed the connection.')
        # Client died without saying anything
        if err == s.SocketTimeoutError:
            return fake.translate('socket', 'The connection timed out.')

        # Something random happened
        if self.clientside:
            return fake.translate('socket', 'You have been disconnected.')
        return fake.translate('socket', 'The client was disconnected.')

    # SENDING

    # Memoization of previously sent value because
    # server data is often sent to multiple destinations
    _memoizeKey = None
    _memoizeData = None

    @staticmethod
    def _serialize(data):
        """Serialize an object to a message that can be sent."""
        serial = jsondumps(data)
        return serial

    def _rawsend(self, serial):
        """Sends serialized data."""
        try:
            result = bytes(serial, "utf-8")
        except TypeError:
            result = serial
        result = self.socket.write(result)
        if result == len(serial):
            # I guess flush forces synchronous sending.
            #self.socket.flush()
            return True

        self._respondToSocketError("Write Error", result, text=serial)
        return False

    def _rawreadline(self):
        """Reads a line from the socket."""
        assert (self.socket.canReadLine())
        data = self.socket.readLine()
        if version_info >= (3, ):
            data = str(data, "UTF-8")
        if len(data) > 0:
            assert (data[-1] == '\n')
            return data

        self._respondToSocketError("Line read Error", -1, length=0)
        return None

    def _rawread(self, length):
        """Reads a line from the socket."""
        if length <= 0:
            return None
        assert (length > 0)
        data = self.socket.read(length)
        if len(data) == length:
            return data

        self._respondToSocketError("Read Error", -1, length=length)
        return None

    def sendObject(self, data):
        """Sends a JSON object over the wire.

		Precondition: self.ready

		"""
        assert (not self.busy or not self.ready)

        if statefulSocket._memoizeKey is data:
            serial = statefulSocket._memoizeData
            # TODO: This check negates the memoization
            # should uncomment for release.
            assert (self._serialize(data) == serial)
        else:
            serial = statefulSocket._serialize(data)
            statefulSocket._memoizeKey = data
            statefulSocket._memoizeData = serial

        self._rawsend(serial + "\n")

    def sendMessage(self, command, **kwargs):
        """Sends a message across the wire."""
        assert (command is not None)
        obj = {
            PARM_COMMAND: command,
            PARM_INTERNAL: True,
        }
        for key, arg in list(kwargs.items()):
            obj[key] = arg
        self.sendObject(obj)

    def sendFile(self, filedata):
        """Begins sending file data directly over the wire.

		filedata -- an open filedata object

		"""
        assert (self.ready)
        assert (not self.busy)

        filedata.seekToBeginning()
        self.sentfile = filedata
        self.updateSend()

    def updateSend(self):
        """Continues sending the current send file."""
        if not self.sentfile:
            return

        while self.socket.bytesToWrite() < CHUNK_SIZE:
            data = self.sentfile.read(self.context)
            if data is None:
                self._closeWithPrejudice()
                return
            if not self._rawsend(data):
                return
            self.filePartlySent.emit(self.sentfile.filename,
                                     UNICODE_STRING(self.sentfile.size),
                                     UNICODE_STRING(self.sentfile.processed))
            if self.sentfile.file.atEnd():
                sentfile = self.sentfile
                self.sentfile = None
                sentfile.file.close()
                self.fileSent.emit(self, sentfile.filename)
                return

    # RECEIVING

    def receiveFile(self, filedata):
        """Writes the next size bytes to the specified file."""

        filedata.seekToBeginning()
        self.receivedfile = filedata
        self.updateReceive()

    def updateReceive(self):
        """Parses incoming data into messages or objects."""

        while True:
            if self.receivedfile is not None:
                length = min(
                    CHUNK_SIZE,
                    self.receivedfile.size - self.receivedfile.processed)
                if self.socket.bytesAvailable() < length:
                    return
                data = self._rawread(length)
                if data is None:
                    return
                if not self.receivedfile.write(self.context, data):
                    self._closeWithPrejudice()
                    return
                self.filePartlyReceived.emit(
                    self.receivedfile.filename,
                    UNICODE_STRING(self.receivedfile.size),
                    UNICODE_STRING(self.receivedfile.processed))
                if self.receivedfile.size == self.receivedfile.processed:
                    receivedfile = self.receivedfile
                    self.receivedfile = None
                    receivedfile.file.close()
                    self.fileReceived.emit(self, receivedfile.filename)
                    # May be more available
                    continue
            else:
                if not self.socket.canReadLine():
                    return
                serial = self._rawreadline()
                if serial is None:
                    return
                # Allow empty lines
                if EMPTY_REGEX.match(UNICODE_STRING(serial)):
                    continue
                try:
                    obj = jsonloads(UNICODE_STRING(serial))
                except:
                    self._respondToSocketError("JSON Error", -1, text=serial)
                    return
                self.receiveObject(obj)

    def receiveObject(self, obj):
        """Look for internal commands or pass directly to object handler."""
        if obj.get(PARM_INTERNAL) == True:
            command = obj.get(PARM_COMMAND)
            if command is not None:
                del obj[PARM_COMMAND]
            del obj[PARM_INTERNAL]
            self.commandReceived.emit(self, command, obj)
        else:
            self.objectReceived.emit(self, obj)

    # SIGNALS

    connected = signal(
        object,
        doc="""Called when the socket becomes ready to start sending;
		when ready becomes True.

		Happens when a remote socket identifies itself as a user or
		a local socket receives a connection notification from the server.

		socket -- this socket

		""")

    disconnected = signal(
        object,
        BASE_STRING,
        doc="""Called when the socket disconnects or fails to connect.

		Not called when disconnected manually (through close()).

		socket -- this socket
		errorMessage -- the untranslated reason the connection failed

		""")

    objectReceived = signal(object,
                            dict,
                            doc="""Called when data is received over the wire.

		socket -- this socket
		data -- the data received

		""")

    commandReceived = signal(object,
                             BASE_STRING,
                             dict,
                             doc="""Called when data is received over the wire.

		socket -- this socket
		command -- the command to carry out
		arguments -- the keyword arguments

		""")

    fileSent = signal(object,
                      BASE_STRING,
                      doc="""Called when a file is done sending.

		socket -- this socket
		name -- the name of the file

		""")

    fileReceived = signal(object,
                          BASE_STRING,
                          doc="""Called when a file is done receiving.

		socket -- this socket
		name -- the name of the file

		""")

    filePartlySent = signal(BASE_STRING,
                            BASE_STRING,
                            BASE_STRING,
                            doc="""Called when a chunk of a file is sent.

		filename -- the filename of the file sent
		size -- the total file size
		processed -- the amount written so far

		""")

    filePartlyReceived = signal(
        BASE_STRING,
        BASE_STRING,
        BASE_STRING,
        doc="""Called when a chunk of a file is received and written.

		filename -- the filename of the file received
		size -- the total file size
		processed -- the amount written so far

		""")

    def _receive(self):
        """Call to make the client receive data."""
        self.updateReceive()

    def _readyRead(self):
        """Called when data is ready."""
        self.updateReceive()

    def _bytesWritten(self, bytes):
        """Occurs when some data is eaten out of the buffer."""
        self.updateSend()

    def _disconnected(self):
        """Called when disconnected from the server."""
        print("[{0}] Disconnected.".format(self.context))
        # TODO: Should we delete the socket here?
        self.socket.deleteLater()

    def _stateChanged(self, newState):
        """Detects connection changes."""
        oldState = self.state
        self.state = newState
        #print "State: {0} {1}".format(oldState, newState)
        if oldState == newState:
            return

        s = QAbstractSocket

        if self.clientside:
            if newState == s.HostLookupState:
                print("[{0}] Looking up host...".format(self.context))
                return
            if newState == s.ConnectingState:
                print("[{0}] Connecting...".format(self.context))
                return
            if newState == s.ConnectedState:
                print("[{0}] Connected. Awaiting activation...".format(
                    self.context))
                self.connected.emit(self)
                return

        if oldState not in (s.HostLookupState, s.ConnectingState,
                            s.ConnectedState):
            return

        if oldState == s.UnconnectedState:
            return
        print("[{context}] Closing error #{id} {message}".format(
            context=self.context,
            id=self.socket.error(),
            message=self.socket.errorString()))
        self.state = s.ConnectedState
        self.close()
        self.disconnected.emit(
            self, self.disconnectionError(oldState, self.socket.error()))

    def _hostFound(self):
        """Responds to host name being resolved. (DNS)"""
        # Apparently the resolved host is still not available
        #print "[{0}] Host found: {1} resolved to {2}:{3}".format(
        #	self.context, self.socket.peerName(), self.socket.peerAddress().toString(), self.socket.peerPort())

    def _error(self, err):
        """Writes errors to the console."""
        print("[{context}] ERROR #{id} {message}".format(
            context=self.context,
            id=self.socket.error(),
            message=self.socket.errorString()))
Exemple #6
0
class BaseClient(object):
    """Base class for local and remote clients."""
    def __init__(self):
        """Initializes the client."""
        # Sockets/server
        self.obj = None
        self.xfer = None
        self.server = None
        self.hostname = None
        self.port = None
        self.password = None

        # File transfer lists
        self.sendList = set()
        self.getList = set()
        self.sentfile = None
        self.receivedfile = None
        # Doesn't need translation
        self.username = UNICODE_STRING(localUser()) or 'localhost'
        assert (self.username)

        self.timer = QTimer()
        self.timer.timeout.connect(self._updatetransfer)
        self.timer.start(1000)

        #self.timer = QTimer()
        #self.timer.timeout.connect(self.transferHack)
        #self.timer.start(3600000)

    @property
    def ready(self):
        return bool(self.obj) and self.obj.ready

    def close(self):
        """Disconnect all network activity."""
        self.sendList = set()
        self.getList = set()
        self.hostname = None
        self.port = None
        if self.obj:
            x = self.obj
            self.obj = None
            x.connected.disconnect()
            x.disconnected.disconnect()
            x.objectReceived.disconnect()
            x.commandReceived.disconnect()
            x.close()
        self._closeXfer()

    def _openXfer(self, socket):
        """Open the transfer socket."""
        assert (self.ready)
        self._closeXfer()
        self.xfer = socket
        socket.connected.connect(self._socketConnected)
        socket.disconnected.connect(self._socketDisconnected)
        socket.objectReceived.connect(self._socketObject)
        socket.commandReceived.connect(self._socketCommand)
        socket.fileSent.connect(self._fileSent)
        socket.fileReceived.connect(self._fileReceived)
        socket.filePartlySent.connect(self._filePartlySent)
        socket.filePartlyReceived.connect(self._filePartlyReceived)

    def _closeXfer(self):
        """Disconnect the transfer socket."""
        if self.xfer:
            if self.sentfile:
                x = self.sentfile
                self.sentfile = None
                x.file.close()
            self.receivedfile = None

            x = self.xfer
            self.xfer = None
            x.connected.disconnect()
            x.disconnected.disconnect()
            x.objectReceived.disconnect()
            x.commandReceived.disconnect()
            x.fileSent.disconnect()
            x.fileReceived.disconnect()
            x.close()

    def _filePartlySent(self, filename, size, processed):
        self.partialTransferEvent.emit(self, filename, size, processed)

    def _filePartlyReceived(self, filename, size, processed):
        self.partialTransferEvent.emit(self, filename, size, processed)

    def send(self, data):
        """Call to send an object over the wire."""
        self.server.receive(self.username, data)

    def receive(self, obj):
        """Make the client receive some data."""
        pass

    def requestFile(self, filename):
        if filename in self.getList:
            # NOTE: could update checksum here (for refreshing the file when updated locally)
            return False
        try:
            file = QFile(makeLocalFilename(filename))
            if not file.open(QFile.ReadWrite):
                try:
                    makedirs(path.dirname(filename))
                except:
                    pass
                if not file.open(QFile.ReadWrite):
                    return False
            try:
                size = file.size()
                digest = generateChecksum(file)
                filedata = fileData(file, filename, size, digest)
            finally:
                file.close()
        except IOError:
            return False
        if stat(filename).st_size == 0:
            remove(filename)
        self.getList.add(filename)
        self.obj.sendMessage(MESSAGE_GET,
                             filename=filedata.filename,
                             size=filedata.size,
                             checksum=filedata.digest)
        message = "[{0}] Requested transfer of {filename} [{size} {checksum}]"
        print(
            message.format(self.obj.context,
                           filename=filedata.filename,
                           size=filedata.size,
                           checksum=filedata.digest))
        self.fileEvent.emit(self, filename, "Requested")
        self._updatetransfer()
        return True

    def _updateSendReceive(self):
        """Determines the order of updates to prioritize server GETs."""
        raise NotImplementedError("Must override.")

    def _updateSend(self):
        """Updates the transfers to send."""

        while self.sendList and not self.sentfile:
            self.fileEvent.emit(
                self, "", "SENDING: " + str(len(self.sendList)) + " QUEUED")
            filedata = next(iter(self.sendList))
            self.sendList.remove(filedata)
            if self._shouldSendFile(filedata):
                self.sentfile = filedata
                self.xfer.sendMessage(MESSAGE_PUT,
                                      filename=filedata.filename,
                                      size=filedata.size,
                                      digest=filedata.digest)
                socket = self.xfer
                message = "[{0}] Offering transfer of {filename} [{size} {checksum}]"
            else:
                self.obj.sendMessage(MESSAGE_IGNORE,
                                     filename=filedata.filename)
                socket = self.obj
                message = "[{0}] Ignored transfer of {filename} [{size} {checksum}]"
            print(
                message.format(socket.context,
                               filename=filedata.filename,
                               size=filedata.size,
                               checksum=filedata.digest))
        if self.sentfile:
            self.fileEvent.emit(
                self, "", "EXITED SEND LOOP DUE TO SENTFILE  (" +
                str(len(self.sendList)) + " FILES REMAIN QUEUED)")

    def _updateReceive(self):
        """Updates the transfers to receive."""

        # Did the server want to send us something?
        if self.receivedfile:
            filename = self.receivedfile.filename
            if self._shouldReceiveFile(self.receivedfile):
                message = "[{0}] Accepted transfer of {filename} [{size} {checksum}]"
                print(
                    message.format(self.xfer.context,
                                   filename=filename,
                                   size=self.receivedfile.size,
                                   checksum=self.receivedfile.digest))
                self.xfer.sendMessage(MESSAGE_ACCEPT, filename=filename)
                self.xfer.receiveFile(self.receivedfile)
            else:
                message = "[{0}] Rejected transfer of {filename} [{size} {checksum}]"
                print(
                    message.format(self.xfer.context,
                                   filename=filename,
                                   size=self.receivedfile.size,
                                   checksum=self.receivedfile.digest))
                self._fileFailed(filename)
                self.xfer.sendMessage(MESSAGE_REJECT, filename=filename)
            self.getList.discard(filename)
            self.receivedfile = None

    def _updatetransfer(self):
        """Opens or updates the transfer socket."""
        if not self.ready:
            self.fileEvent.emit(self, "", "NOT READY")
            return
        if not self.getList and not self.sendList:
            self.fileEvent.emit(self, "", "NO SEND LIST")
            return
        if not self.xfer:
            self.fileEvent.emit(self, "", "NO XFER SOCKET")
            self._openXfer()
            return

        self._updateSendReceive()

    def preemptivelyOpenTransferSocket(self):
        if not self.ready:
            return
        if not self.xfer:
            self._openXfer()
            return

    def transferHack(self):
        '''Kiiiind of crazy...'''
        if self.ready and not self.getList and not self.sendList and not self.xfer.busy:
            self._openXfer()

    def allowSend(client, filename, size, checksum):
        """Replacable hook for determining which files should be sent."""
        return True

    def allowReceipt(client, filename, size, checksum):
        """Replacable hook for determining which files should be accepted."""
        return True

    def _shouldSendFile(self, fileData):
        """Check if we should send a file. Does extra mutating work."""
        filename = fileData.filename
        try:
            # Can we open the file?
            if not fileData.file.open(QFile.ReadOnly):
                self.fileEvent.emit(self, filename, "SENDFILE Could not open")
                print("SENDFILE Could not open")
                return False
            try:
                file = fileData.file
                # Do we already have an identical copy?
                size = fileData.file.size()
                digest = generateChecksum(file)
                if fileData.size is not None and fileData.digest is not None:
                    if size == fileData.size and digest == fileData.digest:
                        self.fileEvent.emit(self, filename,
                                            "SENDFILE Size and digest match")
                        print("SENDFILE Size and digest match")
                        return False

                fileData.size = file.size()
                fileData.digest = digest

                # User hook
                if not self.allowSend(fileData.filename, fileData.size,
                                      fileData.digest):
                    self.fileEvent.emit(self, filename, "SENDFILE User hook")
                    print("SENDFILE User hook")
                    return False

                file = None
                self.fileEvent.emit(self, filename, "SENDFILE Success")
                print("SENDFILE Success")
                return True
            finally:
                if file:
                    file.close()
        except IOError:
            pass
        return False

    def _shouldReceiveFile(self, fileData):
        """Check if we should receive a file. Does extra mutating work."""
        file, filename = fileData.file, fileData.filename

        # Did we ask for the file?
        if not filename in self.getList:
            self.fileEvent.emit(self, filename, "RECVFILE Duplicate")
            print("RECVFILE Duplicate")
            return False
        try:
            # Can we open the file?
            if not file.open(QFile.ReadWrite):
                self.fileEvent.emit(self, filename, "RECVFILE Could not open")
                print("RECVFILE Could not open")
                return False
            try:
                # Do we already have an identical copy?
                if file.size() == fileData.size:
                    if generateChecksum(file) == fileData.digest:
                        self.fileEvent.emit(self, filename,
                                            "RECVFILE Size and digest match")
                        print("RECVFILE Size and digest match")
                        return False

                # User hook
                if not self.allowReceipt(fileData.filename, fileData.size,
                                         fileData.digest):
                    self.fileEvent.emit(self, filename, "RECVFILE User hook")
                    print("RECVFILE User hook")
                    return False

                file = None
                self.fileEvent.emit(self, filename, "RECVFILE Success")
                print("RECVFILE Success")
                return True
            finally:
                if file:
                    file.close()
        except IOError:
            pass
        return False

    # SIGNAL RESPONSES

    def _socketConnected(self, socket):
        """Called when a socket is connected."""
        pass

    def _socketDisconnected(self, socket, errorMessage):
        """Called when a socket disconnects."""
        if socket == self.obj:
            self.close()
        elif socket == self.xfer:
            self._closeXfer()
            self._updatetransfer()

    def _socketObject(self, socket, data):
        """Called when an object is received on a socket."""
        if socket == self.obj:
            self.receive(data)

    def _socketCommand(self, socket, command, kwargs):
        """Responds to socket commands."""
        if socket == self.obj:
            if command not in (MESSAGE_ACTIVATE, MESSAGE_GET, MESSAGE_IGNORE):
                message = "[{0}] Unexpected object command {command}"
                print(message.format(socket.context, command=command))
                return
        elif socket == self.xfer:
            if command not in (MESSAGE_ACTIVATE, MESSAGE_PUT, MESSAGE_ACCEPT,
                               MESSAGE_REJECT):
                message = "[{0}] Unexpected transfer command {command}"
                print(message.format(socket.context, command=command))
                return
        else:
            return
        if ((command == MESSAGE_ACTIVATE) == socket.ready):
            message = "[{0}] Unexpected command {command}"
            print(message.format(socket.context, command=command))
            return
        try:
            kwargs = dict((str(key), val) for key, val in list(kwargs.items()))
            if command == MESSAGE_ACTIVATE:
                self._activateSocket(socket, **kwargs)
            elif command == MESSAGE_GET:
                self._getFile(socket, **kwargs)
            elif command == MESSAGE_PUT:
                self._putFile(socket, **kwargs)
            elif command == MESSAGE_IGNORE:
                self._ignoreFile(socket, **kwargs)
            elif command == MESSAGE_ACCEPT:
                self._acceptFile(socket, **kwargs)
            elif command == MESSAGE_REJECT:
                self._rejectFile(socket, **kwargs)
        except TypeError as e:
            message = "[{0}] Invalid parameters to remote command {command}: {parms}; {err}"
            import traceback
            print(
                message.format(socket.context,
                               command=command,
                               parms=repr(kwargs),
                               err=e))
            traceback.print_exc()

    def _activateSocket(self, socket, username):
        """Activates the socket."""
        pass

    def _getFile(self, socket, filename, size, checksum):
        """Responds to a file request."""
        self.fileEvent.emit(self, filename, "Requested by client")
        self.sendList.add(
            fileData(QFile(makeLocalFilename(filename)), filename, size,
                     checksum))
        self._updatetransfer()

    def _putFile(self, socket, filename, size, digest):
        """Checks whether to accept a sent file."""

        if self.receivedfile is not None:
            message = "[{0}] Remote duplicate PUT; ignoring {filename}"
            print(
                message.format(socket.context,
                               filename=self.receivedfile.filename))

        self.receivedfile = fileData(QFile(makeLocalFilename(filename)),
                                     filename, size, digest)
        self._updatetransfer()

    def _ignoreFile(self, socket, filename):
        """Responds to a file request."""
        message = "[{0}] Remote refused to send {filename}"
        print(message.format(socket.context, filename=filename))
        self.fileEvent.emit(self, filename, "Remote refused to send")
        if filename in self.getList:
            self.getList.remove(filename)
            self._fileFailed(filename)
        self._updatetransfer()

    def _acceptFile(self, socket, filename):
        """Starts sending the specified file."""
        if not self.sentfile or self.sentfile.filename != filename:
            message = "[{0}] Attempt to accept unexpected file {filename}"
            print(message.format(socket.context, filename=filename))
            socket._disconnectWithPrejudice()
            return
        socket.sendFile(self.sentfile)
        self.sentfile = None

    def _rejectFile(self, socket, filename):
        """Cancels sending the specified file."""
        if not self.sentfile or self.sentfile.filename != filename:
            message = "[{0}] Attempt to reject unexpected file {filename}"
            print(message.format(socket.context, filename=filename))
            return
        self.sendList.remove(filename)
        self.sentfile.file.close()
        self.sentfile = None

    def _fileSent(self, socket, filename):
        """Look for more stuff to send."""
        self._updatetransfer()

    def _fileReceived(self, socket, filename):
        """Look more stuff to send."""
        self._updatetransfer()

    def _fileFailed(self, filename):
        """Notify that the socket did not send the specified file."""
        pass

    fileEvent = signal(object,
                       BASE_STRING,
                       BASE_STRING,
                       doc="""Called when something happens relating to a file.

		client -- this client
		filename -- the filename of the file received
		event -- a description of the event

		""")

    partialTransferEvent = signal(object,
                                  BASE_STRING,
                                  BASE_STRING,
                                  BASE_STRING,
                                  doc="""Called when part of a transfer occurs.

		client -- this client
		filename -- the filename of the file involved
		size -- total size of the file
		processed -- amount of the file transferred so far

		""")
Exemple #7
0
class JsonServer(object):
    """A server that processes JSON messages."""
    def __init__(self, client):
        self.clients = {}
        self._addClient(client)
        self.unknown = set()
        self.client = client
        client.server = self
        self.tcp = None
        # Banlist can be manipulated manually;
        # just add ips
        self.banlist = set()
        self.password = None

    @property
    def isConnected(self):
        return bool(self.tcp)

    def _listen(self, port):
        """Starts the server. Returns True on success, else False."""
        if self.isConnected:
            raise RuntimeError("Already connected.")

        self.clients = {}
        self._addClient(self.client)

        tcp = QTcpServer(mainWindow)
        tcp.newConnection.connect(self._newConnection)

        result = tcp.listen(QHostAddress("0.0.0.0"), port)
        if result:
            print("[SERVER] Listening on {0}:{1}".format(
                tcp.serverAddress().toString(), tcp.serverPort()))
            self.tcp = tcp
        else:
            print("[SERVER] Error on listen attempt; {0}".format(
                tcp.errorString()))
            # TODO: Should we delete the server here?
            tcp.deleteLater()

        #TODO: Better error reporting?
        return result

    def close(self):
        self.clients = {}
        self._addClient(self.client)
        if self.isConnected:
            tcp = self.tcp
            self.tcp = None
            for client in list(self.clients.values()):
                client.close()
            for socket in self.unknown:
                socket.close()
            self.unknown = set()
            tcp.close()
            # TODO: Should we delete the server here?
            tcp.deleteLater()
            print("[SERVER] No longer listening.")

    def send(self, username, data):
        """Call to send an object over the wire."""
        if not self.userExists(username):
            raise RuntimeError("Invalid username {0}".format(username))
        client = self.clients[self._processUsername(username)]
        client.receive(data)

    def broadcast(self, data, users=None):
        """Call to send an object over the wire.

		users -- users to send to; default is all
		"""
        if users:
            users = set(self._processUsername(username) for username in users)
        else:
            users = list(self.clients.keys())
        for shortname in users:
            assert (shortname in self.clients)
            self.clients[shortname].receive(data)

    def requestFile(self, username, filename):
        """Requests a file from the specified user."""
        if not self.userExists(username):
            raise RuntimeError("Invalid username {0}".format(username))
        client = self.clients[self._processUsername(username)]
        return client.requestFile(filename)

    def receive(self, username, data):
        """Receive the specified data."""
        return self.objectReceived.emit(self, username, data)

    def allowSend(server, username, filename, size, checksum):
        """Replacable hook for determining which files should be sent."""
        return True

    def allowReceipt(server, username, filename, size, checksum):
        """Replacable hook for determining which files should be accepted."""
        return True

    def _processUsername(self, username):
        """Processes a username to lowercase."""
        return UNICODE_STRING(username).lower()

    def _addClient(self, client):
        """Adds a client to the list."""
        assert (not self.userExists(client.username))
        self.clients[self._processUsername(client.username)] = client

    def userExists(self, username):
        """Checks whether the given username is taken."""
        return (self._processUsername(username) in self.clients)

    def fullname(self, username):
        """Returns the correctly capitalized version of the username."""
        assert (self.userExists(username))
        return self.clients[self._processUsername(username)].username

    def allowPassword(self, passw):
        if passw == self.password:
            return True
        return False

    def addBan(self, IP):
        """Adds the given IP to the banlist."""
        self.banlist.add(str(IP))

    def clearBanlist(self):
        """Clears all entries from the banlist."""
        self.banlist = set()

    @property
    def users(self):
        """Returns the list of usernames."""
        return list(self.clients.keys())

    def userIP(self, username):
        """Gets the IP of an existing client."""
        if not self.userExists(username):
            raise RuntimeError("Invalid username {0}".format(username))
        if self.clients[self._processUsername(username)] == self.client:
            return "127.0.0.1"
        return UNICODE_STRING(self.clients[self._processUsername(
            username)].obj.socket.peerAddress())

    def baseUsername(server):
        """Replaceable hook for the base 'guest' username."""
        # NOTE: should not localize
        return 'guest'

    def allowUsername(server, username):
        """Replacable hook for determining whether a username is OK.

		Does not need to determine uniqueness, but the name may
		be altered to something in the form of '{original}-[A-Za-z0-9]+'

		"""
        return bool(VALID_USERNAME.match(username))

    def _fixUsername(self, username=None):
        """Fixes a username so that it's unique."""
        if not username:
            username = self.baseUsername()
        if self.userExists(username):
            username = username + ('-' + findRandomAppend())
            while self.userExists(username):
                username += findRandomAppend()
        return str(username)

    def setPassword(self, new):
        self.password = new

    def rename(self, oldname, newname):
        """Renames a user to the specified name.

		Does some quick validity checking.
		Raises if not accepted;
		pre: not userExists(newname) and allowUsername(newname) and oldname != newname

		NOTE: Might cause file transfer to be declined, but
			will just reconnect.

		Only renames on this side; you must coordinate it manually.

		"""
        oldname = self._processUsername(oldname)
        newproc = self._processUsername(newname)

        assert (self.userExists(oldname))
        client = self.clients[oldname]
        assert (self.allowUsername(newname))
        assert (client.username != newname)
        assert (not self.userExists(newname) or newproc == oldname)

        del self.clients[oldname]
        client.username = newname
        self._addClient(client)

    def kick(self, username):
        """Kicks a user."""
        if not self.isConnected:
            return
        username = self._processUsername(username)
        assert (username in self.clients)
        client = self.clients[username]
        assert (client != self.client)
        client.close()
        del self.clients[username]
        self.kicked.emit(self, client.username)

    def _dropClient(self, username, errorMessage):
        """Disconnect a remote client."""
        if not self.isConnected:
            return
        username = self._processUsername(username)
        assert (self.userExists(username))
        assert (username in self.clients)
        client = self.clients[username]
        assert (client != self.client)
        client.close()
        assert (username in self.clients)
        del self.clients[username]
        self.disconnected.emit(self, client.username, errorMessage)

    def _respondXferDisconnect(self, username, errorMessage):
        """Attempt to reestablish a lost transfer socket connection."""
        assert (self.userExists(username))
        assert (username.lower() in self.clients)
        client = self.clients[username.lower()]
        assert (client != self.client)
        self.transferDisconnected.emit(self, client.username, errorMessage)

    connected = signal(object,
                       BASE_STRING,
                       doc="""Called when a client connects to the server.

	server -- this server
	username -- the username of the client

	""")

    disconnected = signal(
        object,
        BASE_STRING,
        BASE_STRING,
        doc="""Called when a client disconnects from the server.

		Not called when disconnected manually (through close()).

		Never called when hosting; you will never be disconnected automatically.

		server -- this server
		username -- the username of the client
		errorMessage -- the untranslated reason the connection failed

		""")

    transferDisconnected = signal(
        object,
        BASE_STRING,
        BASE_STRING,
        doc="""Called when a client transfer socket disconnects from the server.

		Not called when disconnected manually (through close()).

		Never called when hosting; you will never be disconnected automatically.

		server -- this server
		username -- the username of the client
		errorMessage -- the untranslated reason the connection failed

		""")

    kicked = signal(object,
                    BASE_STRING,
                    doc="""Called when a client is kicked from the server.

		server -- this server
		username -- the username of the client

		""")

    objectReceived = signal(
        object,
        BASE_STRING,
        dict,
        doc="""Called when an object is received from the client over the wire.

		server -- this server
		username -- the username of the client
		data -- the data received

		""")

    fileReceived = signal(object,
                          BASE_STRING,
                          BASE_STRING,
                          doc="""Called when a file is received over the wire.

		server -- this server
		username -- the username of the client
		filename -- the filename of the file received

		""")

    fileFailed = signal(object,
                        BASE_STRING,
                        BASE_STRING,
                        doc="""Called when a file fails to come over the wire.

		server -- this server
		username -- the username of the client
		filename -- the filename of the file received

		""")

    fileEvent = signal(object,
                       BASE_STRING,
                       BASE_STRING,
                       doc="""Called when a file fails to come over the wire.

		username -- the username of the client
		filename -- the filename of the file received
		event -- a description of the event

		""")

    partialTransferEvent = signal(object,
                                  BASE_STRING,
                                  BASE_STRING,
                                  BASE_STRING,
                                  doc="""Called when part of a transfer occurs.

		username -- the username of the client
		filename -- the filename of the file involved
		size -- total size of the file
		processed -- amount of the file transferred so far

		""")

    def passFileEvent(self, clientName, filename, eventDescription):
        self.fileEvent.emit(clientName, filename, eventDescription)

    def passPartialTransferEvent(self, clientName, filename, size, processed):
        self.partialTransferEvent.emit(clientName, filename, size, processed)

    def _newConnection(self):
        """Responds to a new connection occurring."""
        if not self.tcp:
            return
        while self.tcp.hasPendingConnections():
            socket = self.tcp.nextPendingConnection()
            if str(socket.peerAddress().toString()) in self.banlist:
                socket.close()
                print("[SERVER] Banned client attempted to connect: {1}:{2}".
                      format(socket.peerName(),
                             socket.peerAddress().toString(),
                             socket.peerPort()))
                return
            assert (socket)
            print("[SERVER] New client connected: {1}:{2}".format(
                socket.peerName(),
                socket.peerAddress().toString(), socket.peerPort()))
            socket = statefulSocket(socket=socket)
            self.unknown.add(socket)
            socket.disconnected.connect(self._socketDisconnected)
            socket.commandReceived.connect(self._socketCommand)
            socket.objectReceived.connect(self._socketObject)
            print("Connections successful for ", str(socket))

    def _detachUnknown(self, socket):
        self.unknown.discard(socket)
        socket.disconnected.disconnect()
        socket.commandReceived.disconnect()
        socket.objectReceived.disconnect()

    def _socketDisconnected(self, socket, reason):
        message = "Socket closed before identification: {reason}"
        message = message.format(reason=reason)
        self._forbidSocket(socket, message)

    def _forbidSocket(self, socket, text):
        message = "[{0}] {1}"
        print(message.format(socket.context, text))
        self._detachUnknown(socket)
        socket.close()

    def _socketObject(self, socket, obj):
        message = "Disallowed object data received"
        self._forbidSocket(socket, message)
        return

    def _socketCommand(self, socket, command, kwargs):
        if command != MESSAGE_IDENTIFY:
            message = "Disallowed initial remote command {command}"
            message = message.format(command=command)
            self._forbidSocket(socket, message)
            return
        try:
            kwargs = dict((str(key), val) for key, val in list(kwargs.items()))
            self._socketIdentified(socket, **kwargs)
        except TypeError as e:
            message = "Invalid parameters to initial remote command {command}: {parms}; {err}"
            message = message.format(command=command,
                                     parms=repr(kwargs),
                                     err=e)
            self._forbidSocket(socket, message)

    def _socketIdentified(self,
                          socket,
                          protocol,
                          username,
                          passw=None,
                          networkVersion="1"):
        """Socket identifies itself with protocol and username."""
        if protocol not in (PROTOCOL_OBJECT, PROTOCOL_TRANSFER):
            message = "Disallowed protocol identified {protocol} ({username})"
            message = message.format(protocol=protocol, username=username)
            self._forbidSocket(socket, message)
            return
        if networkVersion not in COMPATIBLE_NETWORK_VERSIONS:
            message = "Incompatible network version (client should update RGG): {networkVersion} ({username})"
            message = message.format(networkVersion=networkVersion,
                                     username=username)
            self._forbidSocket(socket, message)
            return
        self._detachUnknown(socket)
        if protocol == PROTOCOL_OBJECT:
            # Make it unique and valid

            if not self.allowUsername(username):
                username = None
            username = self._fixUsername(username)

            assert (not self.userExists(username))

            if self.password is not None and len(self.password) > 0:
                if not self.allowPassword(passw):
                    message = "Password error {protocol} ({username})"
                    message = message.format(protocol=protocol,
                                             username=username)
                    self._forbidSocket(socket, message)
                    return

            socket.imbueName("S-OBJ")
            socket.activate()
            client = RemoteClient(username, self, socket)
            self._addClient(client)
            client.fileEvent.connect(self.passFileEvent)
            client.partialTransferEvent.connect(self.passPartialTransferEvent)
            socket.sendMessage(MESSAGE_ACTIVATE, username=username)
            self.connected.emit(self, username)
        else:
            # Make sure identities match
            username = self._processUsername(username)
            if username not in self.clients:
                message = "Transfer protocol username does not match {username}"
                message = message.format(username=username)
                self._forbidSocket(socket, message)
                return
            client = self.clients[username]
            if client == self.client:
                message = "Transfer protocol attempt to match server user."
                message = message.format(username=username)
                self._forbidSocket(socket, message)
                return
            assert (client.ready)
            if client.obj.socket.peerAddress() != socket.socket.peerAddress():
                message = "Transfer protocol attempt to match user from different IP."
                message = message.format(username=username)
                self._forbidSocket(socket, message)
                return
            socket.activate()
            socket.imbueName("S-XFR")
            client._openXfer(socket)
            socket.sendMessage(MESSAGE_ACTIVATE, username=username)
            message = "[{0}:{1}] Transfer socket connected to {username}"
            print(
                message.format(socket.context,
                               client.obj.context,
                               username=username))
            client._updatetransfer()
Exemple #8
0
class JsonClient(BaseClient):
    """A client that communicates with a server."""

    # CONNECTION

    @property
    def isHosting(self):
        return self.server.isConnected

    @property
    def isConnected(self):
        return self.obj or self.server.isConnected

    def host(self, port):
        """Starts the network as a server."""
        if self.isConnected:
            raise RuntimeError("Already connected.")
        assert (self.username)
        result = self.server._listen(port)
        if result:
            self.port = port
        return result

    def join(self, hostname, port):
        """Starts the network as a client."""
        if self.isConnected:
            raise RuntimeError("Already connected.")
        self.obj = statefulSocket(name="C-OBJ", hostname=hostname, port=port)
        self.hostname = hostname
        self.port = port
        self.obj.connected.connect(self._socketConnected)
        self.obj.disconnected.connect(self._socketDisconnected)
        self.obj.objectReceived.connect(self._socketObject)
        self.obj.commandReceived.connect(self._socketCommand)
        assert (self.username)

    def close(self):
        """Closes the server as well as this client."""
        super(JsonClient, self).close()
        if self.server.isConnected:
            self.server.close()

    def _openXfer(self):
        """Open the transfer socket."""
        BaseClient._openXfer(
            self,
            statefulSocket(name="C-XFR",
                           hostname=self.hostname,
                           port=self.port))

    def send(self, data):
        """Call to send an object over the wire."""
        if self.ready:
            self.obj.sendObject(data)
        else:
            self.server.receive(self.username, data)

    def requestFile(self, filename):
        if self.ready:
            return BaseClient.requestFile(self, filename)
        else:
            return False

    def receive(self, obj):
        """Make the client receive some data."""
        self.objectReceived.emit(self, obj)

    def _updateSendReceive(self):
        """Determines the order of updates to avoid deadlock."""

        self.fileEvent.emit(self, "", "CHECKING SEND/RECEIVE")

        # Even if there's a transfer waiting, prioritize sending stuff first
        self._updateSend()

        # This check ensures the client priortizes sending over receiving
        if self.sentfile:
            self.fileEvent.emit(self, "", "SENT FILE")
            return
        self._updateReceive()

    # SIGNALS

    connected = signal(object,
                       BASE_STRING,
                       doc="""Called when the client is ready to start sending;
		when isConnected becomes True.

		Never called when hosting; can send immediately in that case.

		client -- this client
		username -- the username the server is using

		""")

    disconnected = signal(
        object,
        BASE_STRING,
        doc="""Called when the client disconnects or fails to connect.

		Not called when disconnected manually (through close()).

		Never called when hosting; you will never be disconnected automatically.

		client -- this client
		errorMessage -- the untranslated reason the connection failed

		""")

    transferDisconnected = signal(
        object,
        BASE_STRING,
        doc="""Called when the transfer socket disconnects or fails to connect.

		Not called when disconnected manually (through close()).

		Never called when hosting; you will never be disconnected automatically.

		client -- this client
		errorMessage -- the untranslated reason the connection failed

		""")

    objectReceived = signal(
        object,
        dict,
        doc="""Called when an object is received over the wire.

		client -- this client
		data -- the data received

		""")

    fileReceived = signal(object,
                          BASE_STRING,
                          doc="""Called when a file is received over the wire.

		client -- this client
		filename -- the filename of the file received

		""")

    fileFailed = signal(object,
                        BASE_STRING,
                        doc="""Called when a file fails to come over the wire.

		client -- this client
		filename -- the filename of the file received

		""")

    def setPassword(self, new):
        self.password = new

    # SIGNAL RESPONSES

    def _socketConnected(self, socket):
        """Called when a socket is connected."""
        if socket == self.obj:
            self.obj.sendMessage(MESSAGE_IDENTIFY,
                                 protocol=PROTOCOL_OBJECT,
                                 username=self.username,
                                 passw=self.password,
                                 networkVersion=NETWORK_VERSION)
        elif socket == self.xfer:
            self.xfer.sendMessage(MESSAGE_IDENTIFY,
                                  protocol=PROTOCOL_TRANSFER,
                                  username=self.username)

    def _socketDisconnected(self, socket, errorMessage):
        """Called when a socket disconnects."""
        if socket == self.obj:
            self.disconnected.emit(self, errorMessage)
        if socket == self.xfer:
            self.transferDisconnected.emit(self, errorMessage)
        super(JsonClient, self)._socketDisconnected(socket, errorMessage)

    def _activateSocket(self, socket, username):
        """Activates the socket."""
        if socket == self.xfer:
            socket.activate()
            self._updatetransfer()
        elif socket == self.obj:
            socket.activate()
            self.connected.emit(self, username)
            self._updatetransfer()
            super(JsonClient, self)._activateSocket(socket, username)

    def _fileReceived(self, socket, filename):
        """Emit and look for more stuff to send."""
        if socket == self.xfer:
            self.fileReceived.emit(self, filename)
        super(JsonClient, self)._fileReceived(socket, filename)

    def _fileFailed(self, filename):
        """Notify that the socket did not send the specified file."""
        self.fileFailed.emit(self, filename)
class ICChatWidget(QDockWidget):
    def __init__(self, mainWindow):
        super(QDockWidget, self).__init__(mainWindow)
        self.setToolTip(self.tr("A widget for in-character chat."))
        self.setWindowTitle(self.tr("IC Chat"))
        self.widgetEditor = QTextBrowser(mainWindow)
        self.widgetLineInput = chatLineEdit(mainWindow)
        self.widgetLineInput.setToolTip(
            self.tr(
                "Type text here and press Enter or Return to transmit it."))
        self.widget = QWidget(mainWindow)
        self.widgetEditor.setReadOnly(True)
        self.widgetEditor.setOpenLinks(False)
        self.characterPreview = QLabel(mainWindow)
        self.characterSelector = QComboBox(mainWindow)
        self.characterSelector.setToolTip(
            self.
            tr("Select the character to be displayed as the speaker of entered text."
               ))
        self.characterAddButton = QPushButton(self.tr("Add New"), mainWindow)
        self.characterAddButton.setToolTip(
            self.tr("Add a new in-character chat character via a dialog box."))
        self.characterDeleteButton = QPushButton(self.tr("Delete"), mainWindow)
        self.characterDeleteButton.setToolTip(
            self.tr(
                "Delete the currently selected in-character chat character."))
        self.characterClearButton = QPushButton(self.tr("Clear"), mainWindow)
        self.characterClearButton.setToolTip(
            self.tr("Deletes all in-character chat characters."))
        self.layout = QGridLayout()
        self.layout.addWidget(self.widgetEditor, 0, 0, 1, 4)
        self.layout.addWidget(self.widgetLineInput, 1, 1, 1, 3)
        self.layout.addWidget(self.characterPreview, 1, 0, 2, 1)
        self.layout.addWidget(self.characterDeleteButton, 2, 3, 1, 1)
        self.layout.addWidget(self.characterClearButton, 3, 3, 1, 1)
        self.layout.addWidget(self.characterAddButton, 2, 2, 1, 1)
        self.layout.addWidget(self.characterSelector, 2, 1, 1, 1)
        self.widget.setLayout(self.layout)
        self.setWidget(self.widget)
        self.setObjectName("IC Chat Widget")
        self.messageCache = []

        self.setAcceptDrops(True)

        mainWindow.addDockWidget(Qt.LeftDockWidgetArea, self)

        #TODO: Store and access characters in a better fashion.
        try:
            self.load(jsonload(ospath.join(CHAR_DIR, "autosave.rgc")))
        except:
            self.characters = []

        self.widgetLineInput.returnPressed.connect(self.processInput)
        self.characterAddButton.clicked.connect(self.newCharacter)
        self.characterDeleteButton.clicked.connect(self.deleteCharacter)
        self.characterClearButton.clicked.connect(self.clearCharacters)
        self.characterSelector.currentIndexChanged.connect(
            self.setCharacterPreview)

        self.updateDeleteButton()

        self.setCharacterPreview()

    def toggleDarkBackgroundSupport(self, dark):
        if dark:
            self.widgetEditor.document().setDefaultStyleSheet(
                "a {color: cyan; }")
        else:
            self.widgetEditor.document().setDefaultStyleSheet(
                "a {color: blue; }")
        self.refreshMessages()

    def refreshMessages(self):
        '''Clear the text display and re-add all messages with current style settings etc.'''
        self.widgetEditor.clear()
        for message in self.messageCache:
            self.widgetEditor.append(message)

    def updateDeleteButton(self):
        self.characterDeleteButton.setEnabled(self.hasCharacters())
        self.characterClearButton.setEnabled(self.hasCharacters())
        self.characterSelector.setEnabled(self.hasCharacters())
        self.widgetLineInput.setEnabled(self.hasCharacters())

    def setCharacterPreview(self, newIndex=-1):
        try:
            preview = QPixmap(
                ospath.join(
                    UNICODE_STRING(PORTRAIT_DIR),
                    UNICODE_STRING(self.characters[
                        self.characterSelector.currentIndex()].portrait)))
            if preview.isNull(
            ):  #Sadly, we have to check ahead, because Qt is dumb and prints an error about the scaling instead of raising one we can catch.
                raise TypeError
            preview = preview.scaled(min(preview.width(), 64),
                                     min(preview.height(), 64))
            self.characterPreview.setPixmap(preview)
        except:
            self.characterPreview.clear()

    def insertMessage(self, mes):
        self.scroll = (self.widgetEditor.verticalScrollBar().value() ==
                       self.widgetEditor.verticalScrollBar().maximum())
        self.messageCache.append(mes)
        self.widgetEditor.append(mes)
        if self.scroll:
            self.widgetEditor.verticalScrollBar().setValue(
                self.widgetEditor.verticalScrollBar().maximum())
        try:
            try:
                self.logfile = open(
                    ospath.join(LOG_DIR, strftime("%b_%d_%Y.log",
                                                  localtime())), 'a')
                self.logfile.write(mes + "\n")
            finally:
                self.logfile.close()
        except:
            pass

    def dragEnterEvent(self, event):
        if event.mimeData().hasImage():
            event.acceptProposedAction()

    def dropEvent(self, event):
        if event.mimeData().hasImage():
            dat = event.mimeData().imageData()
            img = QImage(dat)
            filename = promptSaveFile('Save Portrait',
                                      'Portrait files (*.png)', PORTRAIT_DIR)
            if filename is not None:
                img.save(filename, "PNG")
            event.acceptProposedAction()

    def newCharacter(self):
        dialog = newCharacterDialog()

        def accept():
            valid = dialog.is_valid()
            if not valid:
                showErrorMessage(dialog.error)
            return valid

        if dialog.exec_(self.parentWidget(), accept):
            newchardat = dialog.save()
            newchar = ICChar(*newchardat)
            self.characterSelector.addItem(newchar.id)
            self.characters.append(newchar)
            jsondump(self.dump(), ospath.join(CHAR_DIR, "autosave.rgc"))
            self.characterSelector.setCurrentIndex(
                self.characterSelector.count() - 1)
            self.updateDeleteButton()
            self.setCharacterPreview()

    def _newChar(self, char):
        self.characterSelector.addItem(char.id)
        self.characters.append(char)
        jsondump(self.dump(), ospath.join(CHAR_DIR, "autosave.rgc"))

    def deleteCharacter(self):
        if self.hasCharacters():
            self.characters.pop(self.characterSelector.currentIndex())
            self.characterSelector.removeItem(
                self.characterSelector.currentIndex())
            jsondump(self.dump(), ospath.join(CHAR_DIR, "autosave.rgc"))
            self.updateDeleteButton()

    def clearCharacters(self):
        if promptYesNo('Really clear all characters?') == 16384:
            self.characters = []
            self.characterSelector.clear()
            jsondump(self.dump(), ospath.join(CHAR_DIR, "autosave.rgc"))
            self.updateDeleteButton()
            self.setCharacterPreview()

    def processTags(self, message):
        message = message.replace("<", "&lt;").replace(">", "&gt;")
        for validTag in ("i", "b", "u", "s"):
            message = message.replace("".join(("[", validTag, "]")), "".join(
                ("<", validTag, ">")))
            message = message.replace("".join(("[", "/", validTag, "]")),
                                      "".join(("<", "/", validTag, ">")))
        return message

    def processInput(self):
        self.newmes = UNICODE_STRING(self.widgetLineInput.text())
        self.newmes = self.processTags(self.newmes)
        self.widgetLineInput.clear()
        self.widgetLineInput.addMessage(self.newmes)
        self.ICChatInput.emit(
            self.newmes,
            UNICODE_STRING(
                self.characters[self.characterSelector.currentIndex()].name),
            UNICODE_STRING(self.characters[
                self.characterSelector.currentIndex()].portrait))

    def hasCharacters(self):
        return len(self.characters) > 0

    def dump(self):
        """Serialize to an object valid for JSON dumping."""

        return dict(chars=dict([(i, char.dump())
                                for i, char in enumerate(self.characters)]))

    def load(self, obj):
        """Deserialize set of IC characters from a dictionary."""

        self.characters = []
        self.characterSelector.clear()

        chars = loadObject('ICChatWidget.chars', obj.get('chars'))
        chartemp = [None] * len(list(chars.keys()))
        for ID, char in list(chars.items()):
            chartemp[int(ID)] = char
        for char in chartemp:
            loaded = ICChar.load(char)
            self._newChar(loaded)
        self.updateDeleteButton()
        self.setCharacterPreview()

    ICChatInput = signal(
        BASE_STRING,
        BASE_STRING,
        BASE_STRING,
        doc="""Called when in-character chat input is received.

		charname -- the character name currently selected
		text -- the message entered
		portrait -- the portrait path, relative to data/portraits

		""")
class GLWidget(QGLWidget):
    '''
	Widget for drawing everything, and for catching mouse presses and similar
	'''

    mousePressSignal = pyqtSignal(int, int, int)  #x, y, button
    mouseReleaseSignal = pyqtSignal(int, int, int)  #x, y, button
    mouseMoveSignal = pyqtSignal(int, int)  #x, y
    keyPressSignal = pyqtSignal(int)  #key
    keyReleaseSignal = pyqtSignal(int)  #key

    def __init__(self, parent):
        QGLWidget.__init__(self, parent)

        self.setMinimumSize(320, 240)
        self.w = 640
        self.h = 480
        self.images = dict()
        self.allimgs = []
        self.lastMousePos = [0, 0]
        self.camera = [0, 0]
        self.layers = []
        self.zoom = 1
        self.offset = 0
        self.ctrl = False
        self.shift = False
        self.qimages = {}
        self.texext = GL_TEXTURE_2D
        self.lines = dict()
        self.previewLines = dict()
        self.selectionCircles = dict()
        self.rectangles = {1: []}
        self.error = False
        self.texts = []
        self.textid = 0
        self.logoon = "Off"

        self.setAcceptDrops(True)

        #settings, as used in SAVE_DIR/gfx_settings.rgs
        self.npot = 3
        self.anifilt = 0
        self.magfilter = GL_NEAREST
        self.mipminfilter = GL_NEAREST_MIPMAP_NEAREST
        self.minfilter = GL_NEAREST

        self.setFocusPolicy(Qt.StrongFocus)
        self.setMouseTracking(
            True)  #this may be the fix for a weird problem with leaveevents

    #GL functions
    def paintGL(self):
        '''
		Drawing routine
		'''

        glClear(GL_COLOR_BUFFER_BIT)

        glPushMatrix()
        glTranslatef(self.camera[0], self.camera[1], 0)
        glScaled(self.zoom, self.zoom, 1)

        glColor4f(1.0, 1.0, 1.0, 1.0)
        for layer in self.layers:
            for img in self.images[layer]:
                self.drawImage(img)

        glDisable(self.texext)
        for layer in self.lines:
            glLineWidth(layer)
            glBegin(GL_LINES)
            for line in self.lines[layer]:
                glColor3f(line[4], line[5], line[6])
                glVertex2f(line[0], line[1])
                glVertex2f(line[2], line[3])
            glEnd()
        for layer in self.previewLines:
            glLineWidth(layer)
            glBegin(GL_LINES)
            for line in self.previewLines[layer]:
                glColor3f(line[4], line[5], line[6])
                glVertex2f(line[0], line[1])
                glVertex2f(line[2], line[3])
            glEnd()
        if -1 in self.selectionCircles:
            glLineWidth(3)
            glColor3f(0.0, 1.0, 0.0)
            for circle in self.selectionCircles[-1]:
                glBegin(GL_LINE_LOOP)
                for r in range(0, 360, 3):
                    glVertex2f(circle[0] + cos(r * 0.01745329) * circle[2],
                               circle[1] + sin(r * 0.01745329) * circle[2])
                glEnd()
        for rectangle in self.rectangles[1]:
            glLineWidth(2)
            glColor3f(rectangle[4], rectangle[5], rectangle[6])
            glBegin(GL_LINE_LOOP)
            glVertex2d(rectangle[0], rectangle[1])
            glVertex2d(rectangle[2], rectangle[1])
            glVertex2d(rectangle[2], rectangle[3])
            glVertex2d(rectangle[0], rectangle[3])
            glEnd()
        glEnable(self.texext)

        glColor4f(1.0, 1.0, 1.0, 1.0)
        for text in self.texts:
            _split = text[1].split("\n")
            brk = lambda x, n, acc=[]: brk(x[n:], n, acc + [(x[:n])]
                                           ) if x else acc
            split = []
            for item in _split:
                split.extend(brk(item, 35))
            if len(split[0]) == 0:
                split.pop(0)
            pos = -16 * (len(split) - 1)
            for t in split:
                if len(t) == 0:
                    continue

                self.renderText(float(text[2][0]),
                                float(text[2][1]) + pos, 0, t)
                pos += 16

        glPopMatrix()

    def addSelectionCircle(self, splasifarcity, x, y, radius):
        if not splasifarcity in self.selectionCircles:
            self.selectionCircles[splasifarcity] = []

        self.selectionCircles[splasifarcity].append(
            (float(x), float(y), float(radius)))

    def clearSelectionCircles(self):
        self.selectionCircles.clear()

    def addLine(self, thickness, x, y, w, h, r, g, b):
        if not thickness in self.lines:
            self.lines[thickness] = []

        self.lines[thickness].append((float(x), float(y), float(w), float(h),
                                      float(r), float(g), float(b)))

    def deleteLine(self, thickness, x, y, w, h):
        for thickness in self.lines:
            new_list = []
            for line in self.lines[thickness]:
                if not self.pointIntersectRect((line[0], line[1]), (x, y, w, h)) \
                    and not self.pointIntersectRect((line[2], line[3]), (x, y, w, h)):
                    new_list.append(line)
            self.lines[thickness] = new_list

    def addPreviewLine(self, thickness, x, y, w, h, r, g, b):
        if not thickness in self.previewLines:
            self.previewLines[thickness] = []

        self.previewLines[thickness].append(
            (float(x), float(y), float(w), float(h), float(r), float(g),
             float(b)))

    def clearLines(self):
        self.lines.clear()

    def clearPreviewLines(self):
        self.previewLines.clear()

    def addRectangle(self, x, y, w, h, r, g, b):
        self.rectangles[1].append((float(x), float(y), float(w), float(h),
                                   float(r), float(g), float(b)))

    def clearRectangles(self):
        self.rectangles = {1: []}

    def pointIntersectRect(self, point, rect):
        #point: (x, y)
        #rect:  (x, y, w, h)
        if point[0] < rect[0] or point[0] > rect[0] + rect[2]:
            return False
        if point[1] < rect[1] or point[1] > rect[1] + rect[3]:
            return False
        return True

    def resizeGL(self, w, h):
        '''
		Resize the GL window
		'''

        glViewport(0, 0, w, h)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(0, w, h, 0, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
        self.w = w
        self.h = h

        if self.logoon == "On":
            if self.logo[0] and len(self.logo) == 5:
                self.logo[4].setDrawRect([0, 0, w, h])

    #ugly conversion function :(
    def interpretString(self, string):
        if string == "GL_NEAREST":
            return GL_NEAREST
        if string == "GL_LINEAR":
            return GL_LINEAR
        if string == "GL_NEAREST_MIPMAP_NEAREST":
            return GL_NEAREST_MIPMAP_NEAREST
        if string == "GL_NEAREST_MIPMAP_LINEAR":
            return GL_NEAREST_MIPMAP_LINEAR
        if string == "GL_LINEAR_MIPMAP_NEAREST":
            return GL_LINEAR_MIPMAP_NEAREST
        if string == "GL_LINEAR_MIPMAP_LINEAR":
            return GL_LINEAR_MIPMAP_LINEAR
        return string

    def initializeGL(self):
        '''
		Initialize GL
		'''

        self.fieldtemp = [
            1.0, "GL_NEAREST", "GL_NEAREST", "GL_NEAREST_MIPMAP_NEAREST", "On"
        ]

        try:
            js = jsonload(path.join(SAVE_DIR, "gfx_settings.rgs"))
            self.fieldtemp[0] = loadFloat('gfx.anifilt', js.get('anifilt'))
            self.fieldtemp[1] = loadString('gfx.minfilter',
                                           js.get('minfilter'))
            self.fieldtemp[2] = loadString('gfx.magfilter',
                                           js.get('magfilter'))
            self.fieldtemp[3] = loadString('gfx.mipminfilter',
                                           js.get('mipminfilter'))
            self.fieldtemp[4] = loadString('gfx.FSAA', js.get('FSAA'))
        except:
            #print("no settings detected")
            pass

        #mipmap support and NPOT texture support block
        if not hasGLExtension("GL_ARB_framebuffer_object"):
            #print("GL_ARB_framebuffer_object not supported, switching to GL_GENERATE_MIPMAP")
            self.npot = 2
        version = glGetString(GL_VERSION)
        if int(version[0]) == 1 and int(
                version[2]) < 4:  #no opengl 1.4 support
            #print("GL_GENERATE_MIPMAP not supported, not using mipmapping")
            self.npot = 1
        if not hasGLExtension("GL_ARB_texture_rectangle"):
            #print("GL_TEXTURE_RECTANGLE_ARB not supported, switching to GL_TEXTURE_2D")
            self.texext = GL_TEXTURE_2D
            self.npot = 0

        #assorted settings block
        if hasGLExtension("GL_EXT_texture_filter_anisotropic"
                          ) and self.fieldtemp[0] > 1.0:
            self.anifilt = self.fieldtemp[0]
            #print("using " + str(self.fieldtemp[0]) + "x anisotropic texture filtering. max: " + str(glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)))
        self.minfilter = self.interpretString(self.fieldtemp[1])
        self.magfilter = self.interpretString(self.fieldtemp[2])
        self.mipminfilter = self.interpretString(self.fieldtemp[3])
        if self.mipminfilter == "Off":
            self.mipminfilter = -1
        if self.format().sampleBuffers() and self.fieldtemp[4] == "On":
            #print("enabling "  + str(self.format().samples()) + "x FSAA")
            glEnable(GL_MULTISAMPLE)
        else:
            pass
            #print("FSAA not supported and/or disabled")

        glEnable(self.texext)
        glEnable(GL_BLEND)
        glDisable(GL_DEPTH_TEST)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glViewport(0, 0, self.width(), self.height())
        glClearColor(0.0, 0.0, 0.0, 0.0)

    #util functions
    def createImage(self,
                    qimagepath,
                    layer,
                    textureRect,
                    drawRect,
                    hidden=False,
                    dynamicity=GL_STATIC_DRAW_ARB):
        '''
		Creates an rggTile instance, uploads the correct image to GPU if not in cache, and some other helpful things.
		'''
        #print "requested to create", qimagepath, layer, textureRect, drawRect, hidden
        layer = int(layer)
        texture = None
        found = False

        if qimagepath in self.qimages:
            qimg = self.qimages[qimagepath][0]
            if self.qimages[qimagepath][2] > 0:
                texture = self.qimages[qimagepath][1]
                found = True
        else:
            qimg = QImage(qimagepath)
            #print("created", qimagepath)

        if textureRect[2] == -1:
            textureRect[2] = qimg.width()

        if textureRect[3] == -1:
            textureRect[3] = qimg.height()

        if drawRect[2] == -1:
            drawRect[2] = qimg.width()

        if drawRect[3] == -1:
            drawRect[3] = qimg.height()

        image = tile(qimagepath, textureRect, drawRect, layer, hidden,
                     dynamicity, self)

        if found == False:
            img = None
            if self.npot == 0:
                w = nextPowerOfTwo(qimg.width())
                h = nextPowerOfTwo(qimg.height())
                if w != qimg.width() or h != qimg.height():
                    img = self.convertToGLFormat(qimg.scaled(w, h))
                else:
                    img = self.convertToGLFormat(qimg)
            else:
                img = self.convertToGLFormat(qimg)

            texture = int(glGenTextures(1))
            try:
                imgdata = img.bits().asstring(img.byteCount())
            except Exception as e:
                print(e)
                print("requested to create", qimagepath, layer, textureRect,
                      drawRect, hidden)
                for x in [0, 1, 2, 3]:
                    f_code = _getframe(
                        x
                    ).f_code  #really bad hack to get the filename and number
                    print("Doing it wrong in " + f_code.co_filename + ":" +
                          str(f_code.co_firstlineno))
                    print("Error: " + e)

            #print("created texture", texture)

            glBindTexture(self.texext, texture)

            if self.anifilt > 1.0:
                glTexParameterf(self.texext, GL_TEXTURE_MAX_ANISOTROPY_EXT,
                                self.anifilt)
            if self.npot == 3 and self.mipminfilter != -1:
                glTexParameteri(self.texext, GL_TEXTURE_MIN_FILTER,
                                self.mipminfilter)
                glTexParameteri(self.texext, GL_TEXTURE_MAG_FILTER,
                                self.magfilter)
            elif self.npot == 2 and self.mipminfilter != -1:
                glTexParameteri(self.texext, GL_TEXTURE_MIN_FILTER,
                                self.mipminfilter)
                glTexParameteri(self.texext, GL_TEXTURE_MAG_FILTER,
                                self.magfilter)
                glTexParameteri(self.texext, GL_GENERATE_MIPMAP, GL_TRUE)
            else:
                glTexParameteri(self.texext, GL_TEXTURE_MIN_FILTER,
                                self.minfilter)
                glTexParameteri(self.texext, GL_TEXTURE_MAG_FILTER,
                                self.magfilter)

            glTexParameteri(self.texext, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
            glTexParameteri(self.texext, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)
            glTexParameteri(self.texext, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)

            glTexImage2D(self.texext, 0, GL_RGBA, img.width(), img.height(), 0,
                         GL_RGBA, GL_UNSIGNED_BYTE, imgdata)

            if self.npot == 3 and self.mipminfilter != -1:
                glEnable(GL_TEXTURE_2D)
                glGenerateMipmap(GL_TEXTURE_2D)

            self.qimages[qimagepath] = [qimg, texture,
                                        1]  #texture, reference count
        else:
            self.qimages[qimagepath][2] += 1

        image.textureId = texture

        if layer not in self.images:
            self.images[layer] = []
            self.layers = list(self.images.keys())
            self.layers.sort()
            image.createLayer = True

        self.images[layer].append(image)
        self.allimgs.append(image)

        return image

    def deleteImage(self, image):
        '''
		Decreases the reference count of the texture by one, and deletes it if nothing is using it anymore
		'''

        self.qimages[image.imagepath][2] -= 1

        if self.qimages[image.imagepath][2] <= 0:
            #print("deleting texture", image.textureId)
            glDeleteTextures(image.textureId)
            del self.qimages[image.imagepath]

        self.images[image.layer].remove(image)
        self.allimgs.remove(image)

        image = None

    def deleteImages(self, imageArray):
        '''
		Decreases the reference count of the texture of each image by one, and deletes it if nothing is using it anymore
		'''

        for image in imageArray:
            self.qimages[image.imagepath][2] -= 1

            if self.qimages[image.imagepath][2] <= 0:
                #print("deleting texture", image.textureId)
                glDeleteTextures(image.textureId)
                del self.qimages[image.imagepath]

            self.images[image.layer].remove(image)
            self.allimgs.remove(image)

    def drawImage(self, image):

        if image.hidden:
            return

        x, y, w, h = image.textureRect
        dx, dy, dw, dh = image.drawRect
        r = float(image.rotation)

        cx, cy = self.camera

        # Culling
        if (dx * self.zoom >
                self.w - cx) or (dy * self.zoom > self.h - cy) or (
                    (dx + dw) * self.zoom < 0 - cx) or (
                        (dy + dh) * self.zoom < 0 - cy):
            return

        self.drawTexture(image.textureId, dx, dy, dw, dh, x, y, w, h, r)

    def drawTexture(self, texture, dx, dy, dw, dh, x, y, w, h, r):
        '''
		texture is an int
		textureRect is a list of size 4, determines which square to take from the texture
		drawRect is a list of size 4, is used to determine the drawing size
		'''

        glBindTexture(self.texext, texture)

        glPushMatrix()
        glTranslatef(dx + dw / 2, dy + dh / 2, 0)
        glRotatef(r, 0, 0, 1.0)
        glTranslatef(-1 * (dx + dw / 2), -1 * (dy + dh / 2), 0)

        glBegin(GL_QUADS)
        #Top-left vertex (corner)
        glTexCoord2f(x, y + h)  #image/texture
        glVertex3f(dx, dy, 0)  #screen coordinates

        #Bottom-left vertex (corner)
        glTexCoord2f(x + w, y + h)
        glVertex3f((dx + dw), dy, 0)

        #Bottom-right vertex (corner)
        glTexCoord2f(x + w, y)
        glVertex3f((dx + dw), (dy + dh), 0)

        #Top-right vertex (corner)
        glTexCoord2f(x, y)
        glVertex3f(dx, (dy + dh), 0)
        glEnd()

        glPopMatrix()

    def hideImage(self, image, hide):
        '''
		This function should only be called from image.py
		Use Image.hide() instead.
		'''
        pass

    def setLayer(self, image, newLayer):
        '''
		This function should only be called from image.py
		Use Image.layer instead.
		'''

        oldLayer = image._layer
        image._layer = newLayer
        if newLayer not in self.images:
            self.images[newLayer] = []
            self.layers = list(self.images.keys())
            self.layers.sort()
            image.createLayer = True

        self.images[oldLayer].remove(image)
        self.images[newLayer].append(image)

    def getImageSize(self, image):
        qimg = None

        if image in self.qimages:
            qimg = self.qimages[image][0]
        else:
            qimg = QImage(image)

        return qimg.size()

    def addText(self, text, pos):
        self.texts.append([self.textid, text, pos])
        self.textid += 1
        return self.textid - 1

    def removeText(self, id):
        for i, t in enumerate(self.texts):
            if t[0] == id:
                self.texts.pop(i)
                return

    def setTextPos(self, id, pos):
        for t in self.texts:
            if t[0] == id:
                t[2] = pos

    def mouseMoveEvent(self, mouse):
        self.mouseMoveSignal.emit(mouse.pos().x(), mouse.pos().y())

        mouse.accept()

    def mousePressEvent(self, mouse):
        button = 0

        if self.ctrl:
            button += 3
        if self.shift:
            button += 6

        if mouse.button() == Qt.LeftButton:
            button += 0
        elif mouse.button() == Qt.RightButton:
            button += 2
        elif mouse.button() == Qt.MidButton:
            button += 1
        self.mousePressSignal.emit(mouse.pos().x(), mouse.pos().y(), button)

        mouse.accept()

    def mouseReleaseEvent(self, mouse):
        button = 0

        if self.ctrl:
            button += 3
        if self.shift:
            button += 6

        if mouse.button() == Qt.LeftButton:
            button += 0
        elif mouse.button() == Qt.RightButton:
            button += 2
        elif mouse.button() == Qt.MidButton:
            button += 1
        self.mouseReleaseSignal.emit(mouse.pos().x(), mouse.pos().y(), button)

        mouse.accept()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.ctrl = True
        elif event.key() == Qt.Key_Shift:
            self.shift = True
        elif event.key() == Qt.Key_Plus or event.key() == Qt.Key_Equal:
            self.zoom += 0.15
            if self.zoom > 4:
                self.zoom = 4
        elif event.key() == Qt.Key_Minus:
            self.zoom -= 0.15
            if self.zoom < 0.30:
                self.zoom = 0.30
        elif event.key() == Qt.Key_0:
            self.zoom = 1
        elif event.key() == Qt.Key_Up:
            self.camera[1] += (50 * self.zoom)
        elif event.key() == Qt.Key_Down:
            self.camera[1] -= (50 * self.zoom)
        elif event.key() == Qt.Key_Left:
            self.camera[0] += (50 * self.zoom)
        elif event.key() == Qt.Key_Right:
            self.camera[0] -= (50 * self.zoom)
        else:
            self.keyPressSignal.emit(event.key())

    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.ctrl = False
        elif event.key() == Qt.Key_Shift:
            self.shift = False
        else:
            self.keyReleaseSignal.emit(event.key())

    def wheelEvent(self, mouse):
        oldCoord = [mouse.pos().x(), mouse.pos().y()]
        oldCoord[0] *= float(1) / self.zoom
        oldCoord[1] *= float(1) / self.zoom

        oldCoord2 = self.camera
        oldCoord2[0] *= float(1) / self.zoom
        oldCoord2[1] *= float(1) / self.zoom

        try:
            delta = mouse.angleDelta().y(
            )  #let's not worry about 2-dimensional wheels.
        except AttributeError:
            delta = mouse.delta()

        if delta < 0:
            self.zoom -= 0.5
        elif delta > 0:
            self.zoom += 0.5

        if self.zoom < 0.60:
            self.zoom = 0.5
        elif self.zoom > 4:
            self.zoom = 4

        self.camera[0] = oldCoord2[0] * self.zoom - (
            (oldCoord[0] * self.zoom) - mouse.pos().x())
        self.camera[1] = oldCoord2[1] * self.zoom - (
            (oldCoord[1] * self.zoom) - mouse.pos().y())

        mouse.accept()

    def leaveEvent(self, event):
        self.ctrl = False
        self.shift = False

    def dragEnterEvent(self, event):
        if event.mimeData().hasImage():
            event.acceptProposedAction()
        elif event.mimeData().hasText():
            event.acceptProposedAction()

    def dropEvent(self, event):
        if event.mimeData().hasImage():
            dat = event.mimeData().imageData()
            img = QImage(dat)
            filename = promptSaveFile('Save Pog', 'Pog files (*.png)', POG_DIR)
            if filename is not None:
                img.save(filename, "PNG")
            event.acceptProposedAction()
        elif event.mimeData().hasText():
            self.pogPlace.emit(event.pos().x(),
                               event.pos().y(), str(event.mimeData().text()))

    pogPlace = signal(int,
                      int,
                      BASE_STRING,
                      doc="""Called to request pog placement on the map.""")
class diceRoller(QDockWidget):
    def __init__(self, mainWindow):
        super(QDockWidget, self).__init__(mainWindow)
        self.setWindowTitle(self.tr("Dice"))
        self.realwidget = QWidget(
            mainWindow
        )  #I messed up on the initial setup and was too lazy to rename everything.
        self.widget = QGridLayout()
        self.diceArea = QListWidget(mainWindow)
        try:
            self.load(jsonload(ospath.join(SAVE_DIR, "dice.rgd")))
        except:
            self.macros = [
                QListWidgetItem(QIcon('data/dice.png'), "Sample: 2d6"),
                QListWidgetItem(QIcon('data/dice.png'), "Sample: 4k2"),
                QListWidgetItem(QIcon('data/dice.png'), "Sample: 1dn3")
            ]
        for m in self.macros:
            self.diceArea.addItem(m)
        self.diceArea.currentRowChanged.connect(self.changeCurrentMacro)
        self.rollbutton = QPushButton(self.tr("Roll"), mainWindow)
        self.rollbutton.setToolTip(
            self.tr("Roll dice according to the selected macro."))
        self.addmacrobutton = QPushButton(self.tr("Add Macro"), mainWindow)
        self.addmacrobutton.setToolTip(
            self.tr("Add a new macro via a dialog box."))
        self.removemacrobutton = QPushButton(self.tr("Delete Macro"),
                                             mainWindow)
        self.removemacrobutton.setToolTip(
            self.tr("Remove the currently selected macro."))
        self.rollbutton.clicked.connect(self.rollDice)
        self.addmacrobutton.clicked.connect(self.summonMacro)
        self.removemacrobutton.clicked.connect(self.removeCurrentMacro)
        self.widget.addWidget(self.diceArea, 0, 0)
        self.widget.addWidget(self.rollbutton, 1, 0)
        self.widget.addWidget(self.addmacrobutton, 2, 0)
        self.widget.addWidget(self.removemacrobutton, 3, 0)
        self.realwidget.setLayout(self.widget)
        self.setWidget(self.realwidget)
        self.setObjectName("Dice Widget")
        mainWindow.addDockWidget(Qt.BottomDockWidgetArea, self)
        self.close()
        self.currentMacro = -1

    def changeCurrentMacro(self, n):
        self.currentMacro = n

    def rollDice(self):
        current = self.diceArea.item(self.currentMacro)
        if current is not None:
            text = UNICODE_STRING(current.text())
            self.rollRequested.emit(text[text.rfind(':') + 1:])

    def _addMacro(self, macro):
        self.macros.append(QListWidgetItem(QIcon('data/dice.png'), macro))
        self.diceArea.addItem(self.macros[len(self.macros) - 1])

    def addMacro(self, mac, macname):
        self.macros.append(
            QListWidgetItem(QIcon('data/dice.png'), macname + ': ' + mac))
        self.diceArea.addItem(self.macros[len(self.macros) - 1])
        jsondump(self.dump(), ospath.join(SAVE_DIR, "dice.rgd"))

    def removeCurrentMacro(self):
        if self.diceArea.item(self.currentMacro) != self.diceArea.currentItem(
        ):  #This SHOULD, probably, only occur if there are two items and the first is deleted. Probably.
            self.diceArea.takeItem(0)
            return
        self.diceArea.takeItem(self.currentMacro)
        jsondump(self.dump(), ospath.join(SAVE_DIR, "dice.rgd"))

    def summonMacro(self):
        self.macroRequested.emit()

    def load(self, obj):
        """Deserialize set of macros from a dictionary."""
        self.macros = []
        macroz = loadObject('diceRoller.macros', obj.get('macros'))
        for ID, macro in list(macroz.items()):
            self._addMacro(macro)

    def dump(self):
        """Serialize to an object valid for JSON dumping."""

        macroz = []
        for i in range(0, self.diceArea.count()):
            macroz.append(UNICODE_STRING(self.diceArea.item(i).text()))

        return dict(macros=dict([(i, macro)
                                 for i, macro in enumerate(macroz)]))

    rollRequested = signal(BASE_STRING,
                           doc="""Called when the roll button is hit.

		roll -- the dice to be rolled

		""")

    macroRequested = signal(
        doc="""Called when the add macro button is pressed.""")
Exemple #12
0
class scratchPadWidget(QDockWidget):
	"""A mutually-editable shared text widget."""

	def __init__(self, mainWindow):
		"""Initializes the user list."""
		super(QDockWidget, self).__init__(mainWindow)
		self.setWindowTitle(self.tr("Scratch Pad"))
		self.widget = QWidget(mainWindow)
		self.textArea = QTextEdit(mainWindow)
		self.currentEditingLabel = QLabel("No one is editing.")
		self.getLockButton = QPushButton("Edit")
		self.releaseLockButton = QPushButton("Confirm")
		#self.saveToFileButton = QPushButton("Save...")
		#self.loadButton = QPushButton("Load")
		self.layout = QGridLayout()
		self.layout.addWidget(self.currentEditingLabel, 0, 0, 1, 2)
		self.layout.addWidget(self.textArea, 1, 0, 1, 2)
		self.layout.addWidget(self.getLockButton, 2, 0)
		self.layout.addWidget(self.releaseLockButton, 2, 1)
		self.releaseLockButton.pressed.connect(self._releaseLock)
		self.getLockButton.pressed.connect(self._getLock)
		self.widget.setLayout(self.layout)
		self.setWidget(self.widget)
		self.setObjectName("Scratch Pad Widget")
		self.hasLock = False
		self.updateButtonStatus()
		try:
			with open(ospath.join(SAVE_DIR, "scratchpad.txt"), 'r') as autosave:
				self.textArea.setText(autosave.read())
		except:
			pass
		mainWindow.addDockWidget(Qt.BottomDockWidgetArea, self)

	@property
	def currentText(self):
		return self.textArea.toPlainText()

	def updateButtonStatus(self):
		self.getLockButton.setEnabled(not self.hasLock)
		self.textArea.setReadOnly(not self.hasLock)
		self.releaseLockButton.setEnabled(self.hasLock)

	def _getLock(self):
		self.getScratchPadLock.emit()

	def _releaseLock(self):
		self.updateScratchPad.emit()
		self.releaseScratchPadLock.emit()

	def getLock(self):
		self.hasLock = True
		self.updateButtonStatus()

	def releaseLock(self, name):
		self.hasLock = False
		if name:
			self.currentEditingLabel.setText("%s is editing." % name)
		else:
			self.currentEditingLabel.setText("No one is editing.")
		self.updateButtonStatus()

	def updateText(self, txt):
		if len(txt) > 100000:
			print("Excessively long scratchpad data received. Might be an attack or just a bug.")
			return
		self.textArea.setText(txt)
		try:
			with open(ospath.join(SAVE_DIR, "scratchpad.txt"), 'w') as autosave:
				autosave.write(txt)
		except:
			pass

	getScratchPadLock = signal(doc=
		"""Called to request lock for scratch pad."""
	)

	releaseScratchPadLock = signal(doc=
		"""Called to indicate relinquishment of scratch pad lock."""
	)

	updateScratchPad = signal(doc=
		"""Called to request that the scratchpad be updated."""
	)