Ejemplo n.º 1
0
 def displayImage(self, image):
     converted = imqt.ImageQt(image)
     converted = converted.copy()
     converted = QPixmap.fromImage(converted)
     label = QLabel()
     label.setPixmap(converted)
     self.scrollarea.setWidget(label)
     if (converted.height(),
             converted.width()) not in self.validBorderSizes:
         self.borderButton.setEnabled(False)
     else:
         self.borderButton.setEnabled(True)
Ejemplo n.º 2
0
class mapEditor(QDockWidget):
    def __init__(self, mainWindow):
        super(QDockWidget, self).__init__(mainWindow)

        #self.__eat = True
        self.painting = True
        self.dragging = False
        self.rectStart = None

        self.setWindowTitle(self.tr("Map Editor"))
        self.widget = QWidget(mainWindow)
        self.layout = QGridLayout()
        self.currentTileLayout = QBoxLayout(1)
        self.scrollarea = QScrollArea(mainWindow)
        self.noPaintingButton = QRadioButton(self.tr("Stop Painting"),
                                             mainWindow)
        self.singlePaintingButton = QRadioButton(self.tr("Single Tile Brush"),
                                                 mainWindow)
        self.noPaintingButton.setChecked(True)
        self.rectPaintingButton = QRadioButton(
            self.tr("Area (Rectangle) Brush"), mainWindow)
        self.hollowRectPaintingButton = QRadioButton(
            self.tr("Hollow Rectangle Brush"), mainWindow)
        self.currentTileLabel = QLabel()
        self.currentTileLabelLabel = QLabel(self.tr("Current tile: "))
        self.undoButton = QPushButton("Undo", mainWindow)
        self.redoButton = QPushButton("Redo", mainWindow)
        #self.moveMapButton = QPushButton("Move Map", mainWindow)
        self.layout.addWidget(self.scrollarea, 0, 0, 1, 2)
        self.layout.addWidget(self.noPaintingButton, 1, 0)
        self.layout.addWidget(self.singlePaintingButton, 2, 0)
        self.layout.addWidget(self.rectPaintingButton, 3, 0)
        self.layout.addWidget(self.hollowRectPaintingButton, 4, 0)
        self.layout.addWidget(self.undoButton, 1, 1)
        self.layout.addWidget(self.redoButton, 2, 1)
        #self.layout.addWidget(self.moveMapButton, 3, 1)
        self.layout.addWidget(self.currentTileLabel, 5, 1)
        self.layout.addWidget(self.currentTileLabelLabel, 5, 0)
        self.tilelabel = None
        self.widget.setLayout(self.layout)
        self.setWidget(self.widget)
        self.setObjectName("Map Editor")
        mainWindow.addDockWidget(Qt.RightDockWidgetArea, self)

        self.currentMap = None
        self.copyData = None

        self.undo = []
        self.undoButton.clicked.connect(self._undo)
        self.undoButton.setEnabled(False)

        self.redo = []
        self.redoButton.clicked.connect(self._redo)
        self.redoButton.setEnabled(False)

        addMapChangedListener(self.mapChangedResponse, NORMAL_RESPONSE_LEVEL)
        addMousePressListener(self.mousePressResponse, NORMAL_RESPONSE_LEVEL)
        addMouseMoveListener(self.mouseMoveResponse, NORMAL_RESPONSE_LEVEL)
        addMouseReleaseListener(self.mouseReleaseResponse,
                                NORMAL_RESPONSE_LEVEL)

    def _undo(self):
        try:
            from libraries.rggViews import _sendTileUpdate
        except ImportError:
            from rggViews import _sendTileUpdate
        redoTiles = []
        for data in self.undo.pop():
            redoTiles.append(
                (data[0], data[1], _sendTileUpdate(data[0], data[1], data[2])))
        self.redo.append(redoTiles)
        self.redoButton.setEnabled(True)
        if len(self.undo) == 0:
            self.undoButton.setEnabled(False)

    def _redo(self):
        try:
            from libraries.rggViews import _sendTileUpdate
        except ImportError:
            from rggViews import _sendTileUpdate
        undoTiles = []
        for data in self.redo.pop():
            undoTiles.append(
                (data[0], data[1], _sendTileUpdate(data[0], data[1], data[2])))
        self.undo.append(undoTiles)
        self.undoButton.setEnabled(True)
        if len(self.redo) == 0:
            self.redoButton.setEnabled(False)

    def updateCurrentTile(self):
        self.tilepix = QPixmap()
        self.tilepix.load(self.currentMap.tileset)
        self.tilepix = self.tilepix.copy(
            QRect(*self.tilelabel.currentTileDimensions))
        self.currentTileLabel.setPixmap(self.tilepix)

    def mousePressResponse(self, x, y, t):
        mapPosition = getMapPosition((x, y))

        #This and similar things were a regrettable necessity in the plugin -> nonplugin conversion process.
        try:
            from libraries.rggViews import topmap, _sendTileUpdate
        except ImportError:
            from rggViews import topmap, _sendTileUpdate

        map = topmap(mapPosition)
        if map == None:
            return
        if map != self.currentMap:
            self.mapChangedResponse(map)
        if t == 0:
            self.dragging = True
            if self.isVisible() and self.singlePaintingButton.isChecked():
                clickedtile = (int(((mapPosition[0] - map.drawOffset[0]) /
                                    self.tilelabel.tilex)),
                               int(((mapPosition[1] - map.drawOffset[1]) /
                                    self.tilelabel.tiley)))
                if not map.tilePosExists(clickedtile):
                    return True
                oldtile = _sendTileUpdate(map.ID, clickedtile,
                                          self.tilelabel.currentTile)
                self.undo.append([
                    (map.ID, clickedtile, oldtile),
                ])
                self.redo = []
                self.redoButton.setEnabled(False)
                self.undoButton.setEnabled(True)
                return True
            elif self.isVisible() and (
                    self.rectPaintingButton.isChecked()
                    or self.hollowRectPaintingButton.isChecked()):
                self.rectStart = (int(((mapPosition[0] - map.drawOffset[0]) /
                                       self.tilelabel.tilex)),
                                  int(((mapPosition[1] - map.drawOffset[1]) /
                                       self.tilelabel.tiley)))
                if not map.tilePosExists(self.rectStart):
                    self.rectStart = None
                return True
        elif t == 5:
            if self.isVisible() and not self.noPaintingButton.isChecked():
                clickedtile = (int(((mapPosition[0] - map.drawOffset[0]) /
                                    self.tilelabel.tilex)),
                               int(((mapPosition[1] - map.drawOffset[1]) /
                                    self.tilelabel.tiley)))
                self.tilelabel.currentTile = map.getTile(clickedtile)
                self.tilelabel.updateTile()
                return True
        elif t == 6:
            if self.isVisible(
            ) and not self.noPaintingButton.isChecked() and self.copyData:
                if self.singlePaintingButton.isChecked():
                    clickedtile = (int(((mapPosition[0] - map.drawOffset[0]) /
                                        self.tilelabel.tilex)),
                                   int(((mapPosition[1] - map.drawOffset[1]) /
                                        self.tilelabel.tiley)))
                    for row, columns in enumerate(self.copyData):
                        for column, tile in enumerate(columns):
                            _sendTileUpdate(map.ID, (clickedtile[0] + row,
                                                     clickedtile[1] + column),
                                            tile)
                elif self.rectPaintingButton.isChecked():
                    self.rectStart = (
                        int(((mapPosition[0] - map.drawOffset[0]) /
                             self.tilelabel.tilex)),
                        int(((mapPosition[1] - map.drawOffset[1]) /
                             self.tilelabel.tiley)))
                    if not map.tilePosExists(self.rectStart):
                        self.rectStart = None
                    return True
        elif t == 8:
            if self.isVisible() and not self.noPaintingButton.isChecked():
                self.rectStart = (int(((mapPosition[0] - map.drawOffset[0]) /
                                       self.tilelabel.tilex)),
                                  int(((mapPosition[1] - map.drawOffset[1]) /
                                       self.tilelabel.tiley)))
                if not map.tilePosExists(self.rectStart):
                    self.rectStart = None
                return True

    def mouseMoveResponse(self, x, y):
        if self.dragging and self.isVisible(
        ) and self.singlePaintingButton.isChecked():
            try:
                from libraries.rggViews import topmap
            except ImportError:
                from rggViews import topmap
            mapPosition = getMapPosition((x, y))
            map = topmap(mapPosition)
            if map == None:
                self.dragging = False
                return
            clickedtile = (int(
                ((mapPosition[0] - map.drawOffset[0]) / self.tilelabel.tilex)),
                           int(((mapPosition[1] - map.drawOffset[1]) /
                                self.tilelabel.tiley)))

            if map.tilePosExists(clickedtile) and map.getTile(
                    clickedtile) != self.tilelabel.currentTile:
                try:
                    from libraries.rggViews import _sendTileUpdate
                except ImportError:
                    from rggViews import _sendTileUpdate
                oldtile = _sendTileUpdate(map.ID, clickedtile,
                                          self.tilelabel.currentTile)
                self.undo[-1].append((map.ID, clickedtile, oldtile))
            return True

    def mouseReleaseResponse(self, x, y, t):
        if t == 0:
            try:
                from libraries.rggViews import topmap, _sendTileUpdate, _sendMultipleTileUpdate
            except ImportError:
                from rggViews import topmap, _sendTileUpdate, _sendMultipleTileUpdate
            if self.currentMap == None:
                return
            mapPosition = getMapPosition((x, y))
            map = topmap(mapPosition)
            self.dragging = False
            if map == None or map != self.currentMap:
                return
            if self.isVisible() and self.singlePaintingButton.isChecked():
                return True
            elif self.isVisible() and self.rectPaintingButton.isChecked(
            ) and self.rectStart is not None:
                rectEnd = (int(((mapPosition[0] - map.drawOffset[0]) /
                                self.tilelabel.tilex)),
                           int(((mapPosition[1] - map.drawOffset[1]) /
                                self.tilelabel.tiley)))
                if not map.tilePosExists(rectEnd):
                    self.rectStart = None
                    self.rectEnd = None
                    return
                self.undo.append([])
                self.undoButton.setEnabled(True)
                self.redo = []
                self.redoButton.setEnabled(False)
                oldtiles = _sendMultipleTileUpdate(
                    self.currentMap.ID, (min(rectEnd[0], self.rectStart[0]),
                                         min(rectEnd[1], self.rectStart[1])),
                    (max(rectEnd[0], self.rectStart[0]),
                     max(rectEnd[1], self.rectStart[1])),
                    self.tilelabel.currentTile)
                for x in range(min(rectEnd[0], self.rectStart[0]),
                               max(rectEnd[0], self.rectStart[0]) + 1):
                    for y in range(min(rectEnd[1], self.rectStart[1]),
                                   max(rectEnd[1], self.rectStart[1]) + 1):
                        self.undo[-1].append((map.ID, (x, y), oldtiles.pop(0)))
                return True
            elif self.isVisible() and self.hollowRectPaintingButton.isChecked(
            ) and self.rectStart is not None:
                rectEnd = (int(((mapPosition[0] - map.drawOffset[0]) /
                                self.tilelabel.tilex)),
                           int(((mapPosition[1] - map.drawOffset[1]) /
                                self.tilelabel.tiley)))
                if not map.tilePosExists(rectEnd):
                    self.rectStart = None
                    self.rectEnd = None
                    return
                self.undo.append([])
                self.undoButton.setEnabled(True)
                self.redo = []
                self.redoButton.setEnabled(False)
                if rectEnd[0] != self.rectStart[0]:
                    for x in range(
                            self.rectStart[0], rectEnd[0] +
                        (1 * (rectEnd[0] - self.rectStart[0])),
                            1 * (rectEnd[0] - self.rectStart[0])):
                        if rectEnd[1] != self.rectStart[1]:
                            #TODO: Less lazy and inefficient implementation for this case.
                            for y in range(
                                    self.rectStart[1], rectEnd[1] + 1 *
                                (rectEnd[1] - self.rectStart[1]),
                                    1 * (rectEnd[1] - self.rectStart[1])):
                                if x == self.rectStart[0] or x == rectEnd[
                                        0] or y == self.rectStart[
                                            1] or y == rectEnd[1]:
                                    if self.currentMap.tilePosExists((x, y)):
                                        oldtile = _sendTileUpdate(
                                            self.currentMap.ID, (x, y),
                                            self.tilelabel.currentTile)
                                        self.undo[-1].append(
                                            (map.ID, (x, y), oldtile))
                        else:
                            if self.currentMap.tilePosExists(
                                (x, self.rectStart[1])):
                                oldtile = _sendTileUpdate(
                                    self.currentMap.ID, (x, self.rectStart[1]),
                                    self.tilelabel.currentTile)
                                self.undo[-1].append(
                                    (map.ID, (x, self.rectStart[1]), oldtile))
                else:
                    if rectEnd[1] != self.rectStart[1]:
                        for y in range(
                                self.rectStart[1], rectEnd[1] + 1 *
                            (rectEnd[1] - self.rectStart[1]),
                                1 * (rectEnd[1], self.rectStart[1])):
                            if self.currentMap.tilePosExists(
                                (self.rectStart[0], y)):
                                oldtile = _sendTileUpdate(
                                    self.currentMap.ID, (self.rectStart[0], y),
                                    self.tilelabel.currentTile)
                                self.undo[-1].append(
                                    (map.ID, (self.rectStart[0], y), oldtile))
                    else:
                        if self.currentMap.tilePosExists(
                            (self.rectStart[0], self.rectStart[1])):
                            oldtile = _sendTileUpdate(
                                self.currentMap.ID,
                                (self.rectStart[0], self.rectStart[1]),
                                self.tilelabel.currentTile)
                            self.undo[-1].append(
                                (map.ID, (self.rectStart[0],
                                          self.rectStart[1]), oldtile))
                return True
        elif t == 6:
            if self.isVisible() and self.rectPaintingButton.isChecked(
            ) and self.copyData:
                try:
                    from libraries.rggViews import topmap, _sendTileUpdate
                except ImportError:
                    from rggViews import topmap, _sendTileUpdate
                if self.currentMap == None:
                    return
                mapPosition = getMapPosition((x, y))
                map = topmap(mapPosition)
                self.dragging = False
                if map == None or map != self.currentMap:
                    return
                rectEnd = (int(((mapPosition[0] - map.drawOffset[0]) /
                                self.tilelabel.tilex)),
                           int(((mapPosition[1] - map.drawOffset[1]) /
                                self.tilelabel.tiley)))
                if not map.tilePosExists(rectEnd):
                    self.rectStart = None
                    self.rectEnd = None
                    return
                topleft = (min(self.rectStart[0],
                               rectEnd[0]), min(self.rectStart[1], rectEnd[1]))
                bottomright = (max(self.rectStart[0], rectEnd[0]),
                               max(self.rectStart[1], rectEnd[1]))
                for row in range(1 + (bottomright[0] - topleft[0])):
                    for column in range(1 + (bottomright[1] - topleft[1])):
                        _sendTileUpdate(
                            self.currentMap.ID,
                            (topleft[0] + row, topleft[1] + column),
                            self.copyData[row % len(self.copyData)][
                                column %
                                len(self.copyData[row % len(self.copyData)])])
                return True
        elif t == 8:
            if self.isVisible() and not self.noPaintingButton.isChecked():
                try:
                    from libraries.rggViews import topmap
                except ImportError:
                    from rggViews import topmap
                if self.currentMap == None:
                    return
                mapPosition = getMapPosition((x, y))
                map = topmap(mapPosition)
                self.dragging = False
                if map == None or map != self.currentMap:
                    return
                rectEnd = (int(((mapPosition[0] - map.drawOffset[0]) /
                                self.tilelabel.tilex)),
                           int(((mapPosition[1] - map.drawOffset[1]) /
                                self.tilelabel.tiley)))
                if not map.tilePosExists(rectEnd):
                    self.rectStart = None
                    self.rectEnd = None
                    return
                topleft = (min(self.rectStart[0],
                               rectEnd[0]), min(self.rectStart[1], rectEnd[1]))
                bottomright = (max(self.rectStart[0], rectEnd[0]),
                               max(self.rectStart[1], rectEnd[1]))
                copypaste = []
                for row in range(1 + (bottomright[0] - topleft[0])):
                    copypaste.append([])
                    for column in range(1 + (bottomright[1] - topleft[1])):
                        copypaste[row].append(
                            map.getTile(
                                (topleft[0] + row, topleft[1] + column)))
                self.copyData = copypaste
                return True

    def mapChangedResponse(self, newMap):
        if newMap != None:
            self.currentMap = newMap
            self.tilepixmap = QPixmap()
            self.tilepixmap.load(newMap.tileset)
            if self.tilelabel is None:
                self.tilelabel = mapEditorLabel(newMap.tilesize,
                                                self.tilepixmap.width(),
                                                self.tilepixmap.height(), self)
            else:
                self.tilelabel = mapEditorLabel(newMap.tilesize,
                                                self.tilepixmap.width(),
                                                self.tilepixmap.height(), self,
                                                self.tilelabel.currentTile)
            self.tilelabel.setPixmap(self.tilepixmap)
            self.scrollarea.setWidget(self.tilelabel)
Ejemplo n.º 3
0
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

		""")