1#!/usr/bin/env python3 2 3#**************************************************************************** 4# fontset.py, provides storage/retrieval and a dialog for custom fonts 5# 6# ConvertAll, a units conversion program 7# Copyright (C) 2019, 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 WITTHOUT ANY WARRANTY. See the included LICENSE file for details. 13#***************************************************************************** 14 15from PyQt5.QtCore import (QSize, Qt) 16from PyQt5.QtGui import (QFontDatabase, QFontInfo, QIntValidator) 17from PyQt5.QtWidgets import (QAbstractItemView, QCheckBox, QDialog, 18 QGridLayout, QGroupBox, QHBoxLayout, QLabel, 19 QLineEdit, QListWidget, QPushButton, QVBoxLayout) 20 21 22class CustomFontDialog(QDialog): 23 """Dialog for selecting a custom font. 24 """ 25 def __init__(self, sysFont, currentFont=None, parent=None): 26 """Create a font customization dialog. 27 """ 28 super().__init__(parent) 29 self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | 30 Qt.WindowCloseButtonHint) 31 self.setWindowTitle(_('Customize Font')) 32 self.sysFont = sysFont 33 self.currentFont = currentFont 34 35 topLayout = QVBoxLayout(self) 36 self.setLayout(topLayout) 37 defaultBox = QGroupBox(_('Default Font')) 38 topLayout.addWidget(defaultBox) 39 defaultLayout = QVBoxLayout(defaultBox) 40 self.defaultCheck = QCheckBox(_('&Use system default font')) 41 defaultLayout.addWidget(self.defaultCheck) 42 self.defaultCheck.setChecked(self.currentFont == None) 43 self.defaultCheck.clicked.connect(self.setFontSelectAvail) 44 45 self.fontBox = QGroupBox(_('Select Font')) 46 topLayout.addWidget(self.fontBox) 47 fontLayout = QGridLayout(self.fontBox) 48 spacing = fontLayout.spacing() 49 fontLayout.setSpacing(0) 50 51 label = QLabel(_('&Font')) 52 fontLayout.addWidget(label, 0, 0) 53 label.setIndent(2) 54 self.familyEdit = QLineEdit() 55 fontLayout.addWidget(self.familyEdit, 1, 0) 56 self.familyEdit.setReadOnly(True) 57 self.familyList = SmallListWidget() 58 fontLayout.addWidget(self.familyList, 2, 0) 59 label.setBuddy(self.familyList) 60 self.familyEdit.setFocusProxy(self.familyList) 61 fontLayout.setColumnMinimumWidth(1, spacing) 62 families = [family for family in QFontDatabase().families()] 63 families.sort(key=str.lower) 64 self.familyList.addItems(families) 65 self.familyList.currentItemChanged.connect(self.updateFamily) 66 67 label = QLabel(_('Font st&yle')) 68 fontLayout.addWidget(label, 0, 2) 69 label.setIndent(2) 70 self.styleEdit = QLineEdit() 71 fontLayout.addWidget(self.styleEdit, 1, 2) 72 self.styleEdit.setReadOnly(True) 73 self.styleList = SmallListWidget() 74 fontLayout.addWidget(self.styleList, 2, 2) 75 label.setBuddy(self.styleList) 76 self.styleEdit.setFocusProxy(self.styleList) 77 fontLayout.setColumnMinimumWidth(3, spacing) 78 self.styleList.currentItemChanged.connect(self.updateStyle) 79 80 label = QLabel(_('Si&ze')) 81 fontLayout.addWidget(label, 0, 4) 82 label.setIndent(2) 83 self.sizeEdit = QLineEdit() 84 fontLayout.addWidget(self.sizeEdit, 1, 4) 85 self.sizeEdit.setFocusPolicy(Qt.ClickFocus) 86 validator = QIntValidator(1, 512, self) 87 self.sizeEdit.setValidator(validator) 88 self.sizeList = SmallListWidget() 89 fontLayout.addWidget(self.sizeList, 2, 4) 90 label.setBuddy(self.sizeList) 91 self.sizeList.currentItemChanged.connect(self.updateSize) 92 93 fontLayout.setColumnStretch(0, 30) 94 fontLayout.setColumnStretch(2, 25) 95 fontLayout.setColumnStretch(4, 10) 96 97 sampleBox = QGroupBox(_('Sample')) 98 topLayout.addWidget(sampleBox) 99 sampleLayout = QVBoxLayout(sampleBox) 100 self.sampleEdit = QLineEdit() 101 sampleLayout.addWidget(self.sampleEdit) 102 self.sampleEdit.setAlignment(Qt.AlignCenter) 103 self.sampleEdit.setText(_('AaBbCcDdEeFfGg...TtUuVvWvXxYyZz')) 104 self.sampleEdit.setFixedHeight(self.sampleEdit.sizeHint().height() * 2) 105 106 ctrlLayout = QHBoxLayout() 107 topLayout.addLayout(ctrlLayout) 108 ctrlLayout.addStretch() 109 self.okButton = QPushButton(_('&OK')) 110 ctrlLayout.addWidget(self.okButton) 111 self.okButton.clicked.connect(self.accept) 112 cancelButton = QPushButton(_('&Cancel')) 113 ctrlLayout.addWidget(cancelButton) 114 cancelButton.clicked.connect(self.reject) 115 116 self.setFontSelectAvail() 117 118 def setFontSelectAvail(self): 119 """Disable font selection if default font is checked. 120 121 Also set the controls with the current or default fonts. 122 """ 123 if self.currentFont and not self.defaultCheck.isChecked(): 124 self.setFont(self.currentFont) 125 else: 126 self.setFont(self.sysFont) 127 self.fontBox.setEnabled(not self.defaultCheck.isChecked()) 128 129 def setFont(self, font): 130 """Set the font selector to the given font. 131 132 Arguments: 133 font -- the QFont to set. 134 """ 135 fontInfo = QFontInfo(font) 136 family = fontInfo.family() 137 matches = self.familyList.findItems(family, Qt.MatchExactly) 138 if matches: 139 self.familyList.setCurrentItem(matches[0]) 140 self.familyList.scrollToItem(matches[0], 141 QAbstractItemView.PositionAtTop) 142 style = QFontDatabase().styleString(fontInfo) 143 matches = self.styleList.findItems(style, Qt.MatchExactly) 144 if matches: 145 self.styleList.setCurrentItem(matches[0]) 146 self.styleList.scrollToItem(matches[0]) 147 else: 148 self.styleList.setCurrentRow(0) 149 self.styleList.scrollToItem(self.styleList.currentItem()) 150 size = repr(fontInfo.pointSize()) 151 matches = self.sizeList.findItems(size, Qt.MatchExactly) 152 if matches: 153 self.sizeList.setCurrentItem(matches[0]) 154 self.sizeList.scrollToItem(matches[0]) 155 156 def updateFamily(self, currentItem, previousItem): 157 """Update the family edit box and adjust the style and size options. 158 159 Arguments: 160 currentItem -- the new list widget family item 161 previousItem -- the previous list widget item 162 """ 163 family = currentItem.text() 164 self.familyEdit.setText(family) 165 if self.familyEdit.hasFocus(): 166 self.familyEdit.selectAll() 167 prevStyle = self.styleEdit.text() 168 prevSize = self.sizeEdit.text() 169 fontDb = QFontDatabase() 170 styles = [style for style in fontDb.styles(family)] 171 self.styleList.clear() 172 self.styleList.addItems(styles) 173 if prevStyle: 174 try: 175 num = styles.index(prevStyle) 176 except ValueError: 177 num = 0 178 self.styleList.setCurrentRow(num) 179 self.styleList.scrollToItem(self.styleList.currentItem()) 180 sizes = [repr(size) for size in fontDb.pointSizes(family)] 181 self.sizeList.clear() 182 self.sizeList.addItems(sizes) 183 if prevSize: 184 try: 185 num = sizes.index(prevSize) 186 except ValueError: 187 num = 0 188 self.sizeList.setCurrentRow(num) 189 self.sizeList.scrollToItem(self.sizeList.currentItem()) 190 self.updateSample() 191 192 def updateStyle(self, currentItem, previousItem): 193 """Update the style edit box. 194 195 Arguments: 196 currentItem -- the new list widget style item 197 previousItem -- the previous list widget item 198 """ 199 if currentItem: 200 style = currentItem.text() 201 self.styleEdit.setText(style) 202 if self.styleEdit.hasFocus(): 203 self.styleEdit.selectAll() 204 self.updateSample() 205 206 def updateSize(self, currentItem, previousItem): 207 """Update the size edit box. 208 209 Arguments: 210 currentItem -- the new list widget size item 211 previousItem -- the previous list widget item 212 """ 213 if currentItem: 214 size = currentItem.text() 215 self.sizeEdit.setText(size) 216 if self.sizeEdit.hasFocus(): 217 self.sizeEdit.selectAll() 218 self.updateSample() 219 220 def updateSample(self): 221 """Update the font sample edit font. 222 """ 223 font = self.readFont() 224 if font: 225 self.sampleEdit.setFont(font) 226 227 def readFont(self): 228 """Return the selected font or None. 229 """ 230 family = self.familyEdit.text() 231 style = self.styleEdit.text() 232 size = self.sizeEdit.text() 233 if family and style and size: 234 return QFontDatabase().font(family, style, int(size)) 235 return None 236 237 def resultingFont(self): 238 """Return the selected font or None if system font. 239 """ 240 if self.defaultCheck.isChecked(): 241 return None 242 return self.readFont() 243 244 245class SmallListWidget(QListWidget): 246 """ListWidget with a smaller size hint. 247 """ 248 def __init__(self, parent=None): 249 """Initialize the widget. 250 251 Arguments: 252 parent -- the parent, if given 253 """ 254 super().__init__(parent) 255 256 def sizeHint(self): 257 """Return smaller width. 258 """ 259 itemHeight = self.visualItemRect(self.item(0)).height() 260 return QSize(100, itemHeight * 6) 261