#!/usr/bin/env python # --!-- coding: utf8 --!-- # Lots of stuff from here comes from the excellent focuswriter. import os import re from PyQt5.QtCore import QSettings, QRect, QSize, Qt, QPoint, QFile, QIODevice, QTextStream from PyQt5.QtGui import QPixmap, QPainter, QColor, QBrush, QImage, QTextBlockFormat, QTextCharFormat, QFont, qGray from PyQt5.QtWidgets import qApp, QFrame from manuskript.functions import allPaths, appPath, findBackground, findFirstFile from manuskript.ui.views.MDEditView import MDEditView _thumbCache = {} def loadThemeDatas(themeFile): settings = QSettings(themeFile, QSettings.IniFormat) _themeData = {} # Theme name _themeData["Name"] = getThemeName(themeFile) # Window Background loadThemeSetting(_themeData, settings, "Background/Color", "#000000") loadThemeSetting(_themeData, settings, "Background/ImageFile", "") loadThemeSetting(_themeData, settings, "Background/Type", 0) # Text Background loadThemeSetting(_themeData, settings, "Foreground/Color", "#ffffff") loadThemeSetting(_themeData, settings, "Foreground/Opacity", 50) loadThemeSetting(_themeData, settings, "Foreground/Margin", 40) loadThemeSetting(_themeData, settings, "Foreground/Padding", 10) loadThemeSetting(_themeData, settings, "Foreground/Position", 1) loadThemeSetting(_themeData, settings, "Foreground/Rounding", 5) loadThemeSetting(_themeData, settings, "Foreground/Width", 700) # Text Options loadThemeSetting(_themeData, settings, "Text/Color", "#ffffff") loadThemeSetting(_themeData, settings, "Text/Font", qApp.font().toString()) loadThemeSetting(_themeData, settings, "Text/Misspelled", "#ff0000") # Paragraph Options loadThemeSetting(_themeData, settings, "Spacings/Alignment", 0) loadThemeSetting(_themeData, settings, "Spacings/IndentFirstLine", False) loadThemeSetting(_themeData, settings, "Spacings/LineSpacing", 100) loadThemeSetting(_themeData, settings, "Spacings/ParagraphAbove", 0) loadThemeSetting(_themeData, settings, "Spacings/ParagraphBelow", 0) loadThemeSetting(_themeData, settings, "Spacings/TabWidth", 48) return _themeData def loadThemeSetting(datas, settings, key, default): """ Loads data from ini file, using default value if the key is absent, and casting to the proper type based on default. """ datas[key] = settings.value(key, default, type(default)) def getThemeName(theme): settings = QSettings(theme, QSettings.IniFormat) if settings.contains("Name"): return settings.value("Name") else: return os.path.splitext(os.path.split(theme)[1])[0] def themeTextRect(themeDatas, screenRect): margin = themeDatas["Foreground/Margin"] x = 0 y = margin width = min(themeDatas["Foreground/Width"], screenRect.width() - 2 * margin) height = screenRect.height() - 2 * margin if themeDatas["Foreground/Position"] == 0: # Left x = margin elif themeDatas["Foreground/Position"] == 1: # Center x = (screenRect.width() - width) / 2 elif themeDatas["Foreground/Position"] == 2: # Right x = screenRect.width() - margin - width elif themeDatas["Foreground/Position"] == 3: # Stretched x = margin width = screenRect.width() - 2 * margin return QRect(int(x), int(y), int(width), int(height)) def createThemePreview(theme, screenRect, size=QSize(200, 120)): """ Generates a QPixmap preview for given theme. Theme can be either a string containing the filename of the ini file with the theme settings, or it can be a dict with the settings. If theme is a filename, the result is cached. """ # Checking whether theme is a string or dict if type(theme) == str and os.path.exists(theme): # Theme is the path to an ini file themeDatas = loadThemeDatas(theme) fromFile = True else: themeDatas = theme fromFile = False # Check if item is in cache if fromFile and theme in _thumbCache: if _thumbCache[theme][0] == themeDatas: return _thumbCache[theme][1] pixmap = generateTheme(themeDatas, screenRect) addThemePreviewText(pixmap, themeDatas, screenRect) px = QPixmap(pixmap).scaled(size, Qt.KeepAspectRatio) w = int(px.width() / 10) h = int(px.height() / 10) r = themeTextRect(themeDatas, screenRect) painter = QPainter(px) painter.drawPixmap(QRect(w, h, w * 4, h * 5), pixmap, QRect(r.topLeft() - QPoint(int(w / 3), int(h / 3)), QSize(w * 4, h * 5))) painter.setPen(Qt.white) painter.drawRect(QRect(w, h, w * 4, h * 5)) painter.end() # If theme is a themefile, we keep it in cache if fromFile: _thumbCache[theme] = [themeDatas, px] return px def findThemePath(themeName): p = findFirstFile(re.escape("{}.theme".format(themeName)), "resources/themes") if not p: return findFirstFile(r".*\.theme", "resources/themes") else: return p def generateTheme(themeDatas, screenRect): # Window Background px = QPixmap(screenRect.size()) px.fill(QColor(themeDatas["Background/Color"])) painter = QPainter(px) if themeDatas["Background/ImageFile"]: path = findBackground(themeDatas["Background/ImageFile"]) _type = themeDatas["Background/Type"] if path and _type > 0: if _type == 1: # Tiled painter.fillRect(screenRect, QBrush(QImage(path))) else: img = QImage(path) scaled = img.size() if _type == 3: # Stretched scaled.scale(screenRect.size(), Qt.IgnoreAspectRatio) elif _type == 4: # Scaled scaled.scale(screenRect.size(), Qt.KeepAspectRatio) elif _type == 5: # Zoomed scaled.scale(screenRect.size(), Qt.KeepAspectRatioByExpanding) painter.drawImage(int((screenRect.width() - scaled.width()) / 2), int((screenRect.height() - scaled.height()) / 2), img.scaled(scaled)) # Text Background textRect = themeTextRect(themeDatas, screenRect) painter.save() color = QColor(themeDatas["Foreground/Color"]) color.setAlpha(int(themeDatas["Foreground/Opacity"] * 255 / 100)) painter.setBrush(color) painter.setPen(Qt.NoPen) r = themeDatas["Foreground/Rounding"] painter.drawRoundedRect(textRect, r, r) painter.restore() painter.end() return px def themeEditorGeometry(themeDatas, textRect): padding = themeDatas["Foreground/Padding"] x = textRect.x() + padding y = textRect.y() + padding + themeDatas["Spacings/ParagraphAbove"] width = textRect.width() - 2 * padding height = textRect.height() - 2 * padding - themeDatas["Spacings/ParagraphAbove"] return x, y, width, height def getThemeBlockFormat(themeDatas): bf = QTextBlockFormat() bf.setAlignment(Qt.AlignLeft if themeDatas["Spacings/Alignment"] == 0 else Qt.AlignCenter if themeDatas["Spacings/Alignment"] == 1 else Qt.AlignRight if themeDatas["Spacings/Alignment"] == 2 else Qt.AlignJustify) bf.setLineHeight(themeDatas["Spacings/LineSpacing"], QTextBlockFormat.ProportionalHeight) bf.setTextIndent(themeDatas["Spacings/TabWidth"] * 1 if themeDatas["Spacings/IndentFirstLine"] else 0) bf.setTopMargin(themeDatas["Spacings/ParagraphAbove"]) bf.setBottomMargin(themeDatas["Spacings/ParagraphBelow"]) return bf def setThemeEditorDatas(editor, themeDatas, pixmap, screenRect): textRect = themeTextRect(themeDatas, screenRect) x, y, width, height = themeEditorGeometry(themeDatas, textRect) editor.setGeometry(x, y, width, height) # p = editor.palette() ##p.setBrush(QPalette.Base, QBrush(pixmap.copy(x, y, width, height))) # p.setBrush(QPalette.Base, QColor(Qt.transparent)) # p.setColor(QPalette.Text, QColor(themeDatas["Text/Color"])) # p.setColor(QPalette.Highlight, QColor(themeDatas["Text/Color"])) # p.setColor(QPalette.HighlightedText, Qt.black if qGray(QColor(themeDatas["Text/Color"]).rgb()) > 127 else Qt.white) # editor.setPalette(p) editor.setAttribute(Qt.WA_NoSystemBackground, True) bf = getThemeBlockFormat(themeDatas) editor.setDefaultBlockFormat(bf) # b = editor.document().firstBlock() # cursor = editor.textCursor() # cursor.setBlockFormat(bf) # while b.isValid(): # bf2 = b.blockFormat() # bf2.merge(bf) # cursor.setPosition(b.position()) ##cursor.setPosition(b.position(), QTextCursor.KeepAnchor) # cursor.setBlockFormat(bf2) # b = b.next() editor.setTabStopWidth(themeDatas["Spacings/TabWidth"]) editor.document().setIndentWidth(themeDatas["Spacings/TabWidth"]) editor.highlighter.setMisspelledColor(QColor(themeDatas["Text/Misspelled"])) cf = QTextCharFormat() # f = QFont() # f.fromString(themeDatas["Text/Font"]) # cf.setFont(f) editor.highlighter.setDefaultCharFormat(cf) f = QFont() f.fromString(themeDatas["Text/Font"]) # editor.setFont(f) editor.setStyleSheet(""" QTextEdit {{ background: transparent; color: {foreground}; font-family: {ff}; font-size: {fs}; selection-color: {sc}; selection-background-color: {sbc}; }} """.format( foreground=themeDatas["Text/Color"], ff=f.family(), fs="{}pt".format(str(f.pointSize())), sc="black" if qGray(QColor(themeDatas["Text/Color"]).rgb()) > 127 else "white", sbc=themeDatas["Text/Color"], ) ) editor._fromTheme = True editor._themeData = themeDatas editor.highlighter.updateColorScheme() def addThemePreviewText(pixmap, themeDatas, screenRect): # Text previewText = MDEditView(highlighting=True) previewText.setFrameStyle(QFrame.NoFrame) previewText.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) previewText.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) f = QFile(appPath("resources/themes/preview.txt")) f.open(QIODevice.ReadOnly) previewText.setPlainText(QTextStream(f).readAll()) setThemeEditorDatas(previewText, themeDatas, pixmap, screenRect) previewText.render(pixmap, previewText.pos()) ## Text Background ##themeDatas["Foreground/Color"] ##themeDatas["Foreground/Opacity"] ##themeDatas["Foreground/Margin"] ##themeDatas["Foreground/Padding"] ##themeDatas["Foreground/Position"] ##themeDatas["Foreground/Rounding"] ##themeDatas["Foreground/Width"] ## Text Options ##themeDatas["Text/Color"] ##themeDatas["Text/Font"] # themeDatas["Text/Misspelled"] ## Paragraph Options ##themeDatas["Spacings/IndentFirstLine"] ##themeDatas["Spacings/LineSpacing"] ##themeDatas["Spacings/ParagraphAbove"] ##themeDatas["Spacings/ParagraphBelow"] ##themeDatas["Spacings/TabWidth"]