mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on the editor component for Tweak Book
This commit is contained in:
parent
a3ebc6e3ed
commit
a66904da25
@ -9,6 +9,10 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
from calibre.utils.config import JSONConfig
|
||||
tprefs = JSONConfig('tweak_book_gui')
|
||||
|
||||
tprefs.defaults['editor_theme'] = None
|
||||
tprefs.defaults['editor_font_family'] = None
|
||||
tprefs.defaults['editor_font_size'] = 12
|
||||
|
||||
_current_container = None
|
||||
|
||||
def current_container():
|
||||
|
10
src/calibre/gui2/tweak_book/editor/__init__.py
Normal file
10
src/calibre/gui2/tweak_book/editor/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
|
||||
|
10
src/calibre/gui2/tweak_book/editor/syntax/__init__.py
Normal file
10
src/calibre/gui2/tweak_book/editor/syntax/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
|
||||
|
28
src/calibre/gui2/tweak_book/editor/syntax/base.py
Normal file
28
src/calibre/gui2/tweak_book/editor/syntax/base.py
Normal file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
from PyQt4.Qt import (QSyntaxHighlighter, QApplication, QCursor, Qt)
|
||||
|
||||
from ..themes import highlight_to_char_format
|
||||
|
||||
class SyntaxHighlighter(QSyntaxHighlighter):
|
||||
|
||||
def rehighlight(self):
|
||||
self.outlineexplorer_data = {}
|
||||
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
QSyntaxHighlighter.rehighlight(self)
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
def apply_theme(self, theme):
|
||||
self.theme = {k:highlight_to_char_format(v) for k, v in theme.iteritems()}
|
||||
self.create_formats()
|
||||
self.rehighlight()
|
||||
|
||||
def create_formats(self):
|
||||
pass
|
||||
|
238
src/calibre/gui2/tweak_book/editor/syntax/html.py
Normal file
238
src/calibre/gui2/tweak_book/editor/syntax/html.py
Normal file
@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import re
|
||||
|
||||
from PyQt4.Qt import (QTextCharFormat)
|
||||
|
||||
from .base import SyntaxHighlighter
|
||||
from html5lib.constants import cdataElements, rcdataElements
|
||||
|
||||
entity_pat = re.compile(r'&#{0,1}[a-zA-Z0-9]{1,8};')
|
||||
tag_name_pat = re.compile(r'/{0,1}[a-zA-Z0-9:]+')
|
||||
space_chars = ' \t\r\n\u000c'
|
||||
attribute_name_pat = re.compile(r'''[^%s"'/>=]+''' % space_chars)
|
||||
self_closing_pat = re.compile(r'/\s*>')
|
||||
unquoted_val_pat = re.compile(r'''[^%s'"=<>`]+''' % space_chars)
|
||||
|
||||
class State(object):
|
||||
|
||||
''' Store the parsing state, a stack of bold and italic formatting and the
|
||||
last seen open tag, all in a single integer, so that it can be used with.
|
||||
This assumes an int is at least 32 bits.'''
|
||||
|
||||
NORMAL = 0
|
||||
IN_OPENING_TAG = 1
|
||||
IN_CLOSING_TAG = 2
|
||||
IN_COMMENT = 3
|
||||
IN_PI = 4
|
||||
IN_DOCTYPE = 5
|
||||
ATTRIBUTE_NAME = 6
|
||||
ATTRIBUTE_VALUE = 7
|
||||
SQ_VAL = 8
|
||||
DQ_VAL = 9
|
||||
|
||||
TAGS = {x:i+1 for i, x in enumerate(cdataElements | rcdataElements | {'b', 'em', 'i', 'string', 'a'} | {'h%d' % d for d in range(1, 7)})}
|
||||
TAGS_RMAP = {v:k for k, v in TAGS.iteritems()}
|
||||
UNKNOWN_TAG = '___'
|
||||
|
||||
def __init__(self, num):
|
||||
self.parse = num & 0b1111
|
||||
self.bold = (num >> 4) & 0b11111111
|
||||
self.italic = (num >> 12) & 0b11111111
|
||||
self.tag = self.TAGS_RMAP.get(num >> 20, self.UNKNOWN_TAG)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
tag = self.TAGS.get(self.tag.lower(), 0)
|
||||
return (self.parse & 0b1111) | ((self.bold & 0b11111111) << 4) | ((self.italic & 0b11111111) << 12) | (tag << 20)
|
||||
|
||||
def err(formats, msg):
|
||||
ans = QTextCharFormat(formats['error'])
|
||||
ans.setToolTip(msg)
|
||||
return ans
|
||||
|
||||
def normal(state, text, i, formats):
|
||||
' The normal state in between tags '
|
||||
ch = text[i]
|
||||
if ch == '<':
|
||||
if text[i:i+4] == '<!--':
|
||||
state.parse, fmt = state.IN_COMMENT, formats['comment']
|
||||
return [(4, fmt)]
|
||||
|
||||
if text[i:i+2] == '<?':
|
||||
state.parse, fmt = state.IN_PI, formats['special']
|
||||
return [(2, fmt)]
|
||||
|
||||
if text[i:i+2] == '<!' and text[i+2:].lstrip().lower().startswith('doctype'):
|
||||
state.parse, fmt = state.IN_DOCTYPE, formats['special']
|
||||
return [(2, fmt)]
|
||||
|
||||
m = tag_name_pat.match(text, i + 1)
|
||||
if m is None:
|
||||
return [(1, err(formats, _('An unescaped < is not allowed. Replace it with <')))]
|
||||
|
||||
name = m.group()
|
||||
closing = name.startswith('/')
|
||||
state.parse = state.IN_CLOSING_TAG if closing else state.IN_OPENING_TAG
|
||||
state.tag = name[1:] if closing else name
|
||||
num = 2 if closing else 1
|
||||
return [(num, formats['end_tag' if closing else 'tag']), (len(state.tag), formats['tag_name'])]
|
||||
|
||||
if ch == '&':
|
||||
m = entity_pat.match(text, i)
|
||||
if m is None:
|
||||
return [(1, err(formats, _('An unescaped ampersand is not allowed. Replace it with &')))]
|
||||
return [(len(m.group()), formats['entity'])]
|
||||
|
||||
if ch == '>':
|
||||
return [(1, err(formats, _('An unescaped > is not allowed. Replace it with >')))]
|
||||
|
||||
return [(1, None)]
|
||||
|
||||
def opening_tag(state, text, i, formats):
|
||||
'An opening tag, like <a>'
|
||||
ch = text[i]
|
||||
if ch in space_chars:
|
||||
return [(1, None)]
|
||||
if ch == '/':
|
||||
m = self_closing_pat.match(text, i)
|
||||
if m is None:
|
||||
return [(1, err(formats, _('/ not allowed except at the end of the tag')))]
|
||||
state.parse = state.NORMAL
|
||||
state.tag = State.UNKNOWN_TAG
|
||||
return [(len(m.group()), formats['tag'])]
|
||||
if ch == '>':
|
||||
state.parse = state.NORMAL
|
||||
state.tag = State.UNKNOWN_TAG
|
||||
return [(1, formats['tag'])]
|
||||
m = attribute_name_pat.match(text, i)
|
||||
if m is None:
|
||||
return [(1, err(formats, _('Unknown character')))]
|
||||
state.parse = state.ATTRIBUTE_NAME
|
||||
num = len(m.group())
|
||||
return [(num, formats['attr'])]
|
||||
|
||||
def attribute_name(state, text, i, formats):
|
||||
' After attribute name '
|
||||
ch = text[i]
|
||||
if ch in space_chars:
|
||||
return [(1, None)]
|
||||
if ch == '=':
|
||||
state.parse = State.ATTRIBUTE_VALUE
|
||||
return [(1, formats['attr'])]
|
||||
state.parse = State.IN_OPENING_TAG
|
||||
return [(-1, None)]
|
||||
|
||||
def attribute_value(state, text, i, formats):
|
||||
' After attribute = '
|
||||
ch = text[i]
|
||||
if ch in space_chars:
|
||||
return [(1, None)]
|
||||
if ch in {'"', "'"}:
|
||||
state.parse = State.SQ_VAL if ch == "'" else State.DQ_VAL
|
||||
return [(1, formats['string'])]
|
||||
m = unquoted_val_pat.match(text, i)
|
||||
state.parse = State.IN_OPENING_TAG
|
||||
return [(len(m.group()), formats['string'])]
|
||||
|
||||
def quoted_val(state, text, i, formats):
|
||||
' A quoted attribute value '
|
||||
quote = '"' if state.parse == State.DQ_VAL else "'"
|
||||
pos = text.find(quote, i)
|
||||
if pos == -1:
|
||||
num = len(text) - i
|
||||
else:
|
||||
num = pos - i + 1
|
||||
state.parse = State.IN_OPENING_TAG
|
||||
return [(num, formats['string'])]
|
||||
|
||||
def closing_tag(state, text, i, formats):
|
||||
' A closing tag like </a> '
|
||||
ch = text[i]
|
||||
if ch in space_chars:
|
||||
return [(1, None)]
|
||||
pos = text.find('>', i)
|
||||
if pos == -1:
|
||||
return [(len(text) - i, err(formats, _('A closing tag must contain only the tag name and nothing else')))]
|
||||
state.parse = state.NORMAL
|
||||
num = pos - i + 1
|
||||
ans = [(1, formats['end_tag'])]
|
||||
if num > 1:
|
||||
ans.insert(0, (num - 1, err(formats, _('A closing tag must contain only the tag name and nothing else'))))
|
||||
return ans
|
||||
|
||||
def in_comment(state, text, i, formats):
|
||||
' Comment, processing instruction or doctype '
|
||||
end = {state.IN_COMMENT:'-->', state.IN_PI:'?>'}.get(state.parse, '>')
|
||||
pos = text.find(end, i+1)
|
||||
fmt = formats['comment' if state.parse == state.IN_COMMENT else 'special']
|
||||
if pos == -1:
|
||||
num = len(text) - i
|
||||
else:
|
||||
num = pos - i + len(end)
|
||||
state.parse = state.NORMAL
|
||||
return [(num, fmt)]
|
||||
|
||||
state_map = {
|
||||
State.NORMAL:normal,
|
||||
State.IN_OPENING_TAG: opening_tag,
|
||||
State.IN_CLOSING_TAG: closing_tag,
|
||||
State.ATTRIBUTE_NAME: attribute_name,
|
||||
State.ATTRIBUTE_VALUE: attribute_value,
|
||||
}
|
||||
|
||||
for x in (State.IN_COMMENT, State.IN_PI, State.IN_DOCTYPE):
|
||||
state_map[x] = in_comment
|
||||
|
||||
for x in (State.SQ_VAL, State.DQ_VAL):
|
||||
state_map[x] = quoted_val
|
||||
|
||||
class HTMLHighlighter(SyntaxHighlighter):
|
||||
|
||||
def __init__(self, parent):
|
||||
SyntaxHighlighter.__init__(self, parent)
|
||||
|
||||
def create_formats(self):
|
||||
t = self.theme
|
||||
self.formats = {
|
||||
'normal': QTextCharFormat(),
|
||||
'tag': t['Function'],
|
||||
'end_tag': t['Identifier'],
|
||||
'attr': t['Type'],
|
||||
'tag_name' : t['Statement'],
|
||||
'entity': t['Special'],
|
||||
'error': t['Error'],
|
||||
'comment': t['Comment'],
|
||||
'special': t['Special'],
|
||||
'string': t['String'],
|
||||
}
|
||||
|
||||
def highlightBlock(self, text):
|
||||
try:
|
||||
self.do_highlight(unicode(text))
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def do_highlight(self, text):
|
||||
state = self.previousBlockState()
|
||||
if state == -1:
|
||||
state = State.NORMAL
|
||||
state = State(state)
|
||||
|
||||
i = 0
|
||||
while i < len(text):
|
||||
fmt = state_map[state.parse](state, text, i, self.formats)
|
||||
for num, f in fmt:
|
||||
if f is not None:
|
||||
self.setFormat(i, num, f)
|
||||
i += num
|
||||
|
||||
self.setCurrentBlockState(state.value)
|
||||
|
131
src/calibre/gui2/tweak_book/editor/text.py
Normal file
131
src/calibre/gui2/tweak_book/editor/text.py
Normal file
@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import textwrap
|
||||
|
||||
from PyQt4.Qt import (
|
||||
QPlainTextEdit, QApplication, QFontDatabase, QToolTip, QPalette, QFont)
|
||||
|
||||
from calibre.gui2.tweak_book import tprefs
|
||||
from calibre.gui2.tweak_book.editor.themes import THEMES, DEFAULT_THEME, theme_color
|
||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
||||
from calibre.gui2.tweak_book.editor.syntax.html import HTMLHighlighter
|
||||
|
||||
_dff = None
|
||||
def default_font_family():
|
||||
global _dff
|
||||
if _dff is None:
|
||||
families = set(map(unicode, QFontDatabase().families()))
|
||||
for x in ('Ubuntu Mono', 'Consolas', 'Liberation Mono'):
|
||||
if x in families:
|
||||
_dff = x
|
||||
break
|
||||
if _dff is None:
|
||||
_dff = 'Courier New'
|
||||
return _dff
|
||||
|
||||
class TextEdit(QPlainTextEdit):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QPlainTextEdit.__init__(self, parent)
|
||||
self.highlighter = SyntaxHighlighter(self)
|
||||
self.apply_theme()
|
||||
self.setMouseTracking(True)
|
||||
|
||||
def apply_theme(self):
|
||||
theme = THEMES.get(tprefs['editor_theme'], None)
|
||||
if theme is None:
|
||||
theme = THEMES[DEFAULT_THEME]
|
||||
self.theme = theme
|
||||
pal = self.palette()
|
||||
pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
|
||||
pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
|
||||
pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
|
||||
pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
|
||||
self.setPalette(pal)
|
||||
self.tooltip_palette = pal = QPalette()
|
||||
pal.setColor(pal.ToolTipBase, theme_color(theme, 'Tooltip', 'bg'))
|
||||
pal.setColor(pal.ToolTipText, theme_color(theme, 'Tooltip', 'fg'))
|
||||
font = self.font()
|
||||
ff = tprefs['editor_font_family']
|
||||
if ff is None:
|
||||
ff = default_font_family()
|
||||
font.setFamily(ff)
|
||||
font.setPointSize(tprefs['editor_font_size'])
|
||||
self.tooltip_font = QFont(font)
|
||||
self.tooltip_font.setPointSize(font.pointSize() - 1)
|
||||
self.setFont(font)
|
||||
self.highlighter.apply_theme(theme)
|
||||
|
||||
def load_text(self, text, syntax='html'):
|
||||
self.highlighter = {'html':HTMLHighlighter}.get(syntax, SyntaxHighlighter)(self)
|
||||
self.highlighter.apply_theme(self.theme)
|
||||
self.highlighter.setDocument(self.document())
|
||||
self.setPlainText(text)
|
||||
|
||||
def event(self, ev):
|
||||
if ev.type() == ev.ToolTip:
|
||||
self.show_tooltip(ev)
|
||||
return True
|
||||
return QPlainTextEdit.event(self, ev)
|
||||
|
||||
def syntax_format_for_cursor(self, cursor):
|
||||
if cursor.isNull():
|
||||
return
|
||||
pos = cursor.positionInBlock()
|
||||
for r in cursor.block().layout().additionalFormats():
|
||||
if r.start <= pos < r.start + r.length:
|
||||
return r.format
|
||||
|
||||
def show_tooltip(self, ev):
|
||||
c = self.cursorForPosition(ev.pos())
|
||||
fmt = self.syntax_format_for_cursor(c)
|
||||
if fmt is not None:
|
||||
tt = unicode(fmt.toolTip())
|
||||
if tt:
|
||||
QToolTip.setFont(self.tooltip_font)
|
||||
QToolTip.setPalette(self.tooltip_palette)
|
||||
QToolTip.showText(ev.globalPos(), textwrap.fill(tt))
|
||||
QToolTip.hideText()
|
||||
ev.ignore()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication([])
|
||||
t = TextEdit()
|
||||
t.show()
|
||||
t.load_text(textwrap.dedent('''\
|
||||
<html>
|
||||
<head>
|
||||
<title>Page title</title>
|
||||
<style type="text/css">
|
||||
body { color: green; }
|
||||
</style>
|
||||
</head id="1">
|
||||
<body>
|
||||
<!-- The start of the document -->a
|
||||
<h1 class="head" id="one" >A heading</h1>
|
||||
<p> A single &. An proper entity &.
|
||||
A single < and a single >.
|
||||
These cases are perfectly simple and easy to
|
||||
distinguish. In a free hour, when our power of choice is
|
||||
untrammelled and when nothing prevents our being able to do
|
||||
what we like best, every pleasure is to be welcomed and every
|
||||
pain avoided.</p>
|
||||
|
||||
<p>
|
||||
But in certain circumstances and owing to the claims of duty or the obligations
|
||||
of business it will frequently occur that pleasures have to be
|
||||
repudiated and annoyances accepted. The wise man therefore
|
||||
always holds in these matters to this principle of selection:
|
||||
he rejects pleasures to secure other greater pleasures, or else
|
||||
he endures pains.</p>
|
||||
</body>
|
||||
</html>
|
||||
'''))
|
||||
app.exec_()
|
||||
|
131
src/calibre/gui2/tweak_book/editor/themes.py
Normal file
131
src/calibre/gui2/tweak_book/editor/themes.py
Normal file
@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from PyQt4.Qt import (QColor, QTextCharFormat, QBrush, QFont)
|
||||
|
||||
underline_styles = {'single', 'dash', 'dot', 'dash_dot', 'dash_dot_dot', 'wave', 'spell'}
|
||||
|
||||
DEFAULT_THEME = 'calibre-dark'
|
||||
|
||||
THEMES = {
|
||||
'calibre-dark': # {{{ Based on the wombat color scheme for vim
|
||||
'''
|
||||
CursorLine bg=2d2d2d
|
||||
CursorColumn bg=2d2d2d
|
||||
ColorColumn bg=2d2d2d
|
||||
MatchParen fg=f6f3e8 bg=857b6f bold
|
||||
Pmenu fg=f6f3e8 bg=444444
|
||||
PmenuSel fg=yellow bg=cae682
|
||||
Tooltip fg=black bg=ffffed
|
||||
|
||||
Cursor bg=656565
|
||||
Normal fg=f6f3e8 bg=242424
|
||||
NonText fg=808080 bg=303030
|
||||
LineNr fg=857b6f bg=000000
|
||||
StatusLine fg=f6f3e8 bg=444444 italic
|
||||
StatusLineNC fg=857b6f bg=444444
|
||||
VertSplit fg=444444 bg=444444
|
||||
Folded bg=384048 fg=a0a8b0
|
||||
Title fg=f6f3e8 bold
|
||||
Visual fg=f6f3e8 bg=444444
|
||||
SpecialKey fg=808080 bg=343434
|
||||
|
||||
Comment fg=99968b
|
||||
Todo fg=8f8f8f
|
||||
Constant fg=e5786d
|
||||
String fg=95e454
|
||||
Identifier fg=cae682
|
||||
Function fg=cae682
|
||||
Type fg=cae682
|
||||
Statement fg=8ac6f2
|
||||
Keyword fg=8ac6f2
|
||||
PreProc fg=e5786d
|
||||
Number fg=e5786d
|
||||
Special fg=e7f6da
|
||||
Error us=wave uc=red
|
||||
|
||||
''', # }}}
|
||||
|
||||
}
|
||||
|
||||
def read_color(col):
|
||||
if QColor.isValidColor(col):
|
||||
return QBrush(QColor(col))
|
||||
try:
|
||||
r, g, b = col[0:2], col[2:4], col[4:6]
|
||||
r, g, b = int(r, 16), int(g, 16), int(b, 16)
|
||||
return QBrush(QColor(r, g, b))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Highlight = namedtuple('Highlight', 'fg bg bold italic underline underline_color')
|
||||
|
||||
def read_theme(raw):
|
||||
ans = {}
|
||||
for line in raw.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
bold = italic = False
|
||||
fg = bg = name = underline = underline_color = None
|
||||
for i, token in enumerate(line.split()):
|
||||
if i == 0:
|
||||
name = token
|
||||
else:
|
||||
if token == 'bold':
|
||||
bold = True
|
||||
elif token == 'italic':
|
||||
italic = True
|
||||
elif '=' in token:
|
||||
prefix, val = token.partition('=')[0::2]
|
||||
if prefix == 'us':
|
||||
underline = val if val in underline_styles else None
|
||||
elif prefix == 'uc':
|
||||
underline_color = read_color(val)
|
||||
elif prefix == 'fg':
|
||||
fg = read_color(val)
|
||||
elif prefix == 'bg':
|
||||
bg = read_color(val)
|
||||
if name is not None:
|
||||
ans[name] = Highlight(fg, bg, bold, italic, underline, underline_color)
|
||||
return ans
|
||||
|
||||
|
||||
THEMES = {k:read_theme(raw) for k, raw in THEMES.iteritems()}
|
||||
|
||||
def u(x):
|
||||
x = {'spell':'SpellCheck', 'dash_dot':'DashDot', 'dash_dot_dot':'DashDotDot'}.get(x, x.capitalize())
|
||||
if 'Dot' in x:
|
||||
return x + 'Line'
|
||||
return x + 'Underline'
|
||||
underline_styles = {x:getattr(QTextCharFormat, u(x)) for x in underline_styles}
|
||||
|
||||
def highlight_to_char_format(h):
|
||||
ans = QTextCharFormat()
|
||||
if h.bold:
|
||||
ans.setFontWeight(QFont.Bold)
|
||||
if h.italic:
|
||||
ans.setFontItalic(True)
|
||||
if h.fg is not None:
|
||||
ans.setForeground(h.fg)
|
||||
if h.bg is not None:
|
||||
ans.setBackground(h.bg)
|
||||
if h.underline is not None:
|
||||
ans.setUnderlineStyle(underline_styles[h.underline])
|
||||
if h.underline_color is not None:
|
||||
ans.setUnderlineColor(h.underline_color.color())
|
||||
return ans
|
||||
|
||||
def theme_color(theme, name, attr):
|
||||
try:
|
||||
return getattr(theme[name], attr).color()
|
||||
except (KeyError, AttributeError):
|
||||
return getattr(THEMES[DEFAULT_THEME], attr).color()
|
||||
|
Loading…
x
Reference in New Issue
Block a user