#!/usr/bin/env python #**************************************************************************** # output.py, provides non-GUI base classes for output of node info # # TreeLine, an information storage program # Copyright (C) 2005, Douglas W. Bell # # This is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License, Version 2. This program is # distributed in the hope that it will be useful, but WITTHOUT ANY WARRANTY. #**************************************************************************** from UserList import UserList import globalref class OutputItem: """Tree item rich text output class - stores text, level & height""" def __init__(self, textLines, level): self.textLines = textLines if globalref.docRef.spaceBetween: self.textLines.append(u'') self.level = level self.height = len(self.textLines) self.firstSibling = False self.lastSibling = False self.hasChildren = False self.prefix = '' self.suffix = '' def addBreaks(self): """Add
elements to format lines""" self.textLines = [line + u'
' for line in self.textLines] def addIndents(self, prevLevel, nextLevel): """Add
elements to format indented lines, return this level""" for num in range(self.level - prevLevel): self.textLines[0] = u'
%s' % self.textLines[0] for num in range(self.level - nextLevel): self.textLines[-1] = u'%s
' % self.textLines[-1] return self.level def equalPrefix(self, other): """Return True if prefix and suffix are equivalent""" return self.prefix.strip().upper() == other.prefix.strip().upper() and \ self.suffix.strip().upper() == other.suffix.strip().upper() def textList(self, withPrefix=False, withSuffix=False): """Return list of text lines, optionally with prefix/suffix""" textList = self.textLines[:] if not textList: textList = [''] if withPrefix: textList[0] = self.prefix + textList[0] if withSuffix: textList[-1] += self.suffix return textList class OutputGroup(list): """List of OutputItems - splits into segments & adds indentation""" def __init__(self, initList=[]): list.__init__(self, initList) def __getslice__(self, i, j): """Modified to copy object with slice""" return OutputGroup(list.__getslice__(self, i, j)) def addBreaks(self): """Add
elements to format lines""" for item in self: item.addBreaks() def addIndents(self, prevLevel=0): """Add
elements to indent items""" for num in range(len(self)): nextLevel = 0 if num + 1 < len(self): nextLevel = self[num + 1].level prevLevel = self[num].addIndents(prevLevel, nextLevel) def addPrefix(self): """Add prefix and suffix text to group items""" closeRqd = None for item, next in map(None, self, self[1:]): if item.prefix and item.textLines and closeRqd == None: item.textLines[0] = item.prefix + item.textLines[0] closeRqd = item.suffix if closeRqd != None and (item.lastSibling or item.hasChildren or \ next == None or \ not item.equalPrefix(next)): if item.textLines: item.textLines[-1] = item.textLines[-1] + closeRqd else: item.textLines = [closeRqd] closeRqd = None def joinPrefixItems(self): """Merges adjacent items with same prefix and level for printing""" newList = [] mergeList = OutputGroup() for item in self: if mergeList and (item.level != mergeList[0].level or \ not item.prefix or \ not item.equalPrefix(mergeList[0])): mergeList.mergeGroup() newList.append(mergeList[0]) mergeList[:] = [] mergeList.append(item) mergeList.mergeGroup() newList.append(mergeList[0]) self[:] = newList def mergeGroup(self): """Merge self (group of adjacent items)""" if len(self) < 2: return mainItem = self[0] for item in self[1:]: mainItem.textLines.extend(item.textLines) mainItem.height = reduce(lambda x,y: x+y, [item.height for item in \ self]) mainItem.lastSibling = self[-1].lastSibling mainItem.hasChildren = self[-1].hasChildren def splitColumns(self, numColumns): """Split output into even columns, return list with a group for each""" if numColumns < 2: return [self] if len(self) <= numColumns: return [OutputGroup([item]) for item in self] groupList = [] numEach = len(self) // numColumns for colNum in range(numColumns - 1): groupList.append(self[colNum * numEach : (colNum+1) * numEach]) groupList.append(self[(numColumns-1) * numEach : ]) numChanges = 1 while numChanges: numChanges = 0 for colNum in range(numColumns - 1): if groupList[colNum].totalHeight() > groupList[colNum+1].\ totalHeight() + groupList[colNum][-1].height: groupList[colNum+1].insert(0, groupList[colNum][-1]) groupList[colNum] = groupList[colNum][:-1] numChanges += 1 if groupList[colNum].totalHeight() + groupList[colNum+1][0].\ height <= groupList[colNum+1].totalHeight(): groupList[colNum].append(groupList[colNum+1][0]) groupList[colNum+1] = groupList[colNum+1][1:] numChanges += 1 return groupList def setHeights(self, textHtFunc, totalWidth, indent, fixList): """Set item heights after adding prefixes and applying fixes, textHtFunc is a callback to get height from text, fixList is a list of (regexp, replacement) tuples""" for prevItem, item in zip([None] + self, self): width = totalWidth - indent * item.level if not prevItem or item.level != prevItem.level or not item.prefix: textList = item.textList(True, True) item.height = self.textHeight(textList, textHtFunc, width, \ fixList) else: prevList = prevItem.textList(True, True) prevHeight = self.textHeight(prevList, textHtFunc, width, \ fixList) fullList = prevItem.textList(True, False) + \ item.textList(False, True) fullHeight = self.textHeight(fullList, textHtFunc, width, \ fixList) item.height = fullHeight - prevHeight def textHeight(self, textList, textHtFunc, width, fixList): """Return text height after joining and applying fixes""" text = u'
\n'.join(textList) for exp, repl in fixList: text = exp.sub(repl, text) return textHtFunc(text, width) def totalHeight(self): """Return the combined height of items in the group""" return reduce(lambda x, y: x+y, [item.height for item in self]) def splitPages(self, pageHeight, pageLevelBreaks=None, \ firstChildAdjust=0.2): """Split output by pageHeight, return list with a group for each, record levels for lines in pageLevelBreaks, firstChildAdjust is fraction of page left blank to avoid break between parent and 1st child""" groupList = [OutputGroup()] height = 0 for item in self: if height + item.height > pageHeight: height = 0 nextGroup = OutputGroup() if firstChildAdjust: lastGroup = groupList[-1][:] tmpItem = item deltaHeight = 0 while lastGroup and \ tmpItem.level == lastGroup[-1].level + 1: tmpItem = lastGroup.pop() nextGroup.insert(0, tmpItem) deltaHeight += tmpItem.height if nextGroup and \ deltaHeight < pageHeight * firstChildAdjust: groupList[-1] = lastGroup height = deltaHeight else: nextGroup = OutputGroup() groupList.append(nextGroup) height += item.height groupList[-1].append(item) if pageLevelBreaks != None: activeLevels = {} for pageList in groupList: for item in pageList: if item.firstSibling and item.level: activeLevels[item.level] = True if item.lastSibling and item.level: activeLevels[item.level] = False pageLevelBreaks.append([key for key in activeLevels.keys() \ if activeLevels[key]]) return groupList def getLines(self): """Return full list of text lines from this group""" lines = [] for item in self: lines.extend(item.textLines) return lines