1#!/usr/bin/env python3 2 3#****************************************************************************** 4# printdialogs.py, provides print preview and print settings dialogs 5# 6# TreeLine, an information storage program 7# Copyright (C) 2018, Douglas W. Bell 8# 9# This is free software; you can redistribute it and/or modify it under the 10# terms of the GNU General Public License, either Version 2 or any later 11# version. This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY. See the included LICENSE file for details. 13#****************************************************************************** 14 15import re 16import collections 17from PyQt5.QtCore import (QMarginsF, QPoint, QRect, QSize, QSizeF, Qt, 18 pyqtSignal) 19from PyQt5.QtGui import (QFontDatabase, QFontInfo, QFontMetrics, QIntValidator, 20 QPageLayout, QPageSize) 21from PyQt5.QtWidgets import (QAbstractItemView, QAction, QButtonGroup, 22 QCheckBox, QComboBox, QDialog, QDoubleSpinBox, 23 QGridLayout, QGroupBox, QHBoxLayout, QLabel, 24 QLineEdit, QListWidget, QMenu, QMessageBox, 25 QPushButton, QRadioButton, QSpinBox, QTabWidget, 26 QToolBar, QVBoxLayout, QWidget) 27from PyQt5.QtPrintSupport import (QPrintPreviewWidget, QPrinter, QPrinterInfo) 28import printdata 29import configdialog 30import treeformats 31import undo 32import globalref 33 34 35class PrintPreviewDialog(QDialog): 36 """Dialog for print previews. 37 38 Similar to QPrintPreviewDialog but calls a custom page setup dialog. 39 """ 40 def __init__(self, printData, parent=None): 41 """Create the print preview dialog. 42 43 Arguments: 44 printData -- the PrintData object 45 parent -- the parent window 46 """ 47 super().__init__(parent) 48 self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | 49 Qt.WindowCloseButtonHint) 50 self.setWindowTitle(_('Print Preview')) 51 self.printData = printData 52 topLayout = QVBoxLayout(self) 53 self.setLayout(topLayout) 54 55 toolBar = QToolBar(self) 56 topLayout.addWidget(toolBar) 57 58 self.previewWidget = QPrintPreviewWidget(printData.printer, self) 59 topLayout.addWidget(self.previewWidget) 60 self.previewWidget.previewChanged.connect(self.updateControls) 61 62 self.zoomWidthAct = QAction(_('Fit Width'), self, checkable=True) 63 icon = globalref.toolIcons.getIcon('printpreviewzoomwidth') 64 if icon: 65 self.zoomWidthAct.setIcon(icon) 66 self.zoomWidthAct.triggered.connect(self.zoomWidth) 67 toolBar.addAction(self.zoomWidthAct) 68 69 self.zoomAllAct = QAction(_('Fit Page'), self, checkable=True) 70 icon = globalref.toolIcons.getIcon('printpreviewzoomall') 71 if icon: 72 self.zoomAllAct.setIcon(icon) 73 self.zoomAllAct.triggered.connect(self.zoomAll) 74 toolBar.addAction(self.zoomAllAct) 75 toolBar.addSeparator() 76 77 self.zoomCombo = QComboBox(self) 78 self.zoomCombo.setEditable(True) 79 self.zoomCombo.setInsertPolicy(QComboBox.NoInsert) 80 self.zoomCombo.addItems([' 12%', ' 25%', ' 50%', ' 75%', ' 100%', 81 ' 125%', ' 150%', ' 200%', ' 400%', ' 800%']) 82 self.zoomCombo.currentIndexChanged[str].connect(self.zoomToValue) 83 self.zoomCombo.lineEdit().returnPressed.connect(self.zoomToValue) 84 toolBar.addWidget(self.zoomCombo) 85 86 zoomInAct = QAction(_('Zoom In'), self) 87 icon = globalref.toolIcons.getIcon('printpreviewzoomin') 88 if icon: 89 zoomInAct.setIcon(icon) 90 zoomInAct.triggered.connect(self.zoomIn) 91 toolBar.addAction(zoomInAct) 92 93 zoomOutAct = QAction(_('Zoom Out'), self) 94 icon = globalref.toolIcons.getIcon('printpreviewzoomout') 95 if icon: 96 zoomOutAct.setIcon(icon) 97 zoomOutAct.triggered.connect(self.zoomOut) 98 toolBar.addAction(zoomOutAct) 99 toolBar.addSeparator() 100 101 self.previousAct = QAction(_('Previous Page'), self) 102 icon = globalref.toolIcons.getIcon('printpreviewprevious') 103 if icon: 104 self.previousAct.setIcon(icon) 105 self.previousAct.triggered.connect(self.previousPage) 106 toolBar.addAction(self.previousAct) 107 108 self.pageNumEdit = QLineEdit(self) 109 self.pageNumEdit.setAlignment(Qt.AlignRight | 110 Qt.AlignVCenter) 111 width = QFontMetrics(self.pageNumEdit.font()).width('0000') 112 self.pageNumEdit.setMaximumWidth(width) 113 self.pageNumEdit.returnPressed.connect(self.setPageNum) 114 toolBar.addWidget(self.pageNumEdit) 115 116 self.maxPageLabel = QLabel(' / 000 ', self) 117 toolBar.addWidget(self.maxPageLabel) 118 119 self.nextAct = QAction(_('Next Page'), self) 120 icon = globalref.toolIcons.getIcon('printpreviewnext') 121 if icon: 122 self.nextAct.setIcon(icon) 123 self.nextAct.triggered.connect(self.nextPage) 124 toolBar.addAction(self.nextAct) 125 toolBar.addSeparator() 126 127 self.onePageAct = QAction(_('Single Page'), self, checkable=True) 128 icon = globalref.toolIcons.getIcon('printpreviewsingle') 129 if icon: 130 self.onePageAct.setIcon(icon) 131 self.onePageAct.triggered.connect(self.previewWidget. 132 setSinglePageViewMode) 133 toolBar.addAction(self.onePageAct) 134 135 self.twoPageAct = QAction(_('Facing Pages'), self, 136 checkable=True) 137 icon = globalref.toolIcons.getIcon('printpreviewdouble') 138 if icon: 139 self.twoPageAct.setIcon(icon) 140 self.twoPageAct.triggered.connect(self.previewWidget. 141 setFacingPagesViewMode) 142 toolBar.addAction(self.twoPageAct) 143 toolBar.addSeparator() 144 145 pageSetupAct = QAction(_('Print Setup'), self) 146 icon = globalref.toolIcons.getIcon('fileprintsetup') 147 if icon: 148 pageSetupAct.setIcon(icon) 149 pageSetupAct.triggered.connect(self.printSetup) 150 toolBar.addAction(pageSetupAct) 151 152 filePrintAct = QAction(_('Print'), self) 153 icon = globalref.toolIcons.getIcon('fileprint') 154 if icon: 155 filePrintAct.setIcon(icon) 156 filePrintAct.triggered.connect(self.filePrint) 157 toolBar.addAction(filePrintAct) 158 159 def updateControls(self): 160 """Update control availability and status based on a change signal. 161 """ 162 self.zoomWidthAct.setChecked(self.previewWidget.zoomMode() == 163 QPrintPreviewWidget.FitToWidth) 164 self.zoomAllAct.setChecked(self.previewWidget.zoomMode() == 165 QPrintPreviewWidget.FitInView) 166 zoom = self.previewWidget.zoomFactor() * 100 167 self.zoomCombo.setEditText('{0:4.0f}%'.format(zoom)) 168 self.previousAct.setEnabled(self.previewWidget.currentPage() > 1) 169 self.nextAct.setEnabled(self.previewWidget.currentPage() < 170 self.previewWidget.pageCount()) 171 self.pageNumEdit.setText(str(self.previewWidget.currentPage())) 172 self.maxPageLabel.setText(' / {0} '.format(self.previewWidget. 173 pageCount())) 174 self.onePageAct.setChecked(self.previewWidget.viewMode() == 175 QPrintPreviewWidget.SinglePageView) 176 self.twoPageAct.setChecked(self.previewWidget.viewMode() == 177 QPrintPreviewWidget.FacingPagesView) 178 179 def zoomWidth(self, checked=True): 180 """Set the fit to width zoom mode if checked. 181 182 Arguments: 183 checked -- set this mode if True 184 """ 185 if checked: 186 self.previewWidget.setZoomMode(QPrintPreviewWidget. 187 FitToWidth) 188 else: 189 self.previewWidget.setZoomMode(QPrintPreviewWidget. 190 CustomZoom) 191 self.updateControls() 192 193 def zoomAll(self, checked=True): 194 """Set the fit in view zoom mode if checked. 195 196 Arguments: 197 checked -- set this mode if True 198 """ 199 if checked: 200 self.previewWidget.setZoomMode(QPrintPreviewWidget.FitInView) 201 else: 202 self.previewWidget.setZoomMode(QPrintPreviewWidget. 203 CustomZoom) 204 self.updateControls() 205 206 def zoomToValue(self, factorStr=''): 207 """Zoom to the given combo box string value. 208 209 Arguments: 210 factorStr -- the zoom factor as a string, often with a % suffix 211 """ 212 if not factorStr: 213 factorStr = self.zoomCombo.lineEdit().text() 214 try: 215 factor = float(factorStr.strip(' %')) / 100 216 self.previewWidget.setZoomFactor(factor) 217 except ValueError: 218 pass 219 self.updateControls() 220 221 def zoomIn(self): 222 """Increase the zoom level by an increment. 223 """ 224 self.previewWidget.zoomIn() 225 self.updateControls() 226 227 def zoomOut(self): 228 """Decrease the zoom level by an increment. 229 """ 230 self.previewWidget.zoomOut() 231 self.updateControls() 232 233 def previousPage(self): 234 """Go to the previous page of the preview. 235 """ 236 self.previewWidget.setCurrentPage(self.previewWidget.currentPage() - 1) 237 self.updateControls() 238 239 def nextPage(self): 240 """Go to the next page of the preview. 241 """ 242 self.previewWidget.setCurrentPage(self.previewWidget.currentPage() + 1) 243 self.updateControls() 244 245 def setPageNum(self): 246 """Go to a page number from the line editor based on a signal. 247 """ 248 try: 249 self.previewWidget.setCurrentPage(int(self.pageNumEdit.text())) 250 except ValueError: 251 pass 252 self.updateControls() 253 254 def printSetup(self): 255 """Show a dialog to set margins, page size and other printing options. 256 """ 257 setupDialog = PrintSetupDialog(self.printData, False, self) 258 if setupDialog.exec_() == QDialog.Accepted: 259 self.printData.setupData() 260 self.previewWidget.updatePreview() 261 262 def filePrint(self): 263 """Show dialog and print tree output based on current options. 264 """ 265 self.close() 266 if self.printData.printer.outputFormat() == QPrinter.NativeFormat: 267 self.printData.filePrint() 268 else: 269 self.printData.filePrintPdf() 270 271 def sizeHint(self): 272 """Return a larger default height. 273 """ 274 size = super().sizeHint() 275 size.setHeight(600) 276 return size 277 278 def restoreDialogGeom(self): 279 """Restore dialog window geometry from history options. 280 """ 281 rect = QRect(globalref.histOptions['PrintPrevXPos'], 282 globalref.histOptions['PrintPrevYPos'], 283 globalref.histOptions['PrintPrevXSize'], 284 globalref.histOptions['PrintPrevYSize']) 285 if rect.height() and rect.width(): 286 self.setGeometry(rect) 287 288 def saveDialogGeom(self): 289 """Savedialog window geometry to history options. 290 """ 291 globalref.histOptions.changeValue('PrintPrevXSize', self.width()) 292 globalref.histOptions.changeValue('PrintPrevYSize', self.height()) 293 globalref.histOptions.changeValue('PrintPrevXPos', self.geometry().x()) 294 globalref.histOptions.changeValue('PrintPrevYPos', self.geometry().y()) 295 296 def closeEvent(self, event): 297 """Save dialog geometry at close. 298 299 Arguments: 300 event -- the close event 301 """ 302 if globalref.genOptions['SaveWindowGeom']: 303 self.saveDialogGeom() 304 305 306class PrintSetupDialog(QDialog): 307 """Base dialog for setting the print configuration. 308 309 Pushes most options to the PrintData class. 310 """ 311 def __init__(self, printData, showExtraButtons=True, parent=None): 312 """Create the printing setup dialog. 313 314 Arguments: 315 printData -- a reference to the PrintData class 316 showExtraButtons -- add print preview and print shortcut buttons 317 parent -- the parent window 318 """ 319 super().__init__(parent) 320 self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | 321 Qt.WindowCloseButtonHint) 322 self.setWindowTitle(_('Printing Setup')) 323 self.printData = printData 324 325 topLayout = QVBoxLayout(self) 326 self.setLayout(topLayout) 327 328 tabs = QTabWidget() 329 topLayout.addWidget(tabs) 330 generalPage = GeneralPage(self.printData) 331 tabs.addTab(generalPage, _('&General Options')) 332 pageSetupPage = PageSetupPage(self.printData, 333 generalPage.currentPrinterName) 334 tabs.addTab(pageSetupPage, _('Page &Setup')) 335 fontPage = FontPage(self.printData) 336 tabs.addTab(fontPage, _('&Font Selection')) 337 headerPage = HeaderPage(self.printData) 338 tabs.addTab(headerPage, _('&Header/Footer')) 339 generalPage.printerChanged.connect(pageSetupPage.changePrinter) 340 self.tabPages = [generalPage, pageSetupPage, fontPage, headerPage] 341 342 ctrlLayout = QHBoxLayout() 343 topLayout.addLayout(ctrlLayout) 344 ctrlLayout.addStretch() 345 if showExtraButtons: 346 previewButton = QPushButton(_('Print Pre&view...')) 347 ctrlLayout.addWidget(previewButton) 348 previewButton.clicked.connect(self.preview) 349 printButton = QPushButton(_('&Print...')) 350 ctrlLayout.addWidget(printButton) 351 printButton.clicked.connect(self.quickPrint) 352 okButton = QPushButton(_('&OK')) 353 ctrlLayout.addWidget(okButton) 354 okButton.clicked.connect(self.accept) 355 cancelButton = QPushButton(_('&Cancel')) 356 ctrlLayout.addWidget(cancelButton) 357 cancelButton.clicked.connect(self.reject) 358 359 def quickPrint(self): 360 """Accept this dialog and go to print dialog. 361 """ 362 self.accept() 363 if self.printData.printer.outputFormat() == QPrinter.NativeFormat: 364 self.printData.filePrint() 365 else: 366 self.printData.filePrintPdf() 367 368 def preview(self): 369 """Accept this dialog and go to print preview dialog. 370 """ 371 self.accept() 372 self.printData.printPreview() 373 374 def accept(self): 375 """Store results before closing dialog. 376 """ 377 if not self.tabPages[1].checkValid(): 378 QMessageBox.warning(self, 'TreeLine', 379 _('Error: Page size or margins are invalid')) 380 return 381 changed = False 382 control = self.printData.localControl 383 undoObj = undo.StateSettingUndo(control.structure.undoList, 384 self.printData.fileData, 385 self.printData.readData) 386 for page in self.tabPages: 387 if page.saveChanges(): 388 changed = True 389 if changed: 390 self.printData.adjustSpacing() 391 control.setModified() 392 else: 393 control.structure.undoList.removeLastUndo(undoObj) 394 super().accept() 395 396 397_pdfPrinterName = _('TreeLine PDF Printer') 398 399 400class GeneralPage(QWidget): 401 """Dialog page for misc. print options. 402 """ 403 printerChanged = pyqtSignal(str) 404 def __init__(self, printData, parent=None): 405 """Create the general settings page. 406 407 Arguments: 408 printData -- a reference to the PrintData class 409 parent -- the parent dialog 410 """ 411 super().__init__(parent) 412 self.printData = printData 413 self.printerList = QPrinterInfo.availablePrinterNames() 414 self.printerList.insert(0, _pdfPrinterName) 415 self.currentPrinterName = self.printData.printer.printerName() 416 if not self.currentPrinterName: 417 self.currentPrinterName = _pdfPrinterName 418 419 topLayout = QHBoxLayout(self) 420 self.setLayout(topLayout) 421 leftLayout = QVBoxLayout() 422 topLayout.addLayout(leftLayout) 423 424 whatGroupBox = QGroupBox(_('What to print')) 425 leftLayout.addWidget(whatGroupBox) 426 whatLayout = QVBoxLayout(whatGroupBox) 427 self.whatButtons = QButtonGroup(self) 428 treeButton = QRadioButton(_('&Entire tree')) 429 self.whatButtons.addButton(treeButton, printdata.PrintScope.entireTree) 430 whatLayout.addWidget(treeButton) 431 branchButton = QRadioButton(_('Selected &branches')) 432 self.whatButtons.addButton(branchButton, 433 printdata.PrintScope.selectBranch) 434 whatLayout.addWidget(branchButton) 435 nodeButton = QRadioButton(_('Selected &nodes')) 436 self.whatButtons.addButton(nodeButton, printdata.PrintScope.selectNode) 437 whatLayout.addWidget(nodeButton) 438 self.whatButtons.button(self.printData.printWhat).setChecked(True) 439 self.whatButtons.buttonClicked.connect(self.updateCmdAvail) 440 441 includeBox = QGroupBox(_('Included Nodes')) 442 leftLayout.addWidget(includeBox) 443 includeLayout = QVBoxLayout(includeBox) 444 self.rootButton = QCheckBox(_('&Include root node')) 445 includeLayout.addWidget(self.rootButton) 446 self.rootButton.setChecked(self.printData.includeRoot) 447 self.openOnlyButton = QCheckBox(_('Onl&y open node children')) 448 includeLayout.addWidget(self.openOnlyButton) 449 self.openOnlyButton.setChecked(self.printData.openOnly) 450 leftLayout.addStretch() 451 452 rightLayout = QVBoxLayout() 453 topLayout.addLayout(rightLayout) 454 455 printerBox = QGroupBox(_('Select &Printer')) 456 rightLayout.addWidget(printerBox) 457 printerLayout = QVBoxLayout(printerBox) 458 printerCombo = QComboBox() 459 printerLayout.addWidget(printerCombo) 460 printerCombo.addItems(self.printerList) 461 printerCombo.setCurrentIndex(self.printerList.index(self. 462 currentPrinterName)) 463 printerCombo.currentIndexChanged.connect(self.changePrinter) 464 465 featureBox = QGroupBox(_('Features')) 466 rightLayout.addWidget(featureBox) 467 featureLayout = QVBoxLayout(featureBox) 468 self.linesButton = QCheckBox(_('&Draw lines to children')) 469 featureLayout.addWidget(self.linesButton) 470 self.linesButton.setChecked(self.printData.drawLines) 471 self.widowButton = QCheckBox(_('&Keep first child with parent')) 472 featureLayout.addWidget(self.widowButton) 473 self.widowButton.setChecked(self.printData.widowControl) 474 475 indentBox = QGroupBox(_('Indent')) 476 rightLayout.addWidget(indentBox) 477 indentLayout = QHBoxLayout(indentBox) 478 indentLabel = QLabel(_('Indent Offse&t\n(line height units)')) 479 indentLayout.addWidget(indentLabel) 480 self.indentSpin = QDoubleSpinBox() 481 indentLayout.addWidget(self.indentSpin) 482 indentLabel.setBuddy(self.indentSpin) 483 self.indentSpin.setMinimum(0.5) 484 self.indentSpin.setSingleStep(0.5) 485 self.indentSpin.setDecimals(1) 486 self.indentSpin.setValue(self.printData.indentFactor) 487 rightLayout.addStretch() 488 489 self.updateCmdAvail() 490 491 def updateCmdAvail(self): 492 """Update options available based on print what settings. 493 """ 494 if self.whatButtons.checkedId() == printdata.PrintScope.selectNode: 495 self.rootButton.setChecked(True) 496 self.rootButton.setEnabled(False) 497 self.openOnlyButton.setChecked(False) 498 self.openOnlyButton.setEnabled(False) 499 else: 500 self.rootButton.setEnabled(True) 501 self.openOnlyButton.setEnabled(True) 502 503 def changePrinter(self, printerNum): 504 """Change the current printer based on a combo box signal. 505 506 Arguments: 507 printerNum -- the printer number from the combo box 508 """ 509 self.currentPrinterName = self.printerList[printerNum] 510 self.printerChanged.emit(self.currentPrinterName) 511 512 def saveChanges(self): 513 """Update print data with current dialog settings. 514 515 Return True if saved settings have changed, False otherwise. 516 """ 517 self.printData.printWhat = self.whatButtons.checkedId() 518 self.printData.includeRoot = self.rootButton.isChecked() 519 self.printData.openOnly = self.openOnlyButton.isChecked() 520 if self.currentPrinterName != _pdfPrinterName: 521 self.printData.printer.setPrinterName(self.currentPrinterName) 522 else: 523 self.printData.printer.setPrinterName('') 524 changed = False 525 if self.printData.drawLines != self.linesButton.isChecked(): 526 self.printData.drawLines = self.linesButton.isChecked() 527 changed = True 528 if self.printData.widowControl != self.widowButton.isChecked(): 529 self.printData.widowControl = self.widowButton.isChecked() 530 changed = True 531 if self.printData.indentFactor != self.indentSpin.value(): 532 self.printData.indentFactor = self.indentSpin.value() 533 changed = True 534 return changed 535 536 537_paperSizes = collections.OrderedDict([('Letter', _('Letter (8.5 x 11 in.)')), 538 ('Legal', _('Legal (8.5 x 14 in.)'),), 539 ('Tabloid', _('Tabloid (11 x 17 in.)')), 540 ('A3', _('A3 (279 x 420 mm)')), 541 ('A4', _('A4 (210 x 297 mm)')), 542 ('A5', _('A5 (148 x 210 mm)')), 543 ('Custom', _('Custom Size'))]) 544_units = collections.OrderedDict([('in', _('Inches (in)')), 545 ('mm', _('Millimeters (mm)')), 546 ('cm', _('Centimeters (cm)'))]) 547_unitValues = {'in': 1.0, 'cm': 2.54, 'mm': 25.4} 548_unitDecimals = {'in': 2, 'cm': 1, 'mm': 0} 549 550class PageSetupPage(QWidget): 551 """Dialog page for page setup options. 552 """ 553 def __init__(self, printData, currentPrinterName, parent=None): 554 """Create the page setup settings page. 555 556 Arguments: 557 printData -- a reference to the PrintData class 558 currentPrinterName -- the selected printer for validation 559 parent -- the parent dialog 560 """ 561 super().__init__(parent) 562 self.printData = printData 563 self.currentPrinterName = currentPrinterName 564 565 topLayout = QHBoxLayout(self) 566 self.setLayout(topLayout) 567 leftLayout = QVBoxLayout() 568 topLayout.addLayout(leftLayout) 569 570 unitsBox = QGroupBox(_('&Units')) 571 leftLayout.addWidget(unitsBox) 572 unitsLayout = QVBoxLayout(unitsBox) 573 unitsCombo = QComboBox() 574 unitsLayout.addWidget(unitsCombo) 575 unitsCombo.addItems(list(_units.values())) 576 self.currentUnit = globalref.miscOptions['PrintUnits'] 577 if self.currentUnit not in _units: 578 self.currentUnit = 'in' 579 unitsCombo.setCurrentIndex(list(_units.keys()).index(self.currentUnit)) 580 unitsCombo.currentIndexChanged.connect(self.changeUnits) 581 582 paperSizeBox = QGroupBox(_('Paper &Size')) 583 leftLayout.addWidget(paperSizeBox) 584 paperSizeLayout = QGridLayout(paperSizeBox) 585 spacing = paperSizeLayout.spacing() 586 paperSizeLayout.setVerticalSpacing(0) 587 paperSizeLayout.setRowMinimumHeight(1, spacing) 588 paperSizeCombo = QComboBox() 589 paperSizeLayout.addWidget(paperSizeCombo, 0, 0, 1, 2) 590 paperSizeCombo.addItems(list(_paperSizes.values())) 591 self.currentPaperSize = self.printData.paperSizeName() 592 if self.currentPaperSize not in _paperSizes: 593 self.currentPaperSize = 'Custom' 594 paperSizeCombo.setCurrentIndex(list(_paperSizes.keys()). 595 index(self.currentPaperSize)) 596 paperSizeCombo.currentIndexChanged.connect(self.changePaper) 597 widthLabel = QLabel(_('&Width:')) 598 paperSizeLayout.addWidget(widthLabel, 2, 0) 599 self.paperWidthSpin = UnitSpinBox(self.currentUnit) 600 paperSizeLayout.addWidget(self.paperWidthSpin, 3, 0) 601 widthLabel.setBuddy(self.paperWidthSpin) 602 paperWidth, paperHeight = self.printData.roundedPaperSize() 603 self.paperWidthSpin.setInchValue(paperWidth) 604 heightlabel = QLabel(_('Height:')) 605 paperSizeLayout.addWidget(heightlabel, 2, 1) 606 self.paperHeightSpin = UnitSpinBox(self.currentUnit) 607 paperSizeLayout.addWidget(self.paperHeightSpin, 3, 1) 608 heightlabel.setBuddy(self.paperHeightSpin) 609 self.paperHeightSpin.setInchValue(paperHeight) 610 if self.currentPaperSize != 'Custom': 611 self.paperWidthSpin.setEnabled(False) 612 self.paperHeightSpin.setEnabled(False) 613 614 orientbox = QGroupBox(_('Orientation')) 615 leftLayout.addWidget(orientbox) 616 orientLayout = QVBoxLayout(orientbox) 617 portraitButton = QRadioButton(_('Portra&it')) 618 orientLayout.addWidget(portraitButton) 619 landscapeButton = QRadioButton(_('Lan&dscape')) 620 orientLayout.addWidget(landscapeButton) 621 self.portraitOrient = (self.printData.pageLayout.orientation() == 622 QPageLayout.Portrait) 623 if self.portraitOrient: 624 portraitButton.setChecked(True) 625 else: 626 landscapeButton.setChecked(True) 627 portraitButton.toggled.connect(self.changeOrient) 628 629 rightLayout = QVBoxLayout() 630 topLayout.addLayout(rightLayout) 631 632 marginsBox = QGroupBox(_('Margins')) 633 rightLayout.addWidget(marginsBox) 634 marginsLayout = QGridLayout(marginsBox) 635 spacing = marginsLayout.spacing() 636 marginsLayout.setVerticalSpacing(0) 637 marginsLayout.setRowMinimumHeight(2, spacing) 638 marginsLayout.setRowMinimumHeight(5, spacing) 639 leftLabel = QLabel(_('&Left:')) 640 marginsLayout.addWidget(leftLabel, 3, 0) 641 leftMarginSpin = UnitSpinBox(self.currentUnit) 642 marginsLayout.addWidget(leftMarginSpin, 4, 0) 643 leftLabel.setBuddy(leftMarginSpin) 644 topLabel = QLabel(_('&Top:')) 645 marginsLayout.addWidget(topLabel, 0, 1) 646 topMarginSpin = UnitSpinBox(self.currentUnit) 647 marginsLayout.addWidget(topMarginSpin, 1, 1) 648 topLabel.setBuddy(topMarginSpin) 649 rightLabel = QLabel(_('&Right:')) 650 marginsLayout.addWidget(rightLabel, 3, 2) 651 rightMarginSpin = UnitSpinBox(self.currentUnit) 652 marginsLayout.addWidget(rightMarginSpin, 4, 2) 653 rightLabel.setBuddy(rightMarginSpin) 654 bottomLabel = QLabel(_('&Bottom:')) 655 marginsLayout.addWidget(bottomLabel, 6, 1) 656 bottomMarginSpin = UnitSpinBox(self.currentUnit) 657 marginsLayout.addWidget(bottomMarginSpin, 7, 1) 658 bottomLabel.setBuddy(bottomMarginSpin) 659 self.marginControls = (leftMarginSpin, topMarginSpin, rightMarginSpin, 660 bottomMarginSpin) 661 for control, value in zip(self.marginControls, 662 self.printData.roundedMargins()): 663 control.setInchValue(value) 664 headerLabel = QLabel(_('He&ader:')) 665 marginsLayout.addWidget(headerLabel, 0, 2) 666 self.headerMarginSpin = UnitSpinBox(self.currentUnit) 667 marginsLayout.addWidget(self.headerMarginSpin, 1, 2) 668 headerLabel.setBuddy(self.headerMarginSpin) 669 self.headerMarginSpin.setInchValue(self.printData.headerMargin) 670 footerLabel = QLabel(_('Foot&er:')) 671 marginsLayout.addWidget(footerLabel, 6, 2) 672 self.footerMarginSpin = UnitSpinBox(self.currentUnit) 673 marginsLayout.addWidget(self.footerMarginSpin, 7, 2) 674 footerLabel.setBuddy(self.footerMarginSpin) 675 self.footerMarginSpin.setInchValue(self.printData.footerMargin) 676 677 columnsBox = QGroupBox(_('Columns')) 678 rightLayout.addWidget(columnsBox) 679 columnLayout = QGridLayout(columnsBox) 680 numLabel = QLabel(_('&Number of columns')) 681 columnLayout.addWidget(numLabel, 0, 0) 682 self.columnSpin = QSpinBox() 683 columnLayout.addWidget(self.columnSpin, 0, 1) 684 numLabel.setBuddy(self.columnSpin) 685 self.columnSpin.setMinimum(1) 686 self.columnSpin.setMaximum(9) 687 self.columnSpin.setValue(self.printData.numColumns) 688 spaceLabel = QLabel(_('Space between colu&mns')) 689 columnLayout.addWidget(spaceLabel, 1, 0) 690 self.columnSpaceSpin = UnitSpinBox(self.currentUnit) 691 columnLayout.addWidget(self.columnSpaceSpin, 1, 1) 692 spaceLabel.setBuddy(self.columnSpaceSpin) 693 self.columnSpaceSpin.setInchValue(self.printData.columnSpacing) 694 695 def changePrinter(self, newPrinterName): 696 """Change the currently selected printer. 697 698 Arguments: 699 newPrinterName -- new printer selection 700 """ 701 self.currentPrinterName = newPrinterName 702 703 def changeUnits(self, unitNum): 704 """Change the current unit and update conversions based on a signal. 705 706 Arguments: 707 unitNum -- the unit index number from the combobox 708 """ 709 oldUnit = self.currentUnit 710 self.currentUnit = list(_units.keys())[unitNum] 711 self.paperWidthSpin.changeUnit(self.currentUnit) 712 self.paperHeightSpin.changeUnit(self.currentUnit) 713 for control in self.marginControls: 714 control.changeUnit(self.currentUnit) 715 self.headerMarginSpin.changeUnit(self.currentUnit) 716 self.footerMarginSpin.changeUnit(self.currentUnit) 717 self.columnSpaceSpin.changeUnit(self.currentUnit) 718 719 def changePaper(self, paperNum): 720 """Change the current paper size based on a signal. 721 722 Arguments: 723 paperNum -- the paper size index number from the combobox 724 """ 725 self.currentPaperSize = list(_paperSizes.keys())[paperNum] 726 if self.currentPaperSize != 'Custom': 727 tempPrinter = QPrinter() 728 pageLayout = tempPrinter.pageLayout() 729 pageLayout.setPageSize(QPageSize(getattr(QPageSize, 730 self.currentPaperSize))) 731 if not self.portraitOrient: 732 pageLayout.setOrientation(QPageLayout.Landscape) 733 paperSize = pageLayout.fullRect(QPageLayout.Inch) 734 self.paperWidthSpin.setInchValue(round(paperSize.width(), 2)) 735 self.paperHeightSpin.setInchValue(round(paperSize.height(), 2)) 736 self.paperWidthSpin.setEnabled(self.currentPaperSize == 'Custom') 737 self.paperHeightSpin.setEnabled(self.currentPaperSize == 'Custom') 738 739 def changeOrient(self, isPortrait): 740 """Change the orientation based on a signal. 741 742 Arguments: 743 isPortrait -- true if portrait orientation is selected 744 """ 745 self.portraitOrient = isPortrait 746 width = self.paperWidthSpin.inchValue 747 height = self.paperHeightSpin.inchValue 748 if (self.portraitOrient and width > height) or (not self.portraitOrient 749 and width < height): 750 self.paperWidthSpin.setInchValue(height) 751 self.paperHeightSpin.setInchValue(width) 752 753 def checkValid(self): 754 """Return True if the current page size and margins appear to be valid. 755 """ 756 pageWidth = self.paperWidthSpin.inchValue 757 pageHeight = self.paperHeightSpin.inchValue 758 if pageWidth <= 0 or pageHeight <= 0: 759 return False 760 margins = tuple(control.inchValue for control in self.marginControls) 761 if (margins[0] + margins[2] >= pageWidth or 762 margins[1] + margins[3] >= pageHeight): 763 return False 764 return True 765 766 def saveChanges(self): 767 """Update print data with current dialog settings. 768 769 Return True if saved settings have changed, False otherwise. 770 """ 771 if self.currentUnit != globalref.miscOptions['PrintUnits']: 772 globalref.miscOptions.changeValue('PrintUnits', self.currentUnit) 773 globalref.miscOptions.writeFile() 774 changed = False 775 pageLayout = self.printData.pageLayout 776 if self.currentPaperSize != 'Custom': 777 size = getattr(QPageSize, self.currentPaperSize) 778 if size != pageLayout.pageSize().id(): 779 pageLayout.setPageSize(QPageSize(size)) 780 changed = True 781 else: 782 size = (self.paperWidthSpin.inchValue, 783 self.paperHeightSpin.inchValue) 784 if size != self.printData.roundedPaperSize(): 785 pageLayout.setPageSize(QPageSize(QSizeF(*size), 786 QPageSize.Inch)) 787 changed = True 788 orient = (QPageLayout.Portrait if self.portraitOrient else 789 QPageLayout.Landscape) 790 if orient != pageLayout.orientation(): 791 pageLayout.setOrientation(orient) 792 changed = True 793 margins = tuple(control.inchValue for control in self.marginControls) 794 if margins != self.printData.roundedMargins(): 795 pageLayout.setMargins(QMarginsF(*margins)) 796 changed = True 797 if self.printData.headerMargin != self.headerMarginSpin.inchValue: 798 self.printData.headerMargin = self.headerMarginSpin.inchValue 799 changed = True 800 if self.printData.footerMargin != self.footerMarginSpin.inchValue: 801 self.printData.footerMargin = self.footerMarginSpin.inchValue 802 changed = True 803 if self.printData.numColumns != self.columnSpin.value(): 804 self.printData.numColumns = self.columnSpin.value() 805 changed = True 806 if self.printData.columnSpacing != self.columnSpaceSpin.inchValue: 807 self.printData.columnSpacing = self.columnSpaceSpin.inchValue 808 changed = True 809 return changed 810 811 812class UnitSpinBox(QDoubleSpinBox): 813 """Spin box with unit suffix that can convert the units of its contents. 814 815 Stores the value at full precision to avoid round-trip rounding errors. 816 """ 817 def __init__(self, unit, parent=None): 818 """Create the unit spin box. 819 820 Arguments: 821 unit -- the original unit (abbreviated string) 822 parent -- the parent dialog if given 823 """ 824 super().__init__(parent) 825 self.unit = unit 826 self.inchValue = 0.0 827 self.setupUnit() 828 self.valueChanged.connect(self.changeValue) 829 830 def setupUnit(self): 831 """Set the suffix, decimal places and maximum based on the unit. 832 """ 833 self.blockSignals(True) 834 self.setSuffix(' {0}'.format(self.unit)) 835 decPlaces = _unitDecimals[self.unit] 836 self.setDecimals(decPlaces) 837 # set maximum to 5 digits total 838 self.setMaximum((10**5 - 1) / 10**decPlaces) 839 self.blockSignals(False) 840 841 def changeUnit(self, unit): 842 """Change current unit. 843 844 Arguments: 845 unit -- the new unit (abbreviated string) 846 """ 847 self.unit = unit 848 self.setupUnit() 849 self.setInchValue(self.inchValue) 850 851 def setInchValue(self, inchValue): 852 """Set box to given value, converted to current unit. 853 854 Arguments: 855 inchValue -- the value to set in inches 856 """ 857 self.inchValue = inchValue 858 value = self.inchValue * _unitValues[self.unit] 859 self.blockSignals(True) 860 self.setValue(value) 861 self.blockSignals(False) 862 if value < 4: 863 self.setSingleStep(0.1) 864 elif value > 50: 865 self.setSingleStep(10) 866 else: 867 self.setSingleStep(1) 868 869 def changeValue(self): 870 """Change the stored inch value based on a signal. 871 """ 872 self.inchValue = round(self.value() / _unitValues[self.unit], 2) 873 874 875class SmallListWidget(QListWidget): 876 """ListWidget with a smaller size hint. 877 """ 878 def __init__(self, parent=None): 879 """Initialize the widget. 880 881 Arguments: 882 parent -- the parent, if given 883 """ 884 super().__init__(parent) 885 886 def sizeHint(self): 887 """Return smaller width. 888 """ 889 itemHeight = self.visualItemRect(self.item(0)).height() 890 return QSize(100, itemHeight * 3) 891 892 893class FontPage(QWidget): 894 """Font selection print option dialog page. 895 """ 896 def __init__(self, printData, defaultLabel='', parent=None): 897 """Create the font settings page. 898 899 Arguments: 900 printData -- a reference to the PrintData class 901 defaultLabel -- default font label if given, o/w TreeLine output 902 parent -- the parent dialog 903 """ 904 super().__init__(parent) 905 self.printData = printData 906 self.currentFont = self.printData.mainFont 907 908 topLayout = QVBoxLayout(self) 909 self.setLayout(topLayout) 910 defaultBox = QGroupBox(_('Default Font')) 911 topLayout.addWidget(defaultBox) 912 defaultLayout = QVBoxLayout(defaultBox) 913 if not defaultLabel: 914 defaultLabel = _('&Use TreeLine output view font') 915 self.defaultCheck = QCheckBox(defaultLabel) 916 defaultLayout.addWidget(self.defaultCheck) 917 self.defaultCheck.setChecked(self.printData.useDefaultFont) 918 self.defaultCheck.clicked.connect(self.setFontSelectAvail) 919 920 self.fontBox = QGroupBox(_('Select Font')) 921 topLayout.addWidget(self.fontBox) 922 fontLayout = QGridLayout(self.fontBox) 923 spacing = fontLayout.spacing() 924 fontLayout.setSpacing(0) 925 926 label = QLabel(_('&Font')) 927 fontLayout.addWidget(label, 0, 0) 928 label.setIndent(2) 929 self.familyEdit = QLineEdit() 930 fontLayout.addWidget(self.familyEdit, 1, 0) 931 self.familyEdit.setReadOnly(True) 932 self.familyList = SmallListWidget() 933 fontLayout.addWidget(self.familyList, 2, 0) 934 label.setBuddy(self.familyList) 935 self.familyEdit.setFocusProxy(self.familyList) 936 fontLayout.setColumnMinimumWidth(1, spacing) 937 families = [family for family in QFontDatabase().families()] 938 families.sort(key=str.lower) 939 self.familyList.addItems(families) 940 self.familyList.currentItemChanged.connect(self.updateFamily) 941 942 label = QLabel(_('Font st&yle')) 943 fontLayout.addWidget(label, 0, 2) 944 label.setIndent(2) 945 self.styleEdit = QLineEdit() 946 fontLayout.addWidget(self.styleEdit, 1, 2) 947 self.styleEdit.setReadOnly(True) 948 self.styleList = SmallListWidget() 949 fontLayout.addWidget(self.styleList, 2, 2) 950 label.setBuddy(self.styleList) 951 self.styleEdit.setFocusProxy(self.styleList) 952 fontLayout.setColumnMinimumWidth(3, spacing) 953 self.styleList.currentItemChanged.connect(self.updateStyle) 954 955 label = QLabel(_('Si&ze')) 956 fontLayout.addWidget(label, 0, 4) 957 label.setIndent(2) 958 self.sizeEdit = QLineEdit() 959 fontLayout.addWidget(self.sizeEdit, 1, 4) 960 self.sizeEdit.setFocusPolicy(Qt.ClickFocus) 961 validator = QIntValidator(1, 512, self) 962 self.sizeEdit.setValidator(validator) 963 self.sizeList = SmallListWidget() 964 fontLayout.addWidget(self.sizeList, 2, 4) 965 label.setBuddy(self.sizeList) 966 self.sizeList.currentItemChanged.connect(self.updateSize) 967 968 fontLayout.setColumnStretch(0, 30) 969 fontLayout.setColumnStretch(2, 25) 970 fontLayout.setColumnStretch(4, 10) 971 972 sampleBox = QGroupBox(_('Sample')) 973 topLayout.addWidget(sampleBox) 974 sampleLayout = QVBoxLayout(sampleBox) 975 self.sampleEdit = QLineEdit() 976 sampleLayout.addWidget(self.sampleEdit) 977 self.sampleEdit.setAlignment(Qt.AlignCenter) 978 self.sampleEdit.setText(_('AaBbCcDdEeFfGg...TtUuVvWvXxYyZz')) 979 self.sampleEdit.setFixedHeight(self.sampleEdit.sizeHint().height() * 2) 980 981 self.setFontSelectAvail() 982 983 def setFontSelectAvail(self): 984 """Disable font selection if default font is checked. 985 986 Also set the controls with the current or default fonts. 987 """ 988 if self.defaultCheck.isChecked(): 989 font = self.readFont() 990 if font: 991 self.currentFont = font 992 self.setFont(self.printData.defaultFont) 993 self.fontBox.setEnabled(False) 994 else: 995 self.setFont(self.currentFont) 996 self.fontBox.setEnabled(True) 997 998 def setFont(self, font): 999 """Set the font selector to the given font. 1000 1001 Arguments: 1002 font -- the QFont to set. 1003 """ 1004 fontInfo = QFontInfo(font) 1005 family = fontInfo.family() 1006 matches = self.familyList.findItems(family, Qt.MatchExactly) 1007 if matches: 1008 self.familyList.setCurrentItem(matches[0]) 1009 self.familyList.scrollToItem(matches[0], 1010 QAbstractItemView.PositionAtTop) 1011 style = QFontDatabase().styleString(fontInfo) 1012 matches = self.styleList.findItems(style, Qt.MatchExactly) 1013 if matches: 1014 self.styleList.setCurrentItem(matches[0]) 1015 self.styleList.scrollToItem(matches[0]) 1016 else: 1017 self.styleList.setCurrentRow(0) 1018 self.styleList.scrollToItem(self.styleList.currentItem()) 1019 size = repr(fontInfo.pointSize()) 1020 matches = self.sizeList.findItems(size, Qt.MatchExactly) 1021 if matches: 1022 self.sizeList.setCurrentItem(matches[0]) 1023 self.sizeList.scrollToItem(matches[0]) 1024 1025 def updateFamily(self, currentItem, previousItem): 1026 """Update the family edit box and adjust the style and size options. 1027 1028 Arguments: 1029 currentItem -- the new list widget family item 1030 previousItem -- the previous list widget item 1031 """ 1032 family = currentItem.text() 1033 self.familyEdit.setText(family) 1034 if self.familyEdit.hasFocus(): 1035 self.familyEdit.selectAll() 1036 prevStyle = self.styleEdit.text() 1037 prevSize = self.sizeEdit.text() 1038 fontDb = QFontDatabase() 1039 styles = [style for style in fontDb.styles(family)] 1040 self.styleList.clear() 1041 self.styleList.addItems(styles) 1042 if prevStyle: 1043 try: 1044 num = styles.index(prevStyle) 1045 except ValueError: 1046 num = 0 1047 self.styleList.setCurrentRow(num) 1048 self.styleList.scrollToItem(self.styleList.currentItem()) 1049 sizes = [repr(size) for size in fontDb.pointSizes(family)] 1050 self.sizeList.clear() 1051 self.sizeList.addItems(sizes) 1052 if prevSize: 1053 try: 1054 num = sizes.index(prevSize) 1055 except ValueError: 1056 num = 0 1057 self.sizeList.setCurrentRow(num) 1058 self.sizeList.scrollToItem(self.sizeList.currentItem()) 1059 self.updateSample() 1060 1061 def updateStyle(self, currentItem, previousItem): 1062 """Update the style edit box. 1063 1064 Arguments: 1065 currentItem -- the new list widget style item 1066 previousItem -- the previous list widget item 1067 """ 1068 if currentItem: 1069 style = currentItem.text() 1070 self.styleEdit.setText(style) 1071 if self.styleEdit.hasFocus(): 1072 self.styleEdit.selectAll() 1073 self.updateSample() 1074 1075 def updateSize(self, currentItem, previousItem): 1076 """Update the size edit box. 1077 1078 Arguments: 1079 currentItem -- the new list widget size item 1080 previousItem -- the previous list widget item 1081 """ 1082 if currentItem: 1083 size = currentItem.text() 1084 self.sizeEdit.setText(size) 1085 if self.sizeEdit.hasFocus(): 1086 self.sizeEdit.selectAll() 1087 self.updateSample() 1088 1089 def updateSample(self): 1090 """Update the font sample edit font. 1091 """ 1092 font = self.readFont() 1093 if font: 1094 self.sampleEdit.setFont(font) 1095 1096 def readFont(self): 1097 """Return the selected font or None. 1098 """ 1099 family = self.familyEdit.text() 1100 style = self.styleEdit.text() 1101 size = self.sizeEdit.text() 1102 if family and style and size: 1103 return QFontDatabase().font(family, style, int(size)) 1104 return None 1105 1106 def saveChanges(self): 1107 """Update print data with current dialog settings. 1108 1109 Return True if saved settings have changed, False otherwise. 1110 """ 1111 if self.defaultCheck.isChecked(): 1112 if not self.printData.useDefaultFont: 1113 self.printData.useDefaultFont = True 1114 self.printData.mainFont = self.printData.defaultFont 1115 return True 1116 else: 1117 font = self.readFont() 1118 if font and (self.printData.useDefaultFont or 1119 font != self.printData.mainFont): 1120 self.printData.useDefaultFont = False 1121 self.printData.mainFont = font 1122 return True 1123 return False 1124 1125 1126_headerNames = (_('&Header Left'), _('Header C&enter'), _('Header &Right')) 1127_footerNames = (_('Footer &Left'), _('Footer Ce&nter'), _('Footer Righ&t')) 1128 1129class HeaderPage(QWidget): 1130 """Header/footer print option dialog page. 1131 """ 1132 def __init__(self, printData, parent=None): 1133 """Create the header/footer settings page. 1134 1135 Arguments: 1136 printData -- a reference to the PrintData class 1137 parent -- the parent dialog 1138 """ 1139 super().__init__(parent) 1140 self.printData = printData 1141 self.focusedEditor = None 1142 1143 topLayout = QGridLayout(self) 1144 fieldBox = QGroupBox(_('Fiel&ds')) 1145 topLayout.addWidget(fieldBox, 0, 0, 3, 1) 1146 fieldLayout = QVBoxLayout(fieldBox) 1147 self.fieldListWidget = FieldListWidget() 1148 fieldLayout.addWidget(self.fieldListWidget) 1149 fieldFormatButton = QPushButton(_('Field For&mat')) 1150 fieldLayout.addWidget(fieldFormatButton) 1151 fieldFormatButton.clicked.connect(self.showFieldFormatDialog) 1152 1153 self.addFieldButton = QPushButton('>>') 1154 topLayout.addWidget(self.addFieldButton, 0, 1) 1155 self.addFieldButton.setMaximumWidth(self.addFieldButton.sizeHint(). 1156 height()) 1157 self.addFieldButton.clicked.connect(self.addField) 1158 1159 self.delFieldButton = QPushButton('<<') 1160 topLayout.addWidget(self.delFieldButton, 1, 1) 1161 self.delFieldButton.setMaximumWidth(self.delFieldButton.sizeHint(). 1162 height()) 1163 self.delFieldButton.clicked.connect(self.delField) 1164 1165 headerFooterBox = QGroupBox(_('Header and Footer')) 1166 topLayout.addWidget(headerFooterBox, 0, 2, 2, 1) 1167 headerFooterLayout = QGridLayout(headerFooterBox) 1168 spacing = headerFooterLayout.spacing() 1169 headerFooterLayout.setVerticalSpacing(0) 1170 headerFooterLayout.setRowMinimumHeight(2, spacing) 1171 1172 self.headerEdits = self.addLineEdits(_headerNames, headerFooterLayout, 1173 0) 1174 self.footerEdits = self.addLineEdits(_footerNames, headerFooterLayout, 1175 3) 1176 self.loadContent() 1177 1178 def addLineEdits(self, names, layout, startRow): 1179 """Add line edits for header or footer. 1180 1181 Return a list of line edits added to the top layout. 1182 Arguments: 1183 names -- a list of label names 1184 layout -- the grid layout t use 1185 startRow -- the initial row number 1186 """ 1187 lineEdits = [] 1188 for num, name in enumerate(names): 1189 label = QLabel(name) 1190 layout.addWidget(label, startRow, num) 1191 lineEdit = configdialog.TitleEdit() 1192 layout.addWidget(lineEdit, startRow + 1, num) 1193 label.setBuddy(lineEdit) 1194 lineEdit.cursorPositionChanged.connect(self.setControlAvailability) 1195 lineEdit.focusIn.connect(self.setCurrentEditor) 1196 lineEdits.append(lineEdit) 1197 return lineEdits 1198 1199 def loadContent(self): 1200 """Load field names and header/footer text into the controls. 1201 """ 1202 self.fieldListWidget.addItems(self.printData.localControl.structure. 1203 treeFormats.fileInfoFormat.fieldNames()) 1204 self.fieldListWidget.setCurrentRow(0) 1205 for text, lineEdit in zip(splitHeaderFooter(self.printData.headerText), 1206 self.headerEdits): 1207 lineEdit.blockSignals(True) 1208 lineEdit.setText(text) 1209 lineEdit.blockSignals(False) 1210 for text, lineEdit in zip(splitHeaderFooter(self.printData.footerText), 1211 self.footerEdits): 1212 lineEdit.blockSignals(True) 1213 lineEdit.setText(text) 1214 lineEdit.blockSignals(False) 1215 self.focusedEditor = self.headerEdits[0] 1216 self.headerEdits[0].setFocus() 1217 self.setControlAvailability() 1218 1219 def setControlAvailability(self): 1220 """Set controls available based on text cursor movements. 1221 """ 1222 cursorInField = self.isCursorInField() 1223 self.addFieldButton.setEnabled(cursorInField == None) 1224 self.delFieldButton.setEnabled(cursorInField == True) 1225 1226 def setCurrentEditor(self, sender): 1227 """Set focusedEditor based on editor focus change signal. 1228 1229 Arguments: 1230 sender -- the line editor to focus 1231 """ 1232 self.focusedEditor = sender 1233 self.setControlAvailability() 1234 1235 def isCursorInField(self, selectField=False): 1236 """Return True if a field pattern encloses the cursor/selection. 1237 1238 Return False if the selection overlaps a field. 1239 Return None if there is no field at the cursor. 1240 Arguments: 1241 selectField -- select the entire field pattern if True. 1242 """ 1243 cursorPos = self.focusedEditor.cursorPosition() 1244 selectStart = self.focusedEditor.selectionStart() 1245 if selectStart < 0: 1246 selectStart = cursorPos 1247 elif selectStart == cursorPos: # backward selection 1248 cursorPos += len(self.focusedEditor.selectedText()) 1249 textLine = self.focusedEditor.text() 1250 for match in configdialog.fieldPattern.finditer(textLine): 1251 start = (match.start() if match.start() < selectStart < match.end() 1252 else None) 1253 end = (match.end() if match.start() < cursorPos < match.end() 1254 else None) 1255 if start != None and end != None: 1256 if selectField: 1257 self.focusedEditor.setSelection(start, end - start) 1258 return True 1259 if start != None or end != None: 1260 return False 1261 return None 1262 1263 def addField(self): 1264 """Add selected field to cursor pos in current line editor. 1265 """ 1266 fieldName = self.fieldListWidget.currentItem().text() 1267 self.focusedEditor.insert('{{*!{0}*}}'.format(fieldName)) 1268 self.focusedEditor.setFocus() 1269 1270 def delField(self): 1271 """Remove field from cursor pos in current line editor. 1272 """ 1273 if self.isCursorInField(True): 1274 self.focusedEditor.insert('') 1275 self.focusedEditor.setFocus() 1276 1277 def showFieldFormatDialog(self): 1278 """Show thw dialog used to set file info field formats. 1279 """ 1280 fileInfoFormat = (self.printData.localControl.structure.treeFormats. 1281 fileInfoFormat) 1282 fieldName = self.fieldListWidget.currentItem().text() 1283 field = fileInfoFormat.fieldDict[fieldName] 1284 dialog = HeaderFieldFormatDialog(field, self.printData.localControl, 1285 self) 1286 dialog.exec_() 1287 1288 def saveChanges(self): 1289 """Update print data with current dialog settings. 1290 1291 Return True if saved settings have changed, False otherwise. 1292 """ 1293 changed = False 1294 headerList = [lineEdit.text().replace('/', r'\/') for lineEdit in 1295 self.headerEdits] 1296 while len(headerList) > 1 and not headerList[-1]: 1297 del headerList[-1] 1298 text = '/'.join(headerList) 1299 if self.printData.headerText != text: 1300 self.printData.headerText = text 1301 changed = True 1302 footerList = [lineEdit.text().replace('/', r'\/') for lineEdit in 1303 self.footerEdits] 1304 while len(footerList) > 1 and not footerList[-1]: 1305 del footerList[-1] 1306 text = '/'.join(footerList) 1307 if self.printData.footerText != text: 1308 self.printData.footerText = text 1309 changed = True 1310 return changed 1311 1312 1313class FieldListWidget(QListWidget): 1314 """List widget for fields with smaller width size hint. 1315 """ 1316 def __init__(self, parent=None): 1317 """Create the list widget. 1318 1319 Arguments: 1320 parent -- the parent dialog 1321 """ 1322 super().__init__(parent) 1323 1324 def sizeHint(self): 1325 """Return a size with a smaller width. 1326 """ 1327 return QSize(120, 100) 1328 1329 1330class HeaderFieldFormatDialog(QDialog): 1331 """Dialog to modify file info field formats used in headers and footers. 1332 """ 1333 def __init__(self, field, localControl, parent=None): 1334 """Create the field format dialog. 1335 1336 Arguments: 1337 field -- the field to be modified 1338 localControl -- a ref to the control to save changes and undo 1339 """ 1340 super().__init__(parent) 1341 self.field = field 1342 self.localControl = localControl 1343 1344 self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | 1345 Qt.WindowCloseButtonHint) 1346 self.setWindowTitle(_('Field Format for "{0}"').format(field.name)) 1347 topLayout = QVBoxLayout(self) 1348 self.setLayout(topLayout) 1349 1350 self.formatBox = QGroupBox(_('Output &Format')) 1351 topLayout.addWidget(self.formatBox) 1352 formatLayout = QHBoxLayout(self.formatBox) 1353 self.formatEdit = QLineEdit() 1354 formatLayout.addWidget(self.formatEdit) 1355 self.helpButton = QPushButton(_('Format &Help')) 1356 formatLayout.addWidget(self.helpButton) 1357 self.helpButton.clicked.connect(self.formatHelp) 1358 1359 extraBox = QGroupBox(_('Extra Text')) 1360 topLayout.addWidget(extraBox) 1361 extraLayout = QVBoxLayout(extraBox) 1362 spacing = extraLayout.spacing() 1363 extraLayout.setSpacing(0) 1364 prefixLabel = QLabel(_('&Prefix')) 1365 extraLayout.addWidget(prefixLabel) 1366 self.prefixEdit = QLineEdit() 1367 extraLayout.addWidget(self.prefixEdit) 1368 prefixLabel.setBuddy(self.prefixEdit) 1369 extraLayout.addSpacing(spacing) 1370 suffixLabel = QLabel(_('&Suffix')) 1371 extraLayout.addWidget(suffixLabel) 1372 self.suffixEdit = QLineEdit() 1373 extraLayout.addWidget(self.suffixEdit) 1374 suffixLabel.setBuddy(self.suffixEdit) 1375 1376 ctrlLayout = QHBoxLayout() 1377 topLayout.addLayout(ctrlLayout) 1378 ctrlLayout.addStretch() 1379 okButton = QPushButton(_('&OK')) 1380 ctrlLayout.addWidget(okButton) 1381 okButton.clicked.connect(self.accept) 1382 cancelButton = QPushButton(_('&Cancel')) 1383 ctrlLayout.addWidget(cancelButton) 1384 cancelButton.clicked.connect(self.reject) 1385 1386 self.prefixEdit.setText(self.field.prefix) 1387 self.suffixEdit.setText(self.field.suffix) 1388 self.formatEdit.setText(self.field.format) 1389 1390 self.formatBox.setEnabled(self.field.defaultFormat != '') 1391 1392 def formatHelp(self): 1393 """Provide a format help menu based on a button signal. 1394 """ 1395 menu = QMenu(self) 1396 self.formatHelpDict = {} 1397 for descript, key in self.field.getFormatHelpMenuList(): 1398 if descript: 1399 self.formatHelpDict[descript] = key 1400 menu.addAction(descript) 1401 else: 1402 menu.addSeparator() 1403 menu.popup(self.helpButton. 1404 mapToGlobal(QPoint(0, self.helpButton.height()))) 1405 menu.triggered.connect(self.insertFormat) 1406 1407 def insertFormat(self, action): 1408 """Insert format text from help menu into edit box. 1409 1410 Arguments: 1411 action -- the action from the help menu 1412 """ 1413 self.formatEdit.insert(self.formatHelpDict[action.text()]) 1414 1415 def accept(self): 1416 """Set changes after OK is hit""" 1417 prefix = self.prefixEdit.text() 1418 suffix = self.suffixEdit.text() 1419 format = self.formatEdit.text() 1420 if (self.field.prefix != prefix or self.field.suffix != suffix or 1421 self.field.format != format): 1422 undo.FormatUndo(self.localControl.structure.undoList, 1423 self.localControl.structure.treeFormats, 1424 treeformats.TreeFormats()) 1425 self.field.prefix = prefix 1426 self.field.suffix = suffix 1427 self.field.format = format 1428 self.localControl.setModified() 1429 super().accept() 1430 1431 1432_headerSplitRe = re.compile(r'(?<!\\)/') 1433 1434def splitHeaderFooter(combinedText): 1435 """Return a list of header/footer parts from the text, separated by "/". 1436 1437 Backslash escapes avoid splits. 1438 Arguments: 1439 combinedText -- the text to split 1440 """ 1441 textList = _headerSplitRe.split(combinedText) 1442 return [text.replace(r'\/', '/') for text in textList] 1443