def __init__(self, blocks=(), pathGetter=None, custom=True): if pathGetter is None: pathGetter = self.getDefaultPath self.pathGetter = pathGetter self.datastores = [] self._addDatastoreWithBlocks(blocks) self._addDatastoreByPaths(getPath(trosnoth.data.blocks), getPath(user, 'blocks')) if custom: self._addDatastoreByPaths(getPath(trosnoth.data.blocks, 'custom'))
def __init__(self, blocks=(), pathGetter=None): if pathGetter is None: pathGetter = self.getDefaultPath self.pathGetter = pathGetter self.datastores = [] self._addDatastoreWithBlocks(blocks) self._addDatastoreByPaths(getPath(trosnoth.data.blocks), getPath(user, 'blocks')) self._addDatastoreByPaths(getPath(trosnoth.data.blocks, 'custom')) self.downloadPath = getPath(user, 'downloadedBlocks') self.downloads = self._addDatastoreByPaths(self.downloadPath)
def setTheme(self, themeName): ''' Sets the theme to the theme with the given name. ''' self.name = themeName self.paths = [data.getPath(data.user), data.getPath(data)] def insertPath(p): if os.path.exists(p): self.paths.insert(0, p) insertPath(data.getPath(data.themes, themeName)) insertPath(data.getPath(data.user, 'themes', themeName)) self.initFonts() self.initSounds() self.initColours()
def loadCreditsFileIfNeeded(self): if self.creditsFileLoaded: return filePath = getPath(startupMenu, 'credits3d.txt') with codecs.open(filePath, 'rU', encoding='utf-8') as f: creditsLines = f.read().splitlines() y = 0 for line in creditsLines: if line.startswith('!!'): font = self.app.fonts.creditsH1 line = line[len('!!'):] elif line.startswith('!'): font = self.app.fonts.creditsH2 line = line[len('!'):] else: font = self.app.fonts.creditsFont lineHeight = font.getPandaLineHeight(self.app) node = font.makeOnscreenText( self.app, text=line or ' ', # Prevents .getNumRows() bug fg=self.app.theme.colours.mainMenuColour, align=TextNode.ACenter, pos=(0, y - lineHeight), parent=self.aspect2d, wordwrap=28, ) y -= lineHeight * (node.textNode.getNumRows() + 0.2) self.creditsFileLoaded = True
def getManholeFactory(namespace, password): realm = manhole_ssh.TerminalRealm() # If we don't do this, the server will generate an exception when # you resize the SSH window def windowChanged(self, size): pass realm.sessionFactory.windowChanged = windowChanged def getManhole(_): return Manhole(namespace) realm.chainedProtocolFactory.protocolFactory = getManhole p = portal.Portal(realm) # Username/Password authentication passwordDB = checkers.InMemoryUsernamePasswordDatabaseDontUse() passwordDB.addUser('trosnoth', password) p.registerChecker(passwordDB) factory = manhole_ssh.ConchFactory(p) privatePath = getPath(user, 'authserver', 'manhole_rsa') if not os.path.isfile(privatePath): generateKeys(privatePath) if os.path.isfile(privatePath): factory.privateKeys[b'ssh-rsa'] = k = keys.Key.fromFile(privatePath) if not hasattr(k._keyObject, 'sign'): log.warn('SSH manhole may require newer version of cryptography!') publicPath = privatePath + '.pub' if os.path.isfile(publicPath): factory.publicKeys[b'ssh-rsa'] = keys.Key.fromFile(publicPath) return factory
def __init__(self, filename, channel): self.channel = channel if not pygame.mixer.get_init(): return self.sound = pygame.mixer.Sound(getPath(sound, filename)) self.channelVolume = 1
def playMusic(self): if not pygame.mixer.get_init(): return curFile = self.filenames[self.index] pygame.mixer.music.load(getPath(music, curFile)) pygame.mixer.music.play(0) self.setVolume(self.volume)
def initColours(self): colourPath = self.getPath('config', 'colours.cfg') colourData = self._getColourData(colourPath) defaultColours = self._getColourData(data.getPath(data)) for colourName, colour in defaultColours.iteritems(): if colourName in colourData: colour = colourData[colourName] setattr(self.colours, colourName, colour)
def fromFile(layoutDatabase, filename): mapDir = getPath(user, 'maps') filename = os.path.join(mapDir, filename) if not os.path.exists(filename): raise IOError("Map could not be loaded; it doesn't exist!") with open(filename, 'r') as f: layoutStr = f.read() return MapLayout.fromDumpedState(layoutDatabase, unrepr(layoutStr))
def updateKeyMapping(self): # Set up the keyboard mapping. try: # Try to load keyboard mappings from the user's personal settings. config = open(getPath(user, 'keymap'), 'rU').read() self.keyMapping.load(config) if self.runningPlayerInterface: self.runningPlayerInterface.keyMappingUpdated() except IOError: pass
def __init__(self): try: pygame.mixer.init(44100) except: log.error('Could not initialise audio.') self.index = 0 self.volume = 100 self.filenames = [] for f in os.listdir(getPath(music)): if f.endswith('.ogg'): self.addMusicFile(f)
def __init__(self, app): self.app = app self.index = 0 self.playing = False self.interval = Sequence() for f in os.listdir(getPath(music)): if f.endswith('.ogg'): self.interval.append(SoundInterval( self.app.panda.loader.loadMusic(getPandaPath(music, f)))) self.interval.loop() self.interval.pause()
def save(self): ''' Writes the settings to file in a JSON-like format. ''' # Write to file fn = getPath(user, self.dataFileName) f = open(fn, 'w') data = {} for attr, key, default in self.attributes: data[key] = getattr(self, attr) f.write(repr(data)) f.close()
def populateInputs(self): # Set up the keyboard mapping. self.keyMapping = keyboard.KeyboardMapping(keymap.default_game_keys) try: # Try to load keyboard mappings from the user's personal settings. config = open(getPath(user, 'keymap'), 'rU').read() self.keyMapping.load(config) except IOError: pass # Refresh the display. self.refreshInputs()
def showOpenDialog(self, sender): root = Tk() root.withdraw() tksupport.install(root) filename = askopenfilename( defaultextension='.trosrepl', filetypes=[ ('Trosnoth replay', '*.trosrepl'), ], initialdir=getPath(user, replayDir), title='Select replay', ) if filename: self.onReplay.execute(filename)
def __init__(self, app, colour, onCancel=None, speed=None, loop=True, startOff=False, backText='back to main menu', highlight=(255, 255, 255)): super(CreditsScreen, self).__init__(app) self.colour = colour self.highlight = highlight self.onCancel = Event() if onCancel is not None: self.onCancel.addListener(onCancel) f = open(getPath(startupMenu, 'credits.txt'), 'rU', encoding='utf-8') area = ScaledArea(50, 130, 900, 540) fonts = { 'body': self.app.fonts.creditsFont, 'h1': self.app.fonts.creditsH1, 'h2': self.app.fonts.creditsH2 } text = f.read() if not startOff: # Make at least some room on top and bottom text = '\n' * 3 + text + '\n' * 2 self.credits = scrollingText.ScrollingText(self.app, area, text, self.colour, fonts, textAlign='middle', loop=loop, startOff=startOff) self.credits.setShadowColour((192, 192, 192)) self.credits.setAutoscroll(True) if speed is None: speed = 80 self.credits.setSpeed(ScalingSpeed(speed)) self.credits.setBorder(False) self.backText = backText cancelButton = self.button(backText, self.onCancel.execute, (-50, -30), 'bottomright') self.elements = [self.credits, cancelButton]
def migrate_old_user_info(apps, schema_editor): User = apps.get_registered_model('auth', 'User') TrosnothUser = apps.get_model('trosnoth', 'TrosnothUser') AchievementProgress = apps.get_model('trosnoth', 'AchievementProgress') dataPath = data.getPath(data.user, 'authserver') path = os.path.join(dataPath, 'accounts') if not os.path.isdir(path): return for username in os.listdir(path): userpath = os.path.join(path, username) user = User(username=username, email='') user.save() with open(os.path.join(userpath, 'nick'), 'rb') as f: nick = f.read().decode() with open(os.path.join(userpath, 'stats'), 'rU') as f: stats = json.load(f) with open(os.path.join(userpath, 'password'), 'rb') as f: passwordHash = f.read() lastSeen = datetime.datetime.fromtimestamp(stats.get('lastSeen')) trosnothUser = TrosnothUser(user=user, nick=nick, lastSeen=lastSeen, oldPasswordHash=passwordHash) trosnothUser.save() achievementPath = os.path.join(userpath, 'achievements') if os.path.exists(achievementPath): with open(os.path.join(userpath, 'achievements'), 'rU') as f: achievements = json.load(f) for achId, achData in achievements.items(): progress = None extra = '' if 'progress' in achData: if isinstance(achData['progress'], int): progress = achData['progress'] else: extra = json.dumps(achData['progress']) record = AchievementProgress(user=trosnothUser, achievementId=achId, unlocked=achData.get( 'unlocked', False), progress=progress, data=extra) record.save()
def main(): args = _getParser().parse_args() token = input().encode('ascii') logPrefix = args.logPrefix + '-bots' if args.logPrefix else 'bots' if os.name == 'nt': logpath = data.getPath(data.user, 'authserver', 'logs') data.makeDirs(logpath) initLogging( logFile=os.path.join(logpath, 'log{}.txt'.format(logPrefix))) else: initLogging(prefix='[{}] '.format(logPrefix)) reactor.callWhenRunning(_twisted_main, args, token) reactor.run()
def saveMap(self, filename, force=False): ''' Saves the current map layout to the .trosnoth/maps directory. @param filename: The filename of the map (with extension) @param force: Overwrites the file if it already exists ''' mapDir = getPath(user, 'maps') makeDirs(mapDir) filename = os.path.join(mapDir, filename) if os.path.exists(filename) and not force: return 'File already exists (use "force" parameter to overwrite)' with open(filename, 'w') as f: f.write(repr(self.getWorld().layout.dumpState())) return 'Map saved to %s' % (filename,)
def getFilename(alias, directory, ext, multipleFiles = True): # Figure out the filename to use for the main file gamePath = getPath(user, directory) makeDirs(gamePath) copyCount = 0 succeeded = False if multipleFiles: while not succeeded: filename = '%s (%s)%s' % (alias, str(copyCount), ext) filePath = os.path.join(gamePath, filename) succeeded = not os.path.exists(filePath) copyCount += 1 else: filename = '%s%s' % (alias, ext) filePath = os.path.join(gamePath, filename) return filePath
def __init__(self, dataPath=None, manholePassword=None): if dataPath is None: dataPath = getPath(data.user, 'authserver') makeDirs(dataPath) self.dataPath = dataPath self.manholePassword = None self.authManager = self.authManagerClass(dataPath) self.pubKey, self.privKey = self.loadKeys() self.arenaProxies = {} self.arenaAMPListener = None self.adminTokens = set() self.onArenaStarting = Event(['proxy']) self.onArenaStopped = Event(['proxy']) AuthenticationFactory.instance = self
def revertToDefault(self): self.actions = {} # Load the old file if it exists oldFilePath = getPath(user, 'keymap') try: with open(oldFilePath, 'rU') as f: lines = f.read().splitlines() for line in lines: bits = line.split(':', 1) if len(bits) == 2 and bits[0].isdigit(): key = pygameToPandaKey(int(bits[0])) self.actions.setdefault(key, bits[1]) except IOError: pass for key, action in self.getDefaultKeyMap(): self.actions.setdefault(key, action)
def saveSettings(self): # Perform the save. open(getPath(user, 'keymap'), 'w').write(self.keyMapping.save()) emptyBoxes = [] for box in self.inputLookup.itervalues(): if box.value is None: emptyBoxes.append(box) if len(emptyBoxes) > 0: self.populateInputs() for box in emptyBoxes: box.backColour = self.app.theme.colours.invalidDataColour self.incorrectInput('Warning: some actions have no key', (192, 0, 0)) else: self.mainMenu()
def showLoadReplayFileDialog(self): root = Tk() root.withdraw() tksupport.install(root) filename = askopenfilename( defaultextension='.trosrepl', filetypes=[ ('Trosnoth replay', '*.trosrepl'), ], initialdir=getPath(user, replayDir), title='Select replay', ) if filename: try: self.app.connector.openReplay(filename) except ReplayFileError: self.showReplayFileError() except: log.exception('Error while opening replay file') self.showReplayFileError()
def refresh(self): # Get a list of files with the name '*.tros' logDir = getPath(user, gameDir) makeDirs(logDir) self.games = [] for fname in os.listdir(logDir): if os.path.splitext(fname)[1] != gameExt: continue try: game = RecordedGame(os.path.join(logDir, fname)) except RecordedGameException: continue if game.recordedGameVersion != recordedGameVersion: continue self.games.append(game) self.games.sort(key=lambda game: (-game.unixTimestamp, game.filename))
def __init__(self, dataPath=None): if dataPath is None: dataPath = getPath(data.user, 'authserver') makeDirs(dataPath) self.dataPath = dataPath self.onPrimaryGameChanged = Event() self.primaryGameId = None self.authManager = self.authManagerClass(dataPath) self.pubKey, self.privKey = self.loadKeys() self.servers = {} # Game id -> game. self.nextId = 0 self.registeredGames = [] self.gameStats = {} self.layoutDatabase = LayoutDatabase() self.notifier = self.settings.createNotificationClient() if self.notifier is not None: self.notifier.startService()
def _getSettingsFilename(self): ''' Returns the path to the file that should be used to save and load these settings. May be overridden by subclasses. ''' return getPath(user, self.dataFileName)
def generateHtml(htmlPath, statPath): def plural(value): if value == 1: return '' else: return 's' def add(value, statName=None, altText=None, spacing=True, className=None): if altText is not None: points = POINT_VALUES[altText] * value if type(points) == float: points = '%2.2f' % points altText = ' title="%s point%s\"' % (points, plural(points)) else: altText = '' if className is not None: classText = ' class="%s"' % (className) else: classText = '' if type(value) == float: value = '%2.2f' % value if statName is None: html.append('\t\t\t\t<td%s%s>%s</td>' % (altText, classText, value)) else: nbsp = '' if spacing is False: pluralStr = '' else: nbsp = ' ' pluralStr = plural(value) html.append('\t\t\t\t<td%s>%s%s%s%s</td>' % (altText, value, nbsp, statName, pluralStr)) def addList(title, data): html.append('\t\t\t\t\t<li><b>%s:</b> ' % title) if len(data) == 0: html.append('\t\t\t\t\t\tNone') else: data = sorted(list(data.items()), key=operator.itemgetter(1), reverse=True) string = [] for details in data: string.append('%s (%d)' % details) html.append('\t\t\t\t\t\t' + ', '.join(string)) html.append('\t\t\t\t\t</li>') def accuracy(shotsHit, shotsFired): try: return ((shotsHit**2) / (shotsFired + 0.0)) * 30 except ZeroDivisionError: return 0 from trosnoth.gamerecording.gamerecorder import statDir makeDirs(statDir) if statPath == '': files = os.listdir(statDir) else: files = [statPath] playerStats = {} statNames = [ 'aliveStreak', 'deaths', 'killStreak', 'kills', 'roundsLost', 'roundsWon', 'shotsFired', 'shotsHit', 'coinsEarned', 'coinsUsed', 'coinsWasted', 'tagStreak', 'timeAlive', 'timeDead', 'zoneAssists', 'zoneTags' ] statEnemies = ['playerDeaths', 'playerKills', 'upgradesUsed'] leaders = list( map(str.lower, list(leaderAchievements.keys()) + list(additionalLeaders.keys()))) tableHeaders = [ [ '#', 'Nick', 'Kills', 'Deaths', 'KDR', 'Zone Tags', 'Shots Fired', 'Shots Hit', 'Accuracy', 'Coins Used', 'Killed the most:', 'Died the most to:', 'Points' ], [ '#', 'Nick', 'Coins Earned', 'Coins Used', 'Coins Wasted', 'Favourite Upgrade', 'Time Alive', 'Time Dead', 'ADR', 'Longest Life', 'Points' ], [ '#', 'Nick', 'Kills', 'Kill Streak', 'Zone Tags', 'Zone Assists', 'Tag Streak', 'Points' ], ] tableNames = ['General Overview', 'Coins and Time', 'Kills and Tags'] for x in range(0, len(tableNames)): style = '' if x == 0: style = " style='color: black;'" tableNames[x] = ('<span class="name topLink" id="link%s" ' 'onClick="navigate(\'%s\', %d)"%s>%s</span>' % (x, x, len(tableHeaders[x]), style, tableNames[x])) navigation = ' – '.join(tableNames) html = [] fileMatrix = {} for filename in files: if filename[-9:] != '.trosstat': filename = filename + '.trosstat' statLocation = os.path.join(statPath, filename) try: statFile = open(statLocation) except IOError: raise Exception("'%s' does not exist!" % filename) loadedStats = simplejson.load(statFile) for nick in loadedStats['players']: if nick not in playerStats: playerStats[nick] = loadedStats['players'][nick] fileMatrix[nick] = [filename] else: for stat in statNames: playerStats[nick][stat] += ( loadedStats['players'][nick][stat]) for stat in statEnemies: for enemy in loadedStats['players'][nick][stat]: if enemy not in playerStats[nick][stat]: playerStats[nick][stat][enemy] = 0 playerStats[nick][stat][enemy] += ( loadedStats['players'][nick][stat][enemy]) fileMatrix[nick].append(filename) ranking = {} allData = {} for nick in playerStats: data = playerStats[nick] try: data['accuracy'] = (100.0 * data['shotsHit']) / data['shotsFired'] except ZeroDivisionError: data['accuracy'] = 0 for stat in statEnemies: data[stat + 'Full'] = data[stat].copy() highest = 0 highestName = '----' names = data[stat] for k, v in list(names.items()): if v > highest: highest = v highestName = k if highest == 0: data[stat] = highestName else: data[stat] = '%s (%s)' % (highestName, highest) data['score'] = 0 for stat, value in list(POINT_VALUES.items()): points = data[stat] * value data['score'] += points try: data['kdr'] = '%2.2f' % (float(data['kills']) / data['deaths']) except ZeroDivisionError: data['kdr'] = '----' try: data['adr'] = '%2.2f' % (float(data['timeAlive']) / data['timeDead']) except ZeroDivisionError: data['adr'] = '----' ranking[nick] = data['score'] allData[nick] = data rankingList = sorted(list(ranking.items()), key=operator.itemgetter(1), reverse=True) ranking = {} rankCount = 0 html.append("\t\t<table class='ladder'>") for count in range(0, len(tableNames)): style = '' if count != 0: style = " style='display: none;'" html.append("\t\t\t<tr class='allRows group%s'%s>" % (count, style)) for caption in tableHeaders[count]: html.append('\t\t\t\t<th>%s</th>' % caption) html.append('\t\t\t</tr>') teamNames = ('Blue', 'Red') if 'winningTeamId' in loadedStats: winningTeamId = loadedStats['winningTeamId'] if winningTeamId == b'A': winText = '%s Team won' % (teamNames[0], ) colour = 'navy' elif winningTeamId == b'B': winText = '%s Team won' % (teamNames[1], ) colour = 'maroon' else: winText = 'Game was a draw' colour = 'green' else: winText = 'Game was not finished' colour = 'gray' html.append('<p class="wintext" style="color: %s;">%s</p>' % (colour, winText)) for pair in rankingList: nick = pair[0] rankCount += 1 rankStr = str(rankCount) classy = '' if stripPunctuation(nick).lower() in leaders: classy = ' leader' rankCount -= 1 rankStr = '--' data = allData[nick] if data['bot']: classy = ' bot' rankCount -= 1 rankStr = 'B' if sys.version_info.major == 2: nickId = ''.join('{:02x}'.format(ord(c)) for c in 'abc'.encode('utf-8')) else: nickId = ''.join('{:02x}'.format(c for c in 'abc'.encode('utf-8'))) for count in range(0, len(tableNames)): style = '' if count != 0: style = " style='display: none;'" html.append("\t\t\t<tr class='allRows group%s%s'%s>" % (count, classy, style)) if (data['team'] == b'A'): bgColour = 'blueteam' elif (data['team'] == b'B'): bgColour = 'redteam' elif (data['team'] == NEUTRAL_TEAM_ID): bgColour = 'rogue' add('<strong>%s</strong>' % rankStr, className=bgColour) add('<span class="name" onClick="toggle(\'details-%s\')">%s</span>' % (nickId, nick)) if count == 0: add(data['kills'], 'kill', 'kills') add(data['deaths'], 'death', 'deaths') add(data['kdr']) add(data['zoneTags'], 'tag', 'zoneTags') add(data['shotsFired'], 'shot') add(data['shotsHit'], 'shot') add(data['accuracy'], '%', 'accuracy', False) add(data['coinsUsed'], 'coin', 'coinsUsed') add(data['playerKills']) add(data['playerDeaths']) elif count == 1: add(data['coinsEarned'], 'coin') add(data['coinsUsed'], 'coin', 'coinsUsed') add(data['coinsWasted'], 'coin') add(data['upgradesUsed']) add(int(data['timeAlive']), 'second') add(int(data['timeDead']), 'second') add(data['adr']) add(int(data['aliveStreak']), 'second') elif count == 2: add(data['kills'], 'kill', 'kills') add(data['killStreak'], 'kill') add(data['zoneTags'], 'tag', 'zoneTags') add(data['zoneAssists'], 'assist', 'zoneAssists') add(data['tagStreak'], 'zone') elif count == 3: add(data['shotsFired'], 'shot') add(data['shotsHit'], 'shot') add(data['accuracy'], '%', spacing=False) old = data['accuracy'] * 20 new = accuracy(data['shotsHit'], data['shotsFired']) add(old, 'point') add(new, 'point') add(new - old) add('<strong>%2.2f</strong>' % data['score']) html.append('\t\t\t</tr>') if count == len(tableNames) - 1: html.append( "\t\t\t<tr id='details-%s' style='display: none;'>" % nickId) html.append("\t\t\t\t<td colspan='%d' class='details'" "style='text-align: left;'>" % len(tableHeaders[0])) html.append('\t\t\t\t\t<ul>') addList('Players killed', data['playerKillsFull']) addList('Players died to', data['playerDeathsFull']) addList('Upgrades used', data['upgradesUsedFull']) html.append('\t\t\t\t\t</ul>') html.append('\t\t\t\t</td>') html.append('\t\t\t</tr>') html.append('\t\t</table>') html = '\n' + '\n'.join(html) + '\n' baseHTML = open(getPath(statGeneration, 'statGenerationBase.htm'), 'r').read() html = baseHTML.replace('[[TABLE]]', html) html = html.replace('[[NAVIGATION]]', navigation) with open(htmlPath, 'w') as f: f.write(html)
def queueNext(self): # Queue the next one. self.index = (self.index + 1) % len(self.filenames) curFile = self.filenames[self.index] pygame.mixer.music.queue(getPath(music, curFile))
def populateList(self): defaultTheme = { "name": "Default Theme", "filename": "default", "author": "Trosnoth Team", "content": None, "source": "internal" } # Clear out the sidebar self.themeNameText.setText('') self.themeAuthorText.setText('') for element in self.contents: element.setText('') self.useThemeButton.setText('') self.restartButton.setText('') self.listHeaderText.setText('available themes:') self.themeList.index = -1 userThemeDir = getPath(user, 'themes') internalThemeDir = getPath(themes) makeDirs(userThemeDir) themeList = [] # Get a list of internal themes for dirName in os.listdir(internalThemeDir): if os.path.isdir(os.path.join(internalThemeDir, dirName)): themeList.append("i/%s" % dirName) # Get a list of user-defined themes for dirName in os.listdir(userThemeDir): if os.path.isdir(os.path.join(userThemeDir, dirName)): # Internal themes overrule user-defined themes if "i/" + dirName not in themeList and dirName != "default": themeList.append("u/%s" % dirName) # Assume all themes are valid for now validThemes = themeList[:] self.themeInfo = {} for themeName in themeList: themeInfo = {"content": {}} if themeName.startswith("i/"): themeInfo['source'] = 'internal' directory = internalThemeDir else: themeInfo['source'] = 'user-defined' directory = userThemeDir themeNameList = themeName themeName = themeName[2:] themeInfo['filename'] = themeName[2:] anyContent = False for contentType in list(self.contentTypes.keys()): if themeInfo['source'] == 'internal': contentDir = os.path.join(directory, themeName, contentType) else: contentDir = os.path.join(directory, themeName, contentType) if not os.path.isdir(contentDir): continue else: fileCount = len([ f for f in os.listdir(contentDir) if os.path.isfile(os.path.join(contentDir, f)) ]) if fileCount > 0: anyContent = True themeInfo["content"][contentType] = fileCount if not anyContent: validThemes.remove(themeNameList) continue infoFile = os.path.join(directory, themeName, "info.txt") if os.path.isfile(infoFile): infoFile = open(infoFile) infoContents = infoFile.readlines() else: infoContents = [] if len(infoContents) >= 2: themeInfo["author"] = infoContents[1].strip() else: themeInfo["author"] = None if len(infoContents) >= 1: themeInfo["name"] = infoContents[0].strip() else: themeInfo["name"] = themeName self.themeInfo[themeName] = themeInfo self.themeInfo["default"] = defaultTheme # Sort the themes alphabetically items = [(v['filename'], n) for n, v in self.themeInfo.items()] items.sort() items = [n for v, n in items] self.themeList.setItems(items) if len(self.themeInfo) == 1: self.listHeaderText.setText("1 available theme:") self.themeList.index = 0 self.updateSidebar(0) else: self.listHeaderText.setText("%d available themes:" % len(self.themeInfo))