mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Live CSS renders
This commit is contained in:
parent
b9e96cd34c
commit
9bf60add18
Binary file not shown.
@ -189,7 +189,7 @@ process_rules = (node, cssRules, address, sheet, sheet_index, matching_selectors
|
|||||||
break
|
break
|
||||||
properties = get_style_properties(rule.style, all_properties, node_style, is_ancestor)
|
properties = get_style_properties(rule.style, all_properties, node_style, is_ancestor)
|
||||||
if properties.length > 0
|
if properties.length > 0
|
||||||
data = {'selector':st, 'type':type, 'href':href, 'properties':properties, 'is_ancestor':is_ancestor, 'rule_address':rule_address, 'sheet_index':sheet_index}
|
data = {'selector':st, 'type':type, 'href':href, 'properties':properties, 'rule_address':rule_address, 'sheet_index':sheet_index}
|
||||||
ans.push(data)
|
ans.push(data)
|
||||||
|
|
||||||
get_matched_css = (node, is_ancestor, all_properties) ->
|
get_matched_css = (node, is_ancestor, all_properties) ->
|
||||||
@ -211,7 +211,7 @@ get_matched_css = (node, is_ancestor, all_properties) ->
|
|||||||
if node.getAttribute('style')
|
if node.getAttribute('style')
|
||||||
properties = get_style_properties(node.style, all_properties, node_style, is_ancestor)
|
properties = get_style_properties(node.style, all_properties, node_style, is_ancestor)
|
||||||
if properties.length > 0
|
if properties.length > 0
|
||||||
data = {'selector':null, 'type':'inline', 'href':get_sourceline_address(node), 'properties':properties, 'is_ancestor':is_ancestor, 'rule_address':null, 'sheet_index':null}
|
data = {'selector':null, 'type':'inline', 'href':get_sourceline_address(node), 'properties':properties, 'rule_address':null, 'sheet_index':null}
|
||||||
ans.push(data)
|
ans.push(data)
|
||||||
|
|
||||||
return ans.reverse()
|
return ans.reverse()
|
||||||
@ -326,7 +326,7 @@ class PreviewIntegration
|
|||||||
while target and target.ownerDocument
|
while target and target.ownerDocument
|
||||||
css = get_matched_css(target, is_ancestor, all_properties)
|
css = get_matched_css(target, is_ancestor, all_properties)
|
||||||
if css.length > 0
|
if css.length > 0
|
||||||
ans['nodes'].push({'name':target.tagName?.toLowerCase(), 'css':css})
|
ans['nodes'].push({'name':target.tagName?.toLowerCase(), 'css':css, 'is_ancestor':is_ancestor})
|
||||||
target = target.parentNode
|
target = target.parentNode
|
||||||
is_ancestor = true
|
is_ancestor = true
|
||||||
return JSON.stringify(ans)
|
return JSON.stringify(ans)
|
||||||
|
@ -6,12 +6,194 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import json
|
import json, math
|
||||||
|
|
||||||
from PyQt4.Qt import (QWidget, QTimer)
|
from PyQt4.Qt import (
|
||||||
|
QWidget, QTimer, QStackedLayout, QLabel, QScrollArea, QVBoxLayout,
|
||||||
|
QPainter, Qt, QFontInfo, QPalette, QRect, QSize, QSizePolicy)
|
||||||
|
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
from calibre.gui2.tweak_book import editors, actions, current_container
|
from calibre.gui2.tweak_book import editors, actions, current_container, tprefs
|
||||||
|
from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color
|
||||||
|
from calibre.gui2.tweak_book.editor.text import default_font_family
|
||||||
|
|
||||||
|
class Heading(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, text, expanded=True, parent=None):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
||||||
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
|
self.text = text
|
||||||
|
self.expanded = expanded
|
||||||
|
self.hovering = False
|
||||||
|
self.do_layout()
|
||||||
|
|
||||||
|
def do_layout(self):
|
||||||
|
try:
|
||||||
|
f = self.parent().font()
|
||||||
|
except AttributeError:
|
||||||
|
return
|
||||||
|
f.setBold(True)
|
||||||
|
sz = QFontInfo(f).pointSize()
|
||||||
|
f.setPointSize(int(math.ceil(1.2 * sz)))
|
||||||
|
self.setFont(f)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rendered_text(self):
|
||||||
|
return ('▾' if self.expanded else '▸') + '\xa0' + self.text
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
fm = self.fontMetrics()
|
||||||
|
sz = fm.boundingRect(self.rendered_text).size()
|
||||||
|
return sz
|
||||||
|
|
||||||
|
def paintEvent(self, ev):
|
||||||
|
p = QPainter(self)
|
||||||
|
p.setClipRect(ev.rect())
|
||||||
|
bg = self.palette().color(QPalette.AlternateBase)
|
||||||
|
if self.hovering:
|
||||||
|
bg = bg.lighter(115)
|
||||||
|
p.fillRect(ev.rect(), bg)
|
||||||
|
try:
|
||||||
|
p.drawText(ev.rect(), Qt.AlignLeft|Qt.AlignVCenter|Qt.TextSingleLine, self.rendered_text)
|
||||||
|
finally:
|
||||||
|
p.end()
|
||||||
|
|
||||||
|
def enterEvent(self, ev):
|
||||||
|
self.hovering = True
|
||||||
|
self.update()
|
||||||
|
return QWidget.enterEvent(self, ev)
|
||||||
|
|
||||||
|
def leaveEvent(self, ev):
|
||||||
|
self.hovering = False
|
||||||
|
self.update()
|
||||||
|
return QWidget.leaveEvent(self, ev)
|
||||||
|
|
||||||
|
class Cell(object):
|
||||||
|
|
||||||
|
SIDE_MARGIN = 5
|
||||||
|
FLAGS = Qt.AlignVCenter | Qt.TextSingleLine | Qt.TextIncludeTrailingSpaces
|
||||||
|
|
||||||
|
def __init__(self, text, rect, right_align=False, color_role=QPalette.WindowText):
|
||||||
|
self.rect, self.text = rect, text
|
||||||
|
self.right_align = right_align
|
||||||
|
self.color_role = color_role
|
||||||
|
|
||||||
|
def draw(self, painter, width, palette):
|
||||||
|
flags = self.FLAGS | (Qt.AlignRight if self.right_align else Qt.AlignLeft)
|
||||||
|
rect = QRect(self.rect)
|
||||||
|
if self.right_align:
|
||||||
|
rect.setRight(width - self.SIDE_MARGIN)
|
||||||
|
painter.setPen(palette.color(self.color_role))
|
||||||
|
painter.drawText(rect, flags, self.text)
|
||||||
|
|
||||||
|
class Declaration(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, html_name, data, is_first=False, parent=None):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
||||||
|
self.data = data
|
||||||
|
self.is_first = is_first
|
||||||
|
self.html_name = html_name
|
||||||
|
self.do_layout()
|
||||||
|
|
||||||
|
def do_layout(self):
|
||||||
|
fm = self.fontMetrics()
|
||||||
|
bounding_rect = lambda text: fm.boundingRect(0, 0, 10000, 10000, Cell.FLAGS, text)
|
||||||
|
line_spacing = 5
|
||||||
|
side_margin = Cell.SIDE_MARGIN
|
||||||
|
self.rows = []
|
||||||
|
ypos = line_spacing + (1 if self.is_first else 0)
|
||||||
|
if 'href' in self.data:
|
||||||
|
name = self.data['href']
|
||||||
|
if isinstance(name, list):
|
||||||
|
name = self.html_name
|
||||||
|
br1 = bounding_rect(name)
|
||||||
|
sel = self.data['selector'] or ''
|
||||||
|
if self.data['type'] == 'inline':
|
||||||
|
sel = 'style=""'
|
||||||
|
br2 = bounding_rect(sel)
|
||||||
|
self.hyperlink_rect = QRect(side_margin, ypos, br1.width(), br1.height())
|
||||||
|
self.rows.append([
|
||||||
|
Cell(name, self.hyperlink_rect, color_role=QPalette.Link),
|
||||||
|
Cell(sel, QRect(br1.right() + side_margin, ypos, br2.width(), br2.height()), right_align=True)
|
||||||
|
])
|
||||||
|
ypos += max(br1.height(), br2.height()) + 2 * line_spacing
|
||||||
|
|
||||||
|
for (name, value, important) in self.data['properties']:
|
||||||
|
text = name + ':\xa0'
|
||||||
|
br1 = bounding_rect(text)
|
||||||
|
vtext = value + '\xa0' + ('!' if important else '') + important
|
||||||
|
br2 = bounding_rect(vtext)
|
||||||
|
self.rows.append([
|
||||||
|
Cell(text, QRect(side_margin, ypos, br1.width(), br1.height()), color_role=QPalette.LinkVisited),
|
||||||
|
Cell(vtext, QRect(br1.right() + side_margin, ypos, br2.width(), br2.height()))
|
||||||
|
])
|
||||||
|
ypos += max(br1.height(), br2.height()) + line_spacing
|
||||||
|
|
||||||
|
self.height_hint = ypos + line_spacing
|
||||||
|
self.width_hint = max(row[-1].rect.right() + side_margin for row in self.rows) if self.rows else 0
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
return QSize(self.width_hint, self.height_hint)
|
||||||
|
|
||||||
|
def paintEvent(self, ev):
|
||||||
|
p = QPainter(self)
|
||||||
|
p.setClipRect(ev.rect())
|
||||||
|
palette = self.palette()
|
||||||
|
p.setPen(palette.color(QPalette.WindowText))
|
||||||
|
if not self.is_first:
|
||||||
|
p.drawLine(0, 0, self.width(), 0)
|
||||||
|
try:
|
||||||
|
for row in self.rows:
|
||||||
|
for cell in row:
|
||||||
|
p.save()
|
||||||
|
try:
|
||||||
|
cell.draw(p, self.width(), palette)
|
||||||
|
finally:
|
||||||
|
p.restore()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
p.end()
|
||||||
|
|
||||||
|
|
||||||
|
class Box(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.l = l = QVBoxLayout(self)
|
||||||
|
l.setAlignment(Qt.AlignTop)
|
||||||
|
self.setLayout(l)
|
||||||
|
self.widgets = []
|
||||||
|
|
||||||
|
def show_data(self, data):
|
||||||
|
for w in self.widgets:
|
||||||
|
self.layout().removeWidget(w)
|
||||||
|
w.deleteLater()
|
||||||
|
self.widgets = []
|
||||||
|
for node in data['nodes']:
|
||||||
|
node_name = node['name']
|
||||||
|
if node['is_ancestor']:
|
||||||
|
title = _('Inherited from %s') % node_name
|
||||||
|
else:
|
||||||
|
title = _('Matched CSS rules for %s') % node_name
|
||||||
|
h = Heading(title, parent=self)
|
||||||
|
self.widgets.append(h), self.layout().addWidget(h)
|
||||||
|
for i, declaration in enumerate(node['css']):
|
||||||
|
d = Declaration(data['html_name'], declaration, is_first=i == 0, parent=self)
|
||||||
|
self.widgets.append(d), self.layout().addWidget(d)
|
||||||
|
|
||||||
|
h = Heading(_('Computed final style'), parent=self)
|
||||||
|
self.widgets.append(h), self.layout().addWidget(h)
|
||||||
|
keys = sorted(data['computed_css'])
|
||||||
|
declaration = {'properties':[[k, data['computed_css'][k], ''] for k in keys]}
|
||||||
|
d = Declaration(None, declaration, is_first=True, parent=self)
|
||||||
|
self.widgets.append(d), self.layout().addWidget(d)
|
||||||
|
|
||||||
|
def relayout(self):
|
||||||
|
for w in self.widgets:
|
||||||
|
w.do_layout()
|
||||||
|
w.updateGeometry()
|
||||||
|
|
||||||
class LiveCSS(QWidget):
|
class LiveCSS(QWidget):
|
||||||
|
|
||||||
@ -19,21 +201,61 @@ class LiveCSS(QWidget):
|
|||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
self.preview = preview
|
self.preview = preview
|
||||||
preview.refreshed.connect(self.update_data)
|
preview.refreshed.connect(self.update_data)
|
||||||
|
self.apply_theme()
|
||||||
|
self.setAutoFillBackground(True)
|
||||||
self.update_timer = QTimer(self)
|
self.update_timer = QTimer(self)
|
||||||
self.update_timer.timeout.connect(self.update_data)
|
self.update_timer.timeout.connect(self.update_data)
|
||||||
self.update_timer.setSingleShot(True)
|
self.update_timer.setSingleShot(True)
|
||||||
|
|
||||||
|
self.stack = s = QStackedLayout(self)
|
||||||
|
self.setLayout(s)
|
||||||
|
|
||||||
|
self.clear_label = la = QLabel('<h3>' + _(
|
||||||
|
'No style information found') + '</h3><p>' + _(
|
||||||
|
'Move the cursor inside a HTML tag to see what styles'
|
||||||
|
' apply to that tag.'))
|
||||||
|
la.setWordWrap(True)
|
||||||
|
la.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||||
|
s.addWidget(la)
|
||||||
|
|
||||||
|
self.box = box = Box(self)
|
||||||
|
self.scroll = sc = QScrollArea(self)
|
||||||
|
sc.setWidget(box)
|
||||||
|
sc.setWidgetResizable(True)
|
||||||
|
s.addWidget(sc)
|
||||||
|
|
||||||
|
def apply_theme(self):
|
||||||
|
f = self.font()
|
||||||
|
f.setFamily(tprefs['editor_font_family'] or default_font_family())
|
||||||
|
f.setPointSize(tprefs['editor_font_size'])
|
||||||
|
self.setFont(f)
|
||||||
|
theme = THEMES.get(tprefs['editor_theme'], None)
|
||||||
|
if theme is None:
|
||||||
|
theme = THEMES[default_theme()]
|
||||||
|
pal = self.palette()
|
||||||
|
pal.setColor(pal.Window, theme_color(theme, 'Normal', 'bg'))
|
||||||
|
pal.setColor(pal.WindowText, theme_color(theme, 'Normal', 'fg'))
|
||||||
|
pal.setColor(pal.AlternateBase, theme_color(theme, 'HighlightRegion', 'bg'))
|
||||||
|
pal.setColor(pal.LinkVisited, theme_color(theme, 'Keyword', 'fg'))
|
||||||
|
self.setPalette(pal)
|
||||||
|
if hasattr(self, 'box'):
|
||||||
|
self.box.relayout()
|
||||||
|
self.update()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
pass # TODO: Implement this
|
self.stack.setCurrentIndex(0)
|
||||||
|
|
||||||
def show_data(self, editor_name, sourceline, tags):
|
def show_data(self, editor_name, sourceline, tags):
|
||||||
if sourceline is None:
|
if sourceline is None:
|
||||||
self.clear()
|
self.clear()
|
||||||
else:
|
else:
|
||||||
data = self.read_data(sourceline, tags)
|
data = self.read_data(sourceline, tags)
|
||||||
if data is None:
|
if data is None or len(data['computed_css']) < 1:
|
||||||
self.clear()
|
self.clear()
|
||||||
return
|
return
|
||||||
|
data['html_name'] = editor_name
|
||||||
|
self.box.show_data(data)
|
||||||
|
self.stack.setCurrentIndex(1)
|
||||||
|
|
||||||
def read_data(self, sourceline, tags):
|
def read_data(self, sourceline, tags):
|
||||||
mf = self.preview.view.page().mainFrame()
|
mf = self.preview.view.page().mainFrame()
|
||||||
|
@ -261,6 +261,7 @@ class Main(MainWindow):
|
|||||||
area = getattr(Qt, '%sDockWidgetArea' % capitalize({'vertical':h, 'horizontal':v}[pref]))
|
area = getattr(Qt, '%sDockWidgetArea' % capitalize({'vertical':h, 'horizontal':v}[pref]))
|
||||||
self.setCorner(getattr(Qt, '%s%sCorner' % tuple(map(capitalize, (v, h)))), area)
|
self.setCorner(getattr(Qt, '%s%sCorner' % tuple(map(capitalize, (v, h)))), area)
|
||||||
self.preview.apply_settings()
|
self.preview.apply_settings()
|
||||||
|
self.live_css.apply_theme()
|
||||||
|
|
||||||
def show_status_message(self, msg, timeout=5):
|
def show_status_message(self, msg, timeout=5):
|
||||||
self.status_bar.showMessage(msg, int(timeout*1000))
|
self.status_bar.showMessage(msg, int(timeout*1000))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user