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("<", "<").replace(">", ">") 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 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 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.""")