def set_font(self, font: QFont = None): qfd = QFontDatabase() if font: info = QFontInfo(font) if info.styleHint() != QFont.Monospace: self.logger.warning("font: Please use monospaced font! " f"Unsupported font {info.family}.") font = qfd.systemFont(QFontDatabase.FixedFont) elif "Menlo" in qfd.families(): font = QFont("Menlo") info = QFontInfo(font) else: font = qfd.systemFont(QFontDatabase.FixedFont) info = QFontInfo(font) font.setPointSize(12) self.font = font metrics = QFontMetrics(font) self.char_width = metrics.horizontalAdvance("A") self.char_height = metrics.height() self.line_height = int(self.char_height * 1.2) self.logger.info(f"font: Font {info.family()} selected, character " f"size {self.char_width}x{self.char_height}.") self.row_len = int(self._width / self.char_width) self.col_len = int(self._height / self.line_height)
def get_fonts(fdb: QFontDatabase): fonts = fdb.families() fonts_final = [] for font in fonts: try: ImageFont.truetype('c:/windows/fonts/{}.ttf'.format(font)) fonts_final.append(font) except OSError: ... return fonts_final
def label_listfonts(session): '''Report available fonts.''' has_graphics = session.main_view.render is not None if not has_graphics: from chimerax.core.errors import LimitationError raise LimitationError( "Unable to do list fonts without being able to render images") from PyQt5.QtGui import QFontDatabase fdb = QFontDatabase() fnames = list(fdb.families()) fnames.sort() session.logger.info('%d fonts available:\n%s' % (len(fnames), '\n'.join(fnames)))
def __init__(self, parent=None): super(WaterQualityView, self).__init__(parent) self.scrollarea = None self.model = None self.setFocusPolicy(Qt.StrongFocus) self.selectedRow = -1 self.flowfont = self.font() size = self.font().pointSize() if platform.system() == "Windows": fontDb = QFontDatabase() for face in [face.lower() for face in fontDb.families()]: if face.find("unicode"): self.flowfont = QFont(face, size) break else: self.flowfont = QFont("symbol", size) WaterQualityView.FLOWCHARS = (chr(0xAC), chr(0xAE), chr(0xDE))
def printFonts(self, filter_str): result = [] db = QFontDatabase() fonts = QFontDatabase.families(db) for font in fonts: print(font, '|', filter_str) if filter_str == None: print("A") result.append(str(font)) elif filter_str in font: print("B") result.append(font) return result
def __init__(self, parent): super().__init__(parent) # QScintilla only uses the primary font family rather than searching through alternatives. # So we need to do this manually... db = QFontDatabase() fonts = set(db.families(QFontDatabase.WritingSystem.Latin)) for font_name in self._FONTS: if font_name in fonts: font = QFont(font_name, 10) break else: font = db.systemFont(QFontDatabase.FixedFont) self.setDefaultFont(font) for style, (color, font_kwargs) in self._STYLE_SETTINGS.items(): self.setColor(QColor(color), style) self.setFont(QFont(font.family(), font.pointSize(), **font_kwargs), style) self.getStyling = partial(parent.SendScintilla, QsciScintilla.SCI_GETSTYLEAT)
def __init__(self, editor, parent=None): super(FontsComboBox, self).__init__(parent) self.editor = editor _fontDataBase = QFontDatabase() self.addItems(_fontDataBase.families()) self.setCurrentIndex(self.findText(self.editor.font)) com = QCompleter() com.setCaseSensitivity(Qt.CaseInsensitive) com.setCompletionMode(QCompleter.PopupCompletion) # Style sheet to set false on combobox-popup self.setStyleSheet("QComboBox { combobox-popup: 0; }") self.setMaxVisibleItems(10) self.setCompleter(com) self.currentIndexChanged.connect(self._currentIndexChanged)
def loadFonts(self): database = QFontDatabase() for family in database.families(): self.fontBox.insertItem(999999, family)
class FontAssets(GWAssets): """ A utility for discovering and loading any font assets associated with the application. font_specs = FontAssets("C:/app/assets/fonts") """ def __init__(self, asset_path: Union[Path, str]) -> None: super().__init__(asset_path) self.gui_font_db = QFontDatabase() self.load_new_fonts() self.gui_font = qApp.font() self.gui_font_fixed = QFontDatabase.systemFont(QFontDatabase.FixedFont) def apply_theme(self): # No themes for fonts pass def font_asset_list(self): """Scan the font assets folder for all the subfolders (each assumed to be a font family).""" if self.available_font_families: return self.available_font_families for child in self.asset_path.iterdir(): if child.is_dir(): self.available_font_families.append(child.name) return self.available_font_families def load_new_fonts(self): """Load any fonts that the system doesn't already have installed.""" new_fonts = [] for family in self.font_asset_list(): font_dir: Path = self.asset_path / family if font_dir.is_dir: if family not in self.gui_font_db.families(): for child in font_dir.iterdir(): if child.is_file() and child.suffix in [ ".ttf", ".otf" ]: new_fonts.append(child) new_font: Path for new_font in new_fonts: LOG.diagnostic( f"Loading font asset: {new_font.relative_to(self.asset_path)}") id = self.gui_font_db.addApplicationFont(new_font) if id < 0: LOG.error( f"Failed to add font: {new_font.relative_to(self.asset_path)}" ) return def basic_font_metrics(self, font: QFont) -> BasicFontMetrics: """ Calculates some commonly needed font metrics and returns them in a BasicFontMetrics tuple. """ qMetric = QFontMetrics(font) info = BasicFontMetrics.make([ font.pointSizeF(), qMetric.height(), qMetric.ascent(), qMetric.boundingRect("N").height(), qMetric.boundingRect("N").width(), ]) LOG.diagnostic("GUI Font Family: %s" % font.family()) LOG.diagnostic("GUI Font Point Size: %.2f" % info.points) LOG.diagnostic("GUI Font Pixel Size: %d" % info.pixels) LOG.diagnostic("GUI Base Icon Size: %d" % info.icon_size) LOG.diagnostic("Text 'N' Height: %d" % info.n_height) LOG.diagnostic("Text 'N' Width: %d" % info.n_width) return info def get_text_width(self, font: QFont, proposed_text): """Calculates the width needed to contain a given piece of text.""" return int(ceil( QFontMetrics(font).boundingRect(proposed_text).width()))
# coding = utf-8 import sys from PyQt5.QtGui import QFont, QFontDatabase, QPalette, QColor from PyQt5.QtWidgets import QWidget, QApplication, QPushButton app = QApplication(sys.argv) widget = QWidget() widget.setWindowTitle('调色板与字体') widget.setGeometry(300, 300, 400, 300) db = QFontDatabase() # fonts = db.families(QFontDatabase.SimplifiedChinese) fonts = db.families(QFontDatabase.Any) for f in fonts: print(f) font = QFont('Weibei SC', 30) widget.setFont(font) button1 = QPushButton(parent=widget, text='窗体字体') button1.setGeometry(10, 10, 200, 80) button1.setFont(font) pal = QPalette() pal.setColor(QPalette.Background, QColor(255, 0, 0)) # QPalette.ToolTipText作用于QTooltip,而不是窗体的tooltip属性 # pal.setColor(QPalette.ToolTipText, QColor(255, 0, 255)) widget.setPalette(pal) # 提示信息的颜色可以使用样式表(很多文本类的属性都可以) widget.setToolTip('<font color=red>提示颜色</font>') widget.show()
class GuiTheme: def __init__(self, theParent): self.mainConf = nw.CONFIG self.theParent = theParent self.theIcons = GuiIcons(self.theParent) self.guiPalette = QPalette() self.guiPath = "gui" self.fontPath = "fonts" self.syntaxPath = "syntax" self.cssName = "style.qss" self.confName = "theme.conf" self.themeList = [] self.syntaxList = [] # Loaded Theme Settings ## Theme self.themeName = "" self.themeDescription = "" self.themeAuthor = "" self.themeCredit = "" self.themeUrl = "" self.themeLicense = "" self.themeLicenseUrl = "" ## GUI self.statNone = [120, 120, 120] self.statUnsaved = [200, 15, 39] self.statSaved = [2, 133, 37] self.helpText = [0, 0, 0] # Loaded Syntax Settings ## Main self.syntaxName = "" self.syntaxDescription = "" self.syntaxAuthor = "" self.syntaxCredit = "" self.syntaxUrl = "" self.syntaxLicense = "" self.syntaxLicenseUrl = "" ## Colours self.colBack = [255, 255, 255] self.colText = [0, 0, 0] self.colLink = [0, 0, 0] self.colHead = [0, 0, 0] self.colHeadH = [0, 0, 0] self.colEmph = [0, 0, 0] self.colDialN = [0, 0, 0] self.colDialD = [0, 0, 0] self.colDialS = [0, 0, 0] self.colHidden = [0, 0, 0] self.colKey = [0, 0, 0] self.colVal = [0, 0, 0] self.colSpell = [0, 0, 0] self.colTagErr = [0, 0, 0] self.colRepTag = [0, 0, 0] self.colMod = [0, 0, 0] # Changeable Settings self.guiTheme = None self.guiSyntax = None self.themeRoot = None self.themePath = None self.syntaxFile = None self.confFile = None self.cssFile = None self.guiFontDB = QFontDatabase() self.loadFonts() self.updateFont() self.updateTheme() self.theIcons.updateTheme() self.getIcon = self.theIcons.getIcon self.getPixmap = self.theIcons.getPixmap self.loadDecoration = self.theIcons.loadDecoration # Extract Other Info self.guiDPI = qApp.primaryScreen().logicalDotsPerInchX() self.guiScale = qApp.primaryScreen().logicalDotsPerInchX() / 96.0 self.mainConf.guiScale = self.guiScale logger.verbose("GUI DPI: %.1f" % self.guiDPI) logger.verbose("GUI Scale: %.2f" % self.guiScale) # Fonts self.guiFont = qApp.font() qMetric = QFontMetrics(self.guiFont) self.fontPointSize = self.guiFont.pointSizeF() self.fontPixelSize = int(round(qMetric.height())) self.baseIconSize = int(round(qMetric.ascent())) self.textNHeight = qMetric.boundingRect("N").height() self.textNWidth = qMetric.boundingRect("N").width() # Monospace Font self.guiFontFixed = QFont() self.guiFontFixed.setPointSizeF(0.95 * self.fontPointSize) self.guiFontFixed.setFamily( QFontDatabase.systemFont(QFontDatabase.FixedFont).family()) logger.verbose("GUI Font Family: %s" % self.guiFont.family()) logger.verbose("GUI Font Point Size: %.2f" % self.fontPointSize) logger.verbose("GUI Font Pixel Size: %d" % self.fontPixelSize) logger.verbose("GUI Base Icon Size: %d" % self.baseIconSize) logger.verbose("Text 'N' Height: %d" % self.textNHeight) logger.verbose("Text 'N' Width: %d" % self.textNWidth) # Internal Mapping self.makeAlert = self.theParent.makeAlert self.tr = partial(QCoreApplication.translate, "GuiTheme") return ## # Methods ## def getTextWidth(self, theText, theFont=None): """Returns the width needed to contain a given piece of text. """ if isinstance(theFont, QFont): qMetrics = QFontMetrics(theFont) else: qMetrics = QFontMetrics(self.guiFont) return int(ceil(qMetrics.boundingRect(theText).width())) ## # Actions ## def loadFonts(self): """Add the fonts in the assets fonts folder to the app. """ logger.debug("Loading additional fonts") ttfList = [] fontAssets = os.path.join(self.mainConf.assetPath, self.fontPath) for fontFam in os.listdir(fontAssets): fontDir = os.path.join(fontAssets, fontFam) if os.path.isdir(fontDir): logger.verbose("Found font: %s" % fontFam) if fontFam not in self.guiFontDB.families(): for fontFile in os.listdir(fontDir): ttfFile = os.path.join(fontDir, fontFile) if os.path.isfile(ttfFile) and fontFile.endswith( ".ttf"): ttfList.append(ttfFile) for ttfFile in ttfList: relPath = os.path.relpath(ttfFile, fontAssets) logger.verbose("Adding font: %s" % relPath) fontID = self.guiFontDB.addApplicationFont(ttfFile) if fontID < 0: logger.error("Failed to add font: %s" % relPath) return def updateFont(self): """Update the GUI's font style from settings. """ theFont = QFont() if self.mainConf.guiFont not in self.guiFontDB.families(): if self.mainConf.osWindows: if "Arial" in self.guiFontDB.families(): theFont.setFamily("Arial") else: # On Windows, fall back to Cantarell provided by novelWriter theFont.setFamily("Cantarell") theFont.setPointSize(10) else: theFont = self.guiFontDB.systemFont(QFontDatabase.GeneralFont) self.mainConf.guiFont = theFont.family() self.mainConf.guiFontSize = theFont.pointSize() else: theFont.setFamily(self.mainConf.guiFont) theFont.setPointSize(self.mainConf.guiFontSize) qApp.setFont(theFont) return def updateTheme(self): """Update the GUI theme from theme files. """ self.guiTheme = self.mainConf.guiTheme self.guiSyntax = self.mainConf.guiSyntax self.themeRoot = self.mainConf.themeRoot self.themePath = os.path.join(self.mainConf.themeRoot, self.guiPath, self.guiTheme) self.syntaxFile = os.path.join(self.themeRoot, self.syntaxPath, self.guiSyntax + ".conf") self.confFile = os.path.join(self.themePath, self.confName) self.cssFile = os.path.join(self.themePath, self.cssName) self.loadTheme() self.loadSyntax() # Update dependant colours backCol = qApp.palette().window().color() textCol = qApp.palette().windowText().color() backLCol = backCol.lightnessF() textLCol = textCol.lightnessF() if backLCol > textLCol: helpLCol = textLCol + 0.65 * (backLCol - textLCol) else: helpLCol = backLCol + 0.65 * (textLCol - backLCol) self.helpText = [int(255 * helpLCol)] * 3 return True def loadTheme(self): """Load the currently specified GUI theme. """ logger.debug("Loading theme files") logger.debug("System icon theme is '%s'" % str(QIcon.themeName())) # CSS File cssData = "" try: if os.path.isfile(self.cssFile): with open(self.cssFile, mode="r", encoding="utf8") as inFile: cssData = inFile.read() except Exception: logger.error("Could not load theme css file") nw.logException() return False # Config File confParser = configparser.ConfigParser() try: with open(self.confFile, mode="r", encoding="utf8") as inFile: confParser.read_file(inFile) except Exception: logger.error("Could not load theme settings from: %s" % self.confFile) nw.logException() return False ## Main cnfSec = "Main" if confParser.has_section(cnfSec): self.themeName = self._parseLine(confParser, cnfSec, "name", "") self.themeDescription = self._parseLine(confParser, cnfSec, "description", "N/A") self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A") self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A") self.themeUrl = self._parseLine(confParser, cnfSec, "url", "") self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A") self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "") ## Palette cnfSec = "Palette" if confParser.has_section(cnfSec): self._setPalette(confParser, cnfSec, "window", QPalette.Window) self._setPalette(confParser, cnfSec, "windowtext", QPalette.WindowText) self._setPalette(confParser, cnfSec, "base", QPalette.Base) self._setPalette(confParser, cnfSec, "alternatebase", QPalette.AlternateBase) self._setPalette(confParser, cnfSec, "text", QPalette.Text) self._setPalette(confParser, cnfSec, "tooltipbase", QPalette.ToolTipBase) self._setPalette(confParser, cnfSec, "tooltiptext", QPalette.ToolTipText) self._setPalette(confParser, cnfSec, "button", QPalette.Button) self._setPalette(confParser, cnfSec, "buttontext", QPalette.ButtonText) self._setPalette(confParser, cnfSec, "brighttext", QPalette.BrightText) self._setPalette(confParser, cnfSec, "highlight", QPalette.Highlight) self._setPalette(confParser, cnfSec, "highlightedtext", QPalette.HighlightedText) self._setPalette(confParser, cnfSec, "link", QPalette.Link) self._setPalette(confParser, cnfSec, "linkvisited", QPalette.LinkVisited) ## GUI cnfSec = "GUI" if confParser.has_section(cnfSec): self.statNone = self._loadColour(confParser, cnfSec, "statusnone") self.statUnsaved = self._loadColour(confParser, cnfSec, "statusunsaved") self.statSaved = self._loadColour(confParser, cnfSec, "statussaved") # Apply Styles qApp.setStyleSheet(cssData) qApp.setPalette(self.guiPalette) logger.info("Loaded theme '%s'" % self.guiTheme) return True def loadSyntax(self): """Load the currently specified syntax highlighter theme. """ logger.debug("Loading syntax theme files") confParser = configparser.ConfigParser() try: with open(self.syntaxFile, mode="r", encoding="utf8") as inFile: confParser.read_file(inFile) except Exception: logger.error("Could not load syntax colours from: %s" % self.syntaxFile) nw.logException() return False ## Main cnfSec = "Main" if confParser.has_section(cnfSec): self.syntaxName = self._parseLine(confParser, cnfSec, "name", "") self.syntaxDescription = self._parseLine(confParser, cnfSec, "description", "") self.syntaxAuthor = self._parseLine(confParser, cnfSec, "author", "") self.syntaxCredit = self._parseLine(confParser, cnfSec, "credit", "") self.syntaxUrl = self._parseLine(confParser, cnfSec, "url", "") self.syntaxLicense = self._parseLine(confParser, cnfSec, "license", "") self.syntaxLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "") ## Syntax cnfSec = "Syntax" if confParser.has_section(cnfSec): self.colBack = self._loadColour(confParser, cnfSec, "background") self.colText = self._loadColour(confParser, cnfSec, "text") self.colLink = self._loadColour(confParser, cnfSec, "link") self.colHead = self._loadColour(confParser, cnfSec, "headertext") self.colHeadH = self._loadColour(confParser, cnfSec, "headertag") self.colEmph = self._loadColour(confParser, cnfSec, "emphasis") self.colDialN = self._loadColour(confParser, cnfSec, "straightquotes") self.colDialD = self._loadColour(confParser, cnfSec, "doublequotes") self.colDialS = self._loadColour(confParser, cnfSec, "singlequotes") self.colHidden = self._loadColour(confParser, cnfSec, "hidden") self.colKey = self._loadColour(confParser, cnfSec, "keyword") self.colVal = self._loadColour(confParser, cnfSec, "value") self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline") self.colTagErr = self._loadColour(confParser, cnfSec, "tagerror") self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag") self.colMod = self._loadColour(confParser, cnfSec, "modifier") logger.info("Loaded syntax theme '%s'" % self.guiSyntax) return True def listThemes(self): """Scan the GUI themes folder and list all themes. """ if self.themeList: return self.themeList confParser = configparser.ConfigParser() for themeDir in os.listdir( os.path.join(self.mainConf.themeRoot, self.guiPath)): themeConf = os.path.join(self.mainConf.themeRoot, self.guiPath, themeDir, self.confName) logger.verbose("Checking theme config for '%s'" % themeDir) try: with open(themeConf, mode="r", encoding="utf8") as inFile: confParser.read_file(inFile) except Exception as e: self.makeAlert( [self.tr("Could not load theme config file."), str(e)], nwAlert.ERROR) continue themeName = "" if confParser.has_section("Main"): if confParser.has_option("Main", "name"): themeName = confParser.get("Main", "name") logger.verbose("Theme name is '%s'" % themeName) if themeName != "": self.themeList.append((themeDir, themeName)) self.themeList = sorted(self.themeList, key=lambda x: x[1]) return self.themeList def listSyntax(self): """Scan the syntax themes folder and list all themes. """ if self.syntaxList: return self.syntaxList confParser = configparser.ConfigParser() syntaxDir = os.path.join(self.mainConf.themeRoot, self.syntaxPath) for syntaxFile in os.listdir(syntaxDir): syntaxPath = os.path.join(syntaxDir, syntaxFile) if not os.path.isfile(syntaxPath): continue logger.verbose("Checking theme syntax for '%s'" % syntaxFile) try: with open(syntaxPath, mode="r", encoding="utf8") as inFile: confParser.read_file(inFile) except Exception as e: self.makeAlert( [self.tr("Could not load syntax file."), str(e)], nwAlert.ERROR) return [] syntaxName = "" if confParser.has_section("Main"): if confParser.has_option("Main", "name"): syntaxName = confParser.get("Main", "name") if len(syntaxFile) > 5 and syntaxName != "": self.syntaxList.append((syntaxFile[:-5], syntaxName)) logger.verbose("Syntax name is '%s'" % syntaxName) self.syntaxList = sorted(self.syntaxList, key=lambda x: x[1]) return self.syntaxList ## # Internal Functions ## def _loadColour(self, confParser, cnfSec, cnfName): """Load a colour value from a config string. """ if confParser.has_option(cnfSec, cnfName): inData = confParser.get(cnfSec, cnfName).split(",") outData = [] try: outData.append(int(inData[0])) outData.append(int(inData[1])) outData.append(int(inData[2])) except Exception: logger.error( "Could not load theme colours for '%s' from config file" % cnfName) outData = [0, 0, 0] else: logger.warning( "Could not find theme colours for '%s' in config file" % cnfName) outData = [0, 0, 0] return outData def _setPalette(self, confParser, cnfSec, cnfName, paletteVal): """Set a palette colour value from a config string. """ readCol = [] if confParser.has_option(cnfSec, cnfName): inData = confParser.get(cnfSec, cnfName).split(",") try: readCol.append(int(inData[0])) readCol.append(int(inData[1])) readCol.append(int(inData[2])) except Exception: logger.error( "Could not load theme colours for '%s' from config file" % cnfName) return if len(readCol) == 3: self.guiPalette.setColor(paletteVal, QColor(*readCol)) return def _parseLine(self, confParser, cnfSec, cnfName, cnfDefault): """Simple wrapper for the config parser to check that the entry exists before attempting to load it. """ if confParser.has_section(cnfSec): if confParser.has_option(cnfSec, cnfName): return confParser.get(cnfSec, cnfName) return cnfDefault
class GuiTheme: def __init__(self): self.mainConf = novelwriter.CONFIG self.iconCache = GuiIcons(self) # Loaded Theme Settings # ===================== # Theme self.themeName = "" self.themeDescription = "" self.themeAuthor = "" self.themeCredit = "" self.themeUrl = "" self.themeLicense = "" self.themeLicenseUrl = "" # GUI self.statNone = [120, 120, 120] self.statUnsaved = [200, 15, 39] self.statSaved = [2, 133, 37] self.helpText = [0, 0, 0] # Loaded Syntax Settings # ====================== # Main self.syntaxName = "" self.syntaxDescription = "" self.syntaxAuthor = "" self.syntaxCredit = "" self.syntaxUrl = "" self.syntaxLicense = "" self.syntaxLicenseUrl = "" # Colours self.colBack = [255, 255, 255] self.colText = [0, 0, 0] self.colLink = [0, 0, 0] self.colHead = [0, 0, 0] self.colHeadH = [0, 0, 0] self.colEmph = [0, 0, 0] self.colDialN = [0, 0, 0] self.colDialD = [0, 0, 0] self.colDialS = [0, 0, 0] self.colHidden = [0, 0, 0] self.colKey = [0, 0, 0] self.colVal = [0, 0, 0] self.colSpell = [0, 0, 0] self.colError = [0, 0, 0] self.colRepTag = [0, 0, 0] self.colMod = [0, 0, 0] # Changeable Settings self.guiTheme = None self.guiSyntax = None self.syntaxFile = None self.cssFile = None self.guiFontDB = QFontDatabase() # Class Setup # =========== self._guiPalette = QPalette() self._themeList = [] self._syntaxList = [] self._availThemes = {} self._availSyntax = {} self._listConf(self._availSyntax, os.path.join(self.mainConf.dataPath, "syntax")) self._listConf(self._availSyntax, os.path.join(self.mainConf.assetPath, "syntax")) self._listConf(self._availThemes, os.path.join(self.mainConf.dataPath, "themes")) self._listConf(self._availThemes, os.path.join(self.mainConf.assetPath, "themes")) self.updateFont() self.updateTheme() self.iconCache.updateTheme() # Icon Functions self.getIcon = self.iconCache.getIcon self.getPixmap = self.iconCache.getPixmap self.getItemIcon = self.iconCache.getItemIcon self.loadDecoration = self.iconCache.loadDecoration # Extract Other Info self.guiDPI = qApp.primaryScreen().logicalDotsPerInchX() self.guiScale = qApp.primaryScreen().logicalDotsPerInchX() / 96.0 self.mainConf.guiScale = self.guiScale logger.verbose("GUI DPI: %.1f", self.guiDPI) logger.verbose("GUI Scale: %.2f", self.guiScale) # Fonts self.guiFont = qApp.font() qMetric = QFontMetrics(self.guiFont) self.fontPointSize = self.guiFont.pointSizeF() self.fontPixelSize = int(round(qMetric.height())) self.baseIconSize = int(round(qMetric.ascent())) self.textNHeight = qMetric.boundingRect("N").height() self.textNWidth = qMetric.boundingRect("N").width() # Monospace Font self.guiFontFixed = QFont() self.guiFontFixed.setPointSizeF(0.95 * self.fontPointSize) self.guiFontFixed.setFamily( QFontDatabase.systemFont(QFontDatabase.FixedFont).family()) logger.verbose("GUI Font Family: %s", self.guiFont.family()) logger.verbose("GUI Font Point Size: %.2f", self.fontPointSize) logger.verbose("GUI Font Pixel Size: %d", self.fontPixelSize) logger.verbose("GUI Base Icon Size: %d", self.baseIconSize) logger.verbose("Text 'N' Height: %d", self.textNHeight) logger.verbose("Text 'N' Width: %d", self.textNWidth) return ## # Methods ## def getTextWidth(self, theText, theFont=None): """Returns the width needed to contain a given piece of text. """ if isinstance(theFont, QFont): qMetrics = QFontMetrics(theFont) else: qMetrics = QFontMetrics(self.guiFont) return int(ceil(qMetrics.boundingRect(theText).width())) ## # Actions ## def updateFont(self): """Update the GUI's font style from settings. """ theFont = QFont() if self.mainConf.guiFont not in self.guiFontDB.families(): if self.mainConf.osWindows and "Arial" in self.guiFontDB.families( ): # On Windows we default to Arial if possible theFont.setFamily("Arial") theFont.setPointSize(10) else: theFont = self.guiFontDB.systemFont(QFontDatabase.GeneralFont) self.mainConf.guiFont = theFont.family() self.mainConf.guiFontSize = theFont.pointSize() else: theFont.setFamily(self.mainConf.guiFont) theFont.setPointSize(self.mainConf.guiFontSize) qApp.setFont(theFont) return def updateTheme(self): """Update the GUI theme from theme files. """ self.guiTheme = self.mainConf.guiTheme self.guiSyntax = self.mainConf.guiSyntax self.themeFile = self._availThemes.get(self.guiTheme, None) if self.themeFile is None: logger.error("Could not find GUI theme '%s'", self.guiTheme) else: self.cssFile = self.themeFile[:-5] + ".css" self.loadTheme() self.syntaxFile = self._availSyntax.get(self.guiSyntax, None) if self.syntaxFile is None: logger.error("Could not find syntax theme '%s'", self.guiSyntax) else: self.loadSyntax() # Update dependant colours backCol = qApp.palette().window().color() textCol = qApp.palette().windowText().color() backLCol = backCol.lightnessF() textLCol = textCol.lightnessF() if backLCol > textLCol: helpLCol = textLCol + 0.65 * (backLCol - textLCol) else: helpLCol = backLCol + 0.65 * (textLCol - backLCol) self.helpText = [int(255 * helpLCol)] * 3 return True def loadTheme(self): """Load the currently specified GUI theme. """ logger.info("Loading GUI theme '%s'", self.guiTheme) # Config File confParser = NWConfigParser() try: with open(self.themeFile, mode="r", encoding="utf-8") as inFile: confParser.read_file(inFile) except Exception: logger.error("Could not load theme settings from: %s", self.themeFile) logException() return False # Main cnfSec = "Main" if confParser.has_section(cnfSec): self.themeName = confParser.rdStr(cnfSec, "name", "") self.themeDescription = confParser.rdStr(cnfSec, "description", "N/A") self.themeAuthor = confParser.rdStr(cnfSec, "author", "N/A") self.themeCredit = confParser.rdStr(cnfSec, "credit", "N/A") self.themeUrl = confParser.rdStr(cnfSec, "url", "") self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A") self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "") # Palette cnfSec = "Palette" if confParser.has_section(cnfSec): self._setPalette(confParser, cnfSec, "window", QPalette.Window) self._setPalette(confParser, cnfSec, "windowtext", QPalette.WindowText) self._setPalette(confParser, cnfSec, "base", QPalette.Base) self._setPalette(confParser, cnfSec, "alternatebase", QPalette.AlternateBase) self._setPalette(confParser, cnfSec, "text", QPalette.Text) self._setPalette(confParser, cnfSec, "tooltipbase", QPalette.ToolTipBase) self._setPalette(confParser, cnfSec, "tooltiptext", QPalette.ToolTipText) self._setPalette(confParser, cnfSec, "button", QPalette.Button) self._setPalette(confParser, cnfSec, "buttontext", QPalette.ButtonText) self._setPalette(confParser, cnfSec, "brighttext", QPalette.BrightText) self._setPalette(confParser, cnfSec, "highlight", QPalette.Highlight) self._setPalette(confParser, cnfSec, "highlightedtext", QPalette.HighlightedText) self._setPalette(confParser, cnfSec, "link", QPalette.Link) self._setPalette(confParser, cnfSec, "linkvisited", QPalette.LinkVisited) # GUI cnfSec = "GUI" if confParser.has_section(cnfSec): self.statNone = self._loadColour(confParser, cnfSec, "statusnone") self.statUnsaved = self._loadColour(confParser, cnfSec, "statusunsaved") self.statSaved = self._loadColour(confParser, cnfSec, "statussaved") # CSS File cssData = readTextFile(self.cssFile) if cssData: qApp.setStyleSheet(cssData) # Apply Styles qApp.setPalette(self._guiPalette) return True def loadSyntax(self): """Load the currently specified syntax highlighter theme. """ logger.info("Loading syntax theme '%s'", self.guiSyntax) confParser = NWConfigParser() try: with open(self.syntaxFile, mode="r", encoding="utf-8") as inFile: confParser.read_file(inFile) except Exception: logger.error("Could not load syntax colours from: %s", self.syntaxFile) logException() return False # Main cnfSec = "Main" if confParser.has_section(cnfSec): self.syntaxName = confParser.rdStr(cnfSec, "name", "") self.syntaxDescription = confParser.rdStr(cnfSec, "description", "") self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "") self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "") self.syntaxUrl = confParser.rdStr(cnfSec, "url", "") self.syntaxLicense = confParser.rdStr(cnfSec, "license", "") self.syntaxLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "") # Syntax cnfSec = "Syntax" if confParser.has_section(cnfSec): self.colBack = self._loadColour(confParser, cnfSec, "background") self.colText = self._loadColour(confParser, cnfSec, "text") self.colLink = self._loadColour(confParser, cnfSec, "link") self.colHead = self._loadColour(confParser, cnfSec, "headertext") self.colHeadH = self._loadColour(confParser, cnfSec, "headertag") self.colEmph = self._loadColour(confParser, cnfSec, "emphasis") self.colDialN = self._loadColour(confParser, cnfSec, "straightquotes") self.colDialD = self._loadColour(confParser, cnfSec, "doublequotes") self.colDialS = self._loadColour(confParser, cnfSec, "singlequotes") self.colHidden = self._loadColour(confParser, cnfSec, "hidden") self.colKey = self._loadColour(confParser, cnfSec, "keyword") self.colVal = self._loadColour(confParser, cnfSec, "value") self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline") self.colError = self._loadColour(confParser, cnfSec, "errorline") self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag") self.colMod = self._loadColour(confParser, cnfSec, "modifier") return True def listThemes(self): """Scan the GUI themes folder and list all themes. """ if self._themeList: return self._themeList confParser = NWConfigParser() for themeKey, themePath in self._availThemes.items(): logger.verbose("Checking theme config for '%s'", themeKey) themeName = _loadInternalName(confParser, themePath) if themeName: self._themeList.append((themeKey, themeName)) self._themeList = sorted(self._themeList, key=lambda x: x[1]) return self._themeList def listSyntax(self): """Scan the syntax themes folder and list all themes. """ if self._syntaxList: return self._syntaxList confParser = NWConfigParser() for syntaxKey, syntaxPath in self._availSyntax.items(): logger.verbose("Checking theme syntax for '%s'", syntaxKey) syntaxName = _loadInternalName(confParser, syntaxPath) if syntaxName: self._syntaxList.append((syntaxKey, syntaxName)) self._syntaxList = sorted(self._syntaxList, key=lambda x: x[1]) return self._syntaxList ## # Internal Functions ## def _listConf(self, targetDict, checkDir): """Scan for syntax and gui themes and populate the dictionary. """ if not os.path.isdir(checkDir): return for checkFile in os.listdir(checkDir): confPath = os.path.join(checkDir, checkFile) if os.path.isfile(confPath) and confPath.endswith(".conf"): targetDict[checkFile[:-5]] = confPath return def _loadColour(self, confParser, cnfSec, cnfName): """Load a colour value from a config string. """ if confParser.has_option(cnfSec, cnfName): inData = confParser.get(cnfSec, cnfName).split(",") outData = [] try: outData.append(int(inData[0])) outData.append(int(inData[1])) outData.append(int(inData[2])) except Exception: logger.error( "Could not load theme colours for '%s' from config file", cnfName) outData = [0, 0, 0] else: logger.warning( "Could not find theme colours for '%s' in config file", cnfName) outData = [0, 0, 0] return outData def _setPalette(self, confParser, cnfSec, cnfName, paletteVal): """Set a palette colour value from a config string. """ readCol = [] if confParser.has_option(cnfSec, cnfName): inData = confParser.get(cnfSec, cnfName).split(",") try: readCol.append(int(inData[0])) readCol.append(int(inData[1])) readCol.append(int(inData[2])) except Exception: logger.error( "Could not load theme colours for '%s' from config file", cnfName) return if len(readCol) == 3: self._guiPalette.setColor(paletteVal, QColor(*readCol)) return
class FontDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.__font_db = QFontDatabase() self.setWindowTitle(_("Select font")) layout = QVBoxLayout() content_layout = QHBoxLayout() font_list_layout = QVBoxLayout() font_list_layout.addWidget(QLabel(_("Font family:"))) self.__font_search = UpDownPassingLineEdit() self.__font_search.textEdited.connect(self.__family_search_edited) font_list_layout.addWidget(self.__font_search) self.__font_list = QListWidget() self.__font_list.setFocusPolicy(Qt.NoFocus) for font in sorted(self.__font_db.families()): if self.__font_db.isSmoothlyScalable(font): self.__font_list.addItem(font) self.__font_list.currentTextChanged.connect(self.__family_changed) self.__font_search.destination_widget = self.__font_list font_list_layout.addWidget(self.__font_list) content_layout.addLayout(font_list_layout) style_layout = QVBoxLayout() style_layout.setAlignment(Qt.AlignCenter) self.__bold_widget = self.__create_style_button( "B", _("Bold"), QKeySequence.Bold, FontStyle.bold) style_layout.addWidget(self.__bold_widget) self.__italic_widget = self.__create_style_button( "I", _("Italic"), QKeySequence.Italic, FontStyle.italic) style_layout.addWidget(self.__italic_widget) self.__underline_widget = self.__create_style_button( "U", _("Underline"), QKeySequence.Underline, FontStyle.underline) style_layout.addWidget(self.__underline_widget) self.__strike_widget = self.__create_style_button( "S", _("Strike Out"), STRIKE_OUT, FontStyle.strike) style_layout.addWidget(self.__strike_widget) content_layout.addLayout(style_layout) size_layout = QVBoxLayout() size_layout.setAlignment(Qt.AlignHCenter) size_layout.addWidget(QLabel(_("Size (px):"))) self.__size_edit = UpDownPassingLineEdit() self.__size_edit.textEdited.connect(self.__size_edited) self.__size_edit.setValidator(QIntValidator(5, 60)) size_layout.addWidget(self.__size_edit) self.__size_edit.setSizePolicy( QSizePolicy.Preferred, self.__size_edit.sizePolicy().verticalPolicy()) self.__size_slider = QSlider(Qt.Vertical) self.__size_slider.setFocusPolicy(Qt.NoFocus) self.__size_slider.setRange(5, 60) self.__size_slider.setTickInterval(5) self.__size_slider.setTickPosition(QSlider.TicksRight) self.__size_slider.valueChanged.connect(self.__size_changed) self.__size_edit.destination_widget = self.__size_slider size_layout.addWidget(self.__size_slider) content_layout.addLayout(size_layout) layout.addLayout(content_layout) self.__example = QLineEdit("AaBbYyZz") self.__example.setFixedHeight(80) layout.addWidget(self.__example) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.button(QDialogButtonBox.Ok).setText(_("Ok")) button_box.button(QDialogButtonBox.Cancel).setText(_("Cancel")) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) layout.addWidget(button_box) self.setLayout(layout) self.__font = Fonts.default def __create_style_button(self, text, tooltip, keys, font_style): def toggled(value): self.__font = self.__font.change(font_style, value) self.__refresh_example_font() shortcut = QKeySequence(keys) widget = QPushButton(text) widget.setShortcut(shortcut) widget.setToolTip("{0} ({1})".format(tooltip, shortcut.toString())) font = widget.font() if font_style == FontStyle.bold: font.setBold(True) elif font_style == FontStyle.italic: font.setItalic(True) elif font_style == FontStyle.underline: font.setUnderline(True) elif font_style == FontStyle.strike: font.setStrikeOut(True) widget.setFont(font) widget.setCheckable(True) widget.toggled.connect(toggled) return widget @property def ufl_font(self): return self.__font @ufl_font.setter def ufl_font(self, value): self.__font = value for item in self.__font_list.findItems(value.family, Qt.MatchExactly): self.__font_list.setCurrentItem(item) break else: self.__font_list.clearSelection() self.__font_search.setText(value.family) self.__bold_widget.setChecked(FontStyle.bold in value.style) self.__italic_widget.setChecked(FontStyle.italic in value.style) self.__underline_widget.setChecked(FontStyle.underline in value.style) self.__strike_widget.setChecked(FontStyle.strike in value.style) self.__size_slider.setValue(value.size) self.__size_edit.setText(str(value.size)) self.__refresh_example_font() def __refresh_example_font(self): qfont = QFont(self.__font.family) qfont.setPixelSize(self.__font.size) qfont.setBold(FontStyle.bold in self.__font.style) qfont.setItalic(FontStyle.italic in self.__font.style) qfont.setStrikeOut(FontStyle.strike in self.__font.style) qfont.setUnderline(FontStyle.underline in self.__font.style) self.__example.setFont(qfont) def __family_search_edited(self, name): selected_text = self.__font_list.currentItem().text() if selected_text.startswith(name): return for item in self.__font_list.findItems(name, Qt.MatchStartsWith): self.__font_list.setCurrentItem(item) break def __family_changed(self, value): if self.__font.family != value: if not self.__font_search.hasFocus(): self.__font_search.setText(value) self.__font = self.__font.change_family(value) self.__refresh_example_font() def __size_edited(self, value): if not value or int(value) < 5: return int_value = int(value) if self.__font.size != int_value: self.__font = self.__font.change_size(int_value) self.__size_slider.setValue(int_value) self.__refresh_example_font() def __size_changed(self, value): if self.__font.size != value: if self.__size_edit.text() != str(value): self.__size_edit.setText(str(value)) self.__size_edit.selectAll() self.__font = self.__font.change_size(value) self.__refresh_example_font()
def __init__(self, parent: typing.Optional['QWidget']): super().__init__(parent=parent) self.scrollView = QScrollArea(self) self.scrollView.setWidgetResizable(True) self.scrollView.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.textAttr = QWidget(self) self.textAttr.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) self.textAttrBox = QVBoxLayout() self.textAttr.setLayout(self.textAttrBox) self.cascades = SvgBar(self) self.cascades.setVisible(False) self.cascades.onDelete = self.deleteCascade self.cascades.onDrag = self.resortCascade self.cascades.onCopy = self.onCopy self.textAttrBox.addWidget(self.cascades) self.svgId = QLabel('N/A', self) self.textAttrBox.addWidget(self._title(TR('Type'))) self.textAttrBox.addWidget(self.svgId) self.startXOffsetLabel = self._title(TR('Overlay: Start X Percentage')) self.textAttrBox.addWidget(self.startXOffsetLabel) self.startXOffset = QSlider(self) self.startXOffset.setOrientation(QtCore.Qt.Orientation.Horizontal) self.startXOffset.setMinimum(0) self.startXOffset.setMaximum(3) self.startXOffset.valueChanged.connect( lambda e: self.offsetChanged('xo', self.startXOffset.value() * 0.25)) self.textAttrBox.addWidget(self.startXOffset) self.textAttrBox.addWidget(self._title(TR('Text'))) self.text = QTextEdit(self) self.text.textChanged.connect(self.textChanged) self.text.installEventFilter(self) self.textAttrBox.addWidget(self.text) self.textFont = QComboBox(self) fontFamilies = QFontDatabase() for s in fontFamilies.families(): self.textFont.addItem(s) self.textFont.currentIndexChanged.connect(self.fontChanged) self.textFont.setEditable(True) self.textSize = QComboBox(self) for i in range(8, 150, 1): if i <= 32: self.textSize.addItem(str(i)) elif i <= 80 and i % 2 == 0: self.textSize.addItem(str(i)) elif i % 10 == 0: self.textSize.addItem(str(i)) self.textSize.currentIndexChanged.connect(self.sizeChanged) self.textSize.setEditable(True) self._addBiBoxInTextAttrBox(self._title(TR("Font Family")), self.textFont, self._title(TR("Font Size")), self.textSize) self.textAlign = QComboBox(self) self.textPlace = QComboBox(self) for c in [self.textAlign, self.textPlace]: c.addItem(TR('Center'), 'c') c.addItem(TR('Top'), 't') c.addItem(TR('Bottom'), 'b') c.addItem(TR('Left'), 'l') c.addItem(TR('Right'), 'r') self.textAlign.currentIndexChanged.connect(self.alignChanged) self.textPlace.currentIndexChanged.connect(self.placeChanged) self._addBiBoxInTextAttrBox(self._title(TR("Alignment")), self.textAlign, self._title(TR("Placement")), self.textPlace) self.textX = QSpinBox(self) self.textY = QSpinBox(self) for c in [self.textX, self.textY]: c.setValue(0) c.setMinimum(-1e5) c.setMaximum(1e5) self.textX.valueChanged.connect(lambda e: self.offsetChanged('x', e)) self.textY.valueChanged.connect(lambda e: self.offsetChanged('y', e)) self._addBiBoxInTextAttrBox(self._title(TR("Offset X")), self.textX, self._title(TR("Offset Y")), self.textY) # self.setLayout(self.textAttrBox) self.scrollView.setWidget(self.textAttr) box = QVBoxLayout() box.addWidget(self.scrollView) box.setContentsMargins(0, 0, 0, 0) self.setLayout(box) self.show() self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding) self.updating = False