manuskript/manuskript/ui/editors/themes.py

307 lines
11 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2016-02-07 00:34:22 +13:00
# --!-- coding: utf8 --!--
2015-06-20 04:47:45 +12:00
# Lots of stuff from here comes from the excellent focuswriter.
2016-02-07 00:34:22 +13:00
import os
2015-06-21 02:45:54 +12:00
import re
2016-02-07 00:34:22 +13:00
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
2017-11-28 03:00:07 +13:00
from manuskript.ui.views.MDEditView import MDEditView
2016-02-07 00:34:22 +13:00
_thumbCache = {}
2016-02-07 00:34:22 +13:00
def loadThemeDatas(themeFile):
settings = QSettings(themeFile, QSettings.IniFormat)
_themeData = {}
2016-02-07 00:34:22 +13:00
# Theme name
_themeData["Name"] = getThemeName(themeFile)
2016-02-07 00:34:22 +13:00
# Window Background
loadThemeSetting(_themeData, settings, "Background/Color", "#000000")
loadThemeSetting(_themeData, settings, "Background/ImageFile", "")
loadThemeSetting(_themeData, settings, "Background/Type", 0)
2016-02-07 00:34:22 +13:00
# 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)
2016-02-07 00:34:22 +13:00
# Text Options
loadThemeSetting(_themeData, settings, "Text/Color", "#ffffff")
loadThemeSetting(_themeData, settings, "Text/Font", qApp.font().toString())
loadThemeSetting(_themeData, settings, "Text/Misspelled", "#ff0000")
2016-02-07 00:34:22 +13:00
# 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)
2016-02-07 00:34:22 +13:00
return _themeData
2016-02-07 00:34:22 +13:00
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))
2016-02-07 00:34:22 +13:00
def getThemeName(theme):
settings = QSettings(theme, QSettings.IniFormat)
2016-02-07 00:34:22 +13:00
if settings.contains("Name"):
return settings.value("Name")
else:
return os.path.splitext(os.path.split(theme)[1])[0]
2016-02-07 00:34:22 +13:00
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
2016-02-07 00:34:22 +13:00
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))
2016-02-07 00:34:22 +13:00
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]
2016-02-07 00:34:22 +13:00
pixmap = generateTheme(themeDatas, screenRect)
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
addThemePreviewText(pixmap, themeDatas, screenRect)
2016-02-07 00:34:22 +13:00
px = QPixmap(pixmap).scaled(size, Qt.KeepAspectRatio)
2016-02-07 00:34:22 +13:00
w = int(px.width() / 10)
h = int(px.height() / 10)
r = themeTextRect(themeDatas, screenRect)
2016-02-07 00:34:22 +13:00
painter = QPainter(px)
2016-02-07 00:34:22 +13:00
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)
2016-02-07 00:34:22 +13:00
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]
2016-02-07 00:34:22 +13:00
return px
2015-06-20 04:47:45 +12:00
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
def findThemePath(themeName):
2015-06-21 02:45:54 +12:00
p = findFirstFile(re.escape("{}.theme".format(themeName)), "resources/themes")
if not p:
return findFirstFile(r".*\.theme", "resources/themes")
2015-06-21 02:45:54 +12:00
else:
return p
2015-06-20 04:47:45 +12:00
2016-02-07 00:34:22 +13:00
def generateTheme(themeDatas, screenRect):
# Window Background
px = QPixmap(screenRect.size())
px.fill(QColor(themeDatas["Background/Color"]))
2016-02-07 00:34:22 +13:00
painter = QPainter(px)
if themeDatas["Background/ImageFile"]:
path = findBackground(themeDatas["Background/ImageFile"])
_type = themeDatas["Background/Type"]
if path and _type > 0:
2016-02-07 00:34:22 +13:00
if _type == 1: # Tiled
painter.fillRect(screenRect, QBrush(QImage(path)))
else:
img = QImage(path)
scaled = img.size()
2016-02-07 00:34:22 +13:00
if _type == 3: # Stretched
scaled.scale(screenRect.size(), Qt.IgnoreAspectRatio)
2016-02-07 00:34:22 +13:00
elif _type == 4: # Scaled
scaled.scale(screenRect.size(), Qt.KeepAspectRatio)
2016-02-07 00:34:22 +13:00
elif _type == 5: # Zoomed
scaled.scale(screenRect.size(), Qt.KeepAspectRatioByExpanding)
2016-02-07 00:34:22 +13:00
painter.drawImage(int((screenRect.width() - scaled.width()) / 2),
int((screenRect.height() - scaled.height()) / 2), img.scaled(scaled))
2016-02-07 00:34:22 +13:00
# Text Background
textRect = themeTextRect(themeDatas, screenRect)
2016-02-07 00:34:22 +13:00
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()
2016-02-07 00:34:22 +13:00
painter.end()
2015-06-20 04:47:45 +12:00
return px
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
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"]
2015-06-20 04:47:45 +12:00
return x, y, width, height
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
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"])
2015-06-20 04:47:45 +12:00
return bf
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
def setThemeEditorDatas(editor, themeDatas, pixmap, screenRect):
textRect = themeTextRect(themeDatas, screenRect)
x, y, width, height = themeEditorGeometry(themeDatas, textRect)
editor.setGeometry(x, y, width, height)
2016-02-07 00:34:22 +13:00
# p = editor.palette()
##p.setBrush(QPalette.Base, QBrush(pixmap.copy(x, y, width, height)))
2016-02-07 00:34:22 +13:00
# 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)
2015-06-20 04:47:45 +12:00
editor.setAttribute(Qt.WA_NoSystemBackground, True)
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
bf = getThemeBlockFormat(themeDatas)
editor.setDefaultBlockFormat(bf)
2016-02-07 00:34:22 +13:00
# 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()
2015-06-20 04:47:45 +12:00
editor.setTabStopWidth(themeDatas["Spacings/TabWidth"])
editor.document().setIndentWidth(themeDatas["Spacings/TabWidth"])
2016-02-07 00:34:22 +13:00
2015-06-25 23:41:55 +12:00
editor.highlighter.setMisspelledColor(QColor(themeDatas["Text/Misspelled"]))
2016-02-07 00:34:22 +13:00
2015-06-25 23:41:55 +12:00
cf = QTextCharFormat()
2016-02-07 00:34:22 +13:00
# f = QFont()
# f.fromString(themeDatas["Text/Font"])
# cf.setFont(f)
editor.highlighter.setDefaultCharFormat(cf)
f = QFont()
f.fromString(themeDatas["Text/Font"])
2016-02-07 00:34:22 +13:00
# 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"],
2016-02-07 00:34:22 +13:00
)
)
2015-06-26 03:06:07 +12:00
editor._fromTheme = True
editor._themeData = themeDatas
editor.highlighter.updateColorScheme()
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
def addThemePreviewText(pixmap, themeDatas, screenRect):
# Text
2017-11-28 03:00:07 +13:00
previewText = MDEditView(highlighting=True)
2015-06-20 04:47:45 +12:00
previewText.setFrameStyle(QFrame.NoFrame)
previewText.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
previewText.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
2015-06-21 02:45:54 +12:00
f = QFile(appPath("resources/themes/preview.txt"))
f.open(QIODevice.ReadOnly)
previewText.setPlainText(QTextStream(f).readAll())
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
setThemeEditorDatas(previewText, themeDatas, pixmap, screenRect)
2016-02-07 00:34:22 +13:00
2015-06-20 04:47:45 +12:00
previewText.render(pixmap, previewText.pos())
2016-02-07 00:34:22 +13:00
## Text Background
##themeDatas["Foreground/Color"]
##themeDatas["Foreground/Opacity"]
##themeDatas["Foreground/Margin"]
##themeDatas["Foreground/Padding"]
##themeDatas["Foreground/Position"]
##themeDatas["Foreground/Rounding"]
##themeDatas["Foreground/Width"]
2016-02-07 00:34:22 +13:00
## Text Options
##themeDatas["Text/Color"]
##themeDatas["Text/Font"]
2016-02-07 00:34:22 +13:00
# themeDatas["Text/Misspelled"]
## Paragraph Options
##themeDatas["Spacings/IndentFirstLine"]
##themeDatas["Spacings/LineSpacing"]
##themeDatas["Spacings/ParagraphAbove"]
##themeDatas["Spacings/ParagraphBelow"]
2016-02-07 00:34:22 +13:00
##themeDatas["Spacings/TabWidth"]