def __init__(self, treeViewRef, allActions, parent=None): """Initialize the list view. Arguments: treeViewRef -- a ref to the tree view for data allActions -- a dictionary of control actions for popup menus parent -- the parent main window """ super().__init__(parent) self.structure = treeViewRef.model().treeStructure self.selectionModel = treeViewRef.selectionModel() self.treeModel = treeViewRef.model() self.allActions = allActions self.menu = None self.noMouseSelectMode = False self.mouseFocusNoEditMode = False self.prevSelSpot = None # temp, to check for edit at mouse release self.drivingSelectionChange = False self.conditionalFilter = None self.messageLabel = None self.filterWhat = miscdialogs.FindScope.fullData self.filterHow = miscdialogs.FindType.keyWords self.filterStr = '' self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setItemDelegate(TreeEditDelegate(self)) # use mouse event for editing to avoid with multiple select self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.itemSelectionChanged.connect(self.updateSelectionModel) self.itemChanged.connect(self.changeTitle) treeFont = QTextDocument().defaultFont() treeFontName = globalref.miscOptions['TreeFont'] if treeFontName: treeFont.fromString(treeFontName) self.setFont(treeFont)
def updateFonts(self): """Update custom fonts in views. """ treeFont = QTextDocument().defaultFont() treeFontName = globalref.miscOptions['TreeFont'] if treeFontName: treeFont.fromString(treeFontName) self.treeView.setFont(treeFont) self.treeView.updateTreeGenOptions() if self.treeFilterView: self.treeFilterView.setFont(treeFont) ouputFont = QTextDocument().defaultFont() ouputFontName = globalref.miscOptions['OutputFont'] if ouputFontName: ouputFont.fromString(ouputFontName) editorFont = QTextDocument().defaultFont() editorFontName = globalref.miscOptions['EditorFont'] if editorFontName: editorFont.fromString(editorFontName) for i in range(2): self.outputSplitter.widget(i).setFont(ouputFont) self.editorSplitter.widget(i).setFont(editorFont) self.titleSplitter.widget(i).setFont(editorFont)
class PrintData: """Class to handle printing of tree output data. Stores print data and main printing functions. """ def __init__(self, localControl): """Initialize the print data. Arguments: localControl -- a reference to the parent local control """ self.localControl = localControl self.outputGroup = None self.printWhat = PrintScope.entireTree self.includeRoot = True self.openOnly = False self.printer = QPrinter(QPrinter.HighResolution) self.pageLayout = self.printer.pageLayout() self.setDefaults() self.adjustSpacing() def setDefaults(self): """Set all paparmeters saved in TreeLine files to default values. """ self.drawLines = True self.widowControl = True self.indentFactor = 2.0 self.pageLayout.setUnits(QPageLayout.Inch) self.pageLayout.setPageSize(QPageSize(QPageSize.Letter)) self.pageLayout.setOrientation(QPageLayout.Portrait) self.pageLayout.setMargins(QMarginsF(*(_defaultMargin, ) * 4)) self.headerMargin = _defaultHeaderPos self.footerMargin = _defaultHeaderPos self.numColumns = 1 self.columnSpacing = _defaultColumnSpace self.headerText = '' self.footerText = '' self.useDefaultFont = True self.setDefaultFont() def setDefaultFont(self): """Set the default font initially and based on an output font change. """ self.defaultFont = QTextDocument().defaultFont() fontName = globalref.miscOptions['OutputFont'] if fontName: self.defaultFont.fromString(fontName) if self.useDefaultFont: self.mainFont = self.defaultFont def adjustSpacing(self): """Adjust line spacing & indent size based on font & indent factor. """ self.lineSpacing = QFontMetrics(self.mainFont, self.printer).lineSpacing() self.indentSize = self.indentFactor * self.lineSpacing def fileData(self): """Return a dictionary of non-default settings for storage. """ data = {} if not self.drawLines: data['printlines'] = False if not self.widowControl: data['printwidowcontrol'] = False if self.indentFactor != 2.0: data['printindentfactor'] = self.indentFactor pageSizeId = self.pageLayout.pageSize().id() if pageSizeId == QPageSize.Custom: paperWidth, paperHeight = self.roundedPaperSize() data['printpaperwidth'] = paperWidth data['printpaperheight'] = paperHeight elif pageSizeId != QPageSize.Letter: data['printpapersize'] = self.paperSizeName(pageSizeId) if self.pageLayout.orientation() != QPageLayout.Portrait: data['printportrait'] = False if self.roundedMargins() != (_defaultMargin, ) * 4: data['printmargins'] = list(self.roundedMargins()) if self.headerMargin != _defaultHeaderPos: data['printheadermargin'] = self.headerMargin if self.footerMargin != _defaultHeaderPos: data['printfootermargin'] = self.footerMargin if self.numColumns > 1: data['printnumcolumns'] = self.numColumns if self.columnSpacing != _defaultColumnSpace: data['printcolumnspace'] = self.columnSpacing if self.headerText: data['printheadertext'] = self.headerText if self.footerText: data['printfootertext'] = self.footerText if not self.useDefaultFont: data['printfont'] = self.mainFont.toString() return data def readData(self, data): """Restore saved settings from a dictionary. Arguments: data -- a dictionary of stored non-default settings """ self.setDefaults() # necessary for undo/redo self.drawLines = data.get('printlines', True) self.widowControl = data.get('printwidowcontrol', True) self.indentFactor = data.get('printindentfactor', 2.0) if 'printpapersize' in data: self.pageLayout.setPageSize( QPageSize(getattr(QPageSize, data['printpapersize']))) self.pageLayout.setMargins(QMarginsF(*(_defaultMargin, ) * 4)) if 'printpaperwidth' in data and 'printpaperheight' in data: width = data['printpaperwidth'] height = data['printpaperheight'] self.pageLayout.setPageSize( QPageSize(QSizeF(width, height), QPageSize.Inch)) self.pageLayout.setMargins(QMarginsF(*(_defaultMargin, ) * 4)) if not data.get('printportrait', True): self.pageLayout.setOrientation(QPageLayout.Landscape) if 'printmargins' in data: margins = data['printmargins'] self.pageLayout.setMargins(QMarginsF(*margins)) self.headerMargin = data.get('printheadermargin', _defaultHeaderPos) self.footerMargin = data.get('printfootermargin', _defaultHeaderPos) self.numColumns = data.get('printnumcolumns', 1) self.columnSpacing = data.get('printcolumnspace', _defaultColumnSpace) self.headerText = data.get('printheadertext', '') self.footerText = data.get('printfootertext', '') if 'printfont' in data: self.useDefaultFont = False self.mainFont.fromString(data['printfont']) self.adjustSpacing() def roundedMargins(self): """Return a tuple of rounded page margins in inches. Rounds to nearest .01" to avoid Qt unit conversion artifacts. """ margins = self.pageLayout.margins(QPageLayout.Inch) return tuple( round(margin, 2) for margin in (margins.left(), margins.top(), margins.right(), margins.bottom())) def roundedPaperSize(self): """Return a tuple of rounded paper width and height. Rounds to nearest .01" to avoid Qt unit conversion artifacts. """ size = self.pageLayout.fullRect(QPageLayout.Inch) return (round(size.width(), 2), round(size.height(), 2)) def paperSizeName(self, sizeId=None): """Return a QPageSize attribute name matching the paper size ID. Arguments: sizeId -- the Qt size ID, if None, use current size """ if sizeId == None: sizeId = self.pageLayout.pageSize().id() matches = [] for name, num in vars(QPageSize).items(): if num == sizeId: matches.append(name) if not matches: return 'Custom' if len(matches) > 1: text = QPageSize(sizeId).name().split(None, 1)[0] for name in matches: if name == text: return name return matches[0] def setupData(self): """Load data to be printed and set page info. """ if self.printWhat == PrintScope.entireTree: selSpots = self.localControl.structure.rootSpots() else: selSpots = ( self.localControl.currentSelectionModel().selectedSpots()) if not selSpots: selSpots = self.localControl.structure.rootSpots() self.outputGroup = treeoutput.OutputGroup( selSpots, self.includeRoot, self.printWhat != PrintScope.selectNode, self.openOnly) self.paginate() def paginate(self): """Define the pages and locations of output items and set page range. """ pageNum = 1 columnNum = 0 pagePos = 0 itemSplit = False self.checkPageLayout() heightAvail = (self.pageLayout.paintRect().height() * self.printer.logicalDpiY()) columnSpacing = int(self.columnSpacing * self.printer.logicalDpiX()) widthAvail = ( (self.pageLayout.paintRect().width() * self.printer.logicalDpiX() - columnSpacing * (self.numColumns - 1)) // self.numColumns) newGroup = treeoutput.OutputGroup([]) while self.outputGroup: item = self.outputGroup.pop(0) widthRemain = widthAvail - item.level * self.indentSize if pagePos != 0 and (newGroup[-1].addSpace or item.addSpace): pagePos += self.lineSpacing if item.siblingPrefix: siblings = treeoutput.OutputGroup([]) siblings.append(item) while True: item = siblings.combineLines() item.setDocHeight(self.printer, widthRemain, self.mainFont, True) if pagePos + item.height > heightAvail: self.outputGroup.insert(0, siblings.pop()) item = (siblings.combineLines() if siblings else None) break if (self.outputGroup and item.level == self.outputGroup[0].level and item.equalPrefix(self.outputGroup[0])): siblings.append(self.outputGroup.pop(0)) else: break if item: item.setDocHeight(self.printer, widthRemain, self.mainFont, True) if item.height > heightAvail and not itemSplit: item, newItem = item.splitDocHeight( heightAvail - pagePos, heightAvail, self.printer, widthRemain, self.mainFont) if newItem: self.outputGroup.insert(0, newItem) itemSplit = True if item and (pagePos + item.height <= heightAvail or pagePos == 0): item.pageNum = pageNum item.columnNum = columnNum item.pagePos = pagePos newGroup.append(item) pagePos += item.height else: if columnNum + 1 < self.numColumns: columnNum += 1 else: pageNum += 1 columnNum = 0 pagePos = 0 itemSplit = False if item: self.outputGroup.insert(0, item) if self.widowControl and not item.siblingPrefix: moveItems = [] moveHeight = 0 level = item.level while (newGroup and not newGroup[-1].siblingPrefix and newGroup[-1].level == level - 1 and ((newGroup[-1].pageNum == pageNum - 1 and newGroup[-1].columnNum == columnNum) or (newGroup[-1].pageNum == pageNum and newGroup[-1].columnNum == columnNum - 1))): moveItems.insert(0, newGroup.pop()) moveHeight += moveItems[0].height level -= 1 if (moveItems and newGroup and moveHeight < (heightAvail // 5)): self.outputGroup[0:0] = moveItems else: newGroup.extend(moveItems) self.outputGroup = newGroup self.outputGroup.loadFamilyRefs() self.printer.setFromTo(1, pageNum) def checkPageLayout(self): """Check and set the page layout on the current printer. Verify that the layout settings match the printer, adjust if required. """ if not self.printer.setPageLayout(self.pageLayout): tempPrinter = QPrinter() tempPageLayout = tempPrinter.pageLayout() tempPageLayout.setUnits(QPageLayout.Inch) pageSizeIssue = False defaultPageSize = tempPageLayout.pageSize() tempPageLayout.setPageSize(self.pageLayout.pageSize()) if not tempPrinter.setPageLayout(tempPageLayout): pageSizeIssue = True tempPageLayout.setPageSize(defaultPageSize) marginIssue = not (tempPageLayout.setMargins( self.pageLayout.margins()) and tempPrinter.setPageLayout(tempPageLayout)) if marginIssue: margin = 0.1 while True: if (tempPageLayout.setMargins(QMarginsF(*(margin, ) * 4)) and tempPrinter.setPageLayout(tempPageLayout)): break margin += 0.1 newMargins = [] for oldMargin in self.roundedMargins(): newMargins.append( oldMargin if oldMargin >= margin else margin) tempPageLayout.setMargins(QMarginsF(*newMargins)) tempPageLayout.setOrientation(self.pageLayout.orientation()) self.printer.setPageLayout(tempPageLayout) if not pageSizeIssue and not marginIssue: return if pageSizeIssue and marginIssue: msg = _('Warning: Page size and margin settings unsupported ' 'on current printer.\nSave page adjustments?') elif pageSizeIssue: msg = _('Warning: Page size setting unsupported ' 'on current printer.\nSave adjustment?') else: msg = _('Warning: Margin settings unsupported ' 'on current printer.\nSave adjustments?') ans = QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if ans == QMessageBox.Yes: self.pageLayout = tempPageLayout def paintData(self, printer): """Paint data to be printed to the printer. """ pageNum = 1 try: maxPageNum = self.outputGroup[-1].pageNum except IndexError: # printing empty branch maxPageNum = 1 if self.printer.printRange() != QPrinter.AllPages: pageNum = self.printer.fromPage() maxPageNum = self.printer.toPage() painter = QPainter() if not painter.begin(self.printer): QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', _('Error initializing printer')) QApplication.setOverrideCursor(Qt.WaitCursor) while True: self.paintPage(pageNum, painter) if pageNum == maxPageNum: QApplication.restoreOverrideCursor() return pageNum += 1 self.printer.newPage() def paintPage(self, pageNum, painter): """Paint data for the given page to the printer. Arguments: pageNum -- the page number to be printed painter -- the painter for this print job """ paintContext = QAbstractTextDocumentLayout.PaintContext() # set context text color to black to wrok with dark app themes paintContext.palette = QPalette() paintContext.palette.setColor(QPalette.Text, Qt.black) try: totalNumPages = self.outputGroup[-1].pageNum except IndexError: # printing empty branch totalNumPages = 1 headerDoc = self.headerFooterDoc(True, pageNum, totalNumPages) if headerDoc: layout = headerDoc.documentLayout() layout.setPaintDevice(self.printer) headerDoc.setTextWidth(self.pageLayout.paintRect().width() * self.printer.logicalDpiX()) painter.save() topMargin = self.pageLayout.margins(QPageLayout.Inch).top() headerDelta = ((self.headerMargin - topMargin) * self.printer.logicalDpiX()) painter.translate(0, int(headerDelta)) layout.draw(painter, paintContext) painter.restore() painter.save() columnSpacing = int(self.columnSpacing * self.printer.logicalDpiX()) columnDelta = ( (self.pageLayout.paintRect().width() * self.printer.logicalDpiX() - columnSpacing * (self.numColumns - 1)) / self.numColumns) + columnSpacing for columnNum in range(self.numColumns): if columnNum > 0: painter.translate(columnDelta, 0) self.paintColumn(pageNum, columnNum, painter, paintContext) painter.restore() footerDoc = self.headerFooterDoc(False, pageNum, totalNumPages) if footerDoc: layout = footerDoc.documentLayout() layout.setPaintDevice(self.printer) footerDoc.setTextWidth(self.pageLayout.paintRect().width() * self.printer.logicalDpiX()) painter.save() bottomMargin = self.pageLayout.margins(QPageLayout.Inch).bottom() footerDelta = ((bottomMargin - self.footerMargin) * self.printer.logicalDpiX()) painter.translate( 0, self.pageLayout.paintRect().height() * self.printer.logicalDpiX() + int(footerDelta) - self.lineSpacing) layout.draw(painter, paintContext) painter.restore() def paintColumn(self, pageNum, columnNum, painter, paintContext): """Paint data for the given column to the printer. Arguments: pageNum -- the page number to be printed columnNum -- the column number to be printed painter -- the painter for this print job """ columnItems = [ item for item in self.outputGroup if item.pageNum == pageNum and item.columnNum == columnNum ] for item in columnItems: layout = item.doc.documentLayout() painter.save() painter.translate(item.level * self.indentSize, item.pagePos) layout.draw(painter, paintContext) painter.restore() if self.drawLines: self.addPrintLines(pageNum, columnNum, columnItems, painter) def addPrintLines(self, pageNum, columnNum, columnItems, painter): """Paint lines between parent and child items on the page. Arguments: pageNum -- the page number to be printed columnNum -- the column number to be printed columnItems -- a list of items in this column painter -- the painter for this print job """ parentsDrawn = set() horizOffset = self.indentSize // 2 vertOffset = self.lineSpacing // 2 heightAvail = (self.pageLayout.paintRect().height() * self.printer.logicalDpiY()) for item in columnItems: if item.level > 0: indent = item.level * self.indentSize vertPos = item.pagePos + vertOffset painter.drawLine(indent - horizOffset, vertPos, indent - self.lineSpacing // 4, vertPos) parent = item.parentItem while parent: if parent in parentsDrawn: break lineStart = 0 lineEnd = heightAvail if (parent.pageNum == pageNum and parent.columnNum == columnNum): lineStart = parent.pagePos + parent.height if (parent.lastChildItem.pageNum == pageNum and parent.lastChildItem.columnNum == columnNum): lineEnd = parent.lastChildItem.pagePos + vertOffset if (parent.lastChildItem.pageNum > pageNum or (parent.lastChildItem.pageNum == pageNum and parent.lastChildItem.columnNum >= columnNum)): horizPos = ((parent.level + 1) * self.indentSize - horizOffset) painter.drawLine(horizPos, lineStart, horizPos, lineEnd) parentsDrawn.add(parent) parent = parent.parentItem def formatHeaderFooter(self, header=True, pageNum=1, numPages=1): """Return an HTML table formatted header or footer. Return an empty string if no header/footer is defined. Arguments: header -- return header if True, footer if false """ if header: textParts = printdialogs.splitHeaderFooter(self.headerText) else: textParts = printdialogs.splitHeaderFooter(self.footerText) if not textParts: return '' fileInfoFormat = self.localControl.structure.treeFormats.fileInfoFormat fileInfoNode = self.localControl.structure.fileInfoNode fileInfoFormat.updateFileInfo(self.localControl.filePathObj, fileInfoNode) fileInfoNode.data[fileInfoFormat.pageNumFieldName] = repr(pageNum) fileInfoNode.data[fileInfoFormat.numPagesFieldName] = repr(numPages) fileInfoFormat.changeOutputLines(textParts, keepBlanks=True) textParts = fileInfoFormat.formatOutput(fileInfoNode, keepBlanks=True) alignments = ('left', 'center', 'right') result = ['<table width="100%"><tr>'] for text, align in zip(textParts, alignments): if text: result.append('<td align="{0}">{1}</td>'.format(align, text)) if len(result) > 1: result.append('</tr></table>') return '\n'.join(result) return '' def headerFooterDoc(self, header=True, pageNum=1, numPages=1): """Return a text document for the header or footer. Return None if no header/footer is defined. Arguments: header -- return header if True, footer if false """ text = self.formatHeaderFooter(header, pageNum, numPages) if text: doc = QTextDocument() doc.setHtml(text) doc.setDefaultFont(self.mainFont) frameFormat = doc.rootFrame().frameFormat() frameFormat.setBorder(0) frameFormat.setMargin(0) frameFormat.setPadding(0) doc.rootFrame().setFrameFormat(frameFormat) return doc return None def printSetup(self): """Show a dialog to set margins, page size and other printing options. """ setupDialog = printdialogs.PrintSetupDialog( self, True, QApplication.activeWindow()) setupDialog.exec_() def printPreview(self): """Show a preview of printing results. """ self.setupData() previewDialog = printdialogs.PrintPreviewDialog( self, QApplication.activeWindow()) previewDialog.previewWidget.paintRequested.connect(self.paintData) if globalref.genOptions['SaveWindowGeom']: previewDialog.restoreDialogGeom() previewDialog.exec_() def filePrint(self): """Show dialog and print tree output based on current options. """ self.printer.setOutputFormat(QPrinter.NativeFormat) self.setupData() printDialog = QPrintDialog(self.printer, QApplication.activeWindow()) if printDialog.exec_() == QDialog.Accepted: self.paintData(self.printer) def filePrintPdf(self): """Export to a PDF file with current options. """ filters = ';;'.join( (globalref.fileFilters['pdf'], globalref.fileFilters['all'])) defaultFilePath = str(globalref.mainControl.defaultPathObj()) defaultFilePath = os.path.splitext(defaultFilePath)[0] if os.path.basename(defaultFilePath): defaultFilePath = '{0}.{1}'.format(defaultFilePath, 'pdf') filePath, selectFilter = QFileDialog.getSaveFileName( QApplication.activeWindow(), _('TreeLine - Export PDF'), defaultFilePath, filters) if not filePath: return if not os.path.splitext(filePath)[1]: filePath = '{0}.{1}'.format(filePath, 'pdf') origFormat = self.printer.outputFormat() self.printer.setOutputFormat(QPrinter.PdfFormat) self.printer.setOutputFileName(filePath) self.adjustSpacing() self.setupData() self.paintData(self.printer) self.printer.setOutputFormat(origFormat) self.printer.setOutputFileName('') self.adjustSpacing()