Live CSS renders

This commit is contained in:
Kovid Goyal 2014-05-20 11:49:49 +05:30
parent b9e96cd34c
commit 9bf60add18
4 changed files with 231 additions and 8 deletions

Binary file not shown.

View File

@ -189,7 +189,7 @@ process_rules = (node, cssRules, address, sheet, sheet_index, matching_selectors
break
properties = get_style_properties(rule.style, all_properties, node_style, is_ancestor)
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)
get_matched_css = (node, is_ancestor, all_properties) ->
@ -211,7 +211,7 @@ get_matched_css = (node, is_ancestor, all_properties) ->
if node.getAttribute('style')
properties = get_style_properties(node.style, all_properties, node_style, is_ancestor)
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)
return ans.reverse()
@ -326,7 +326,7 @@ class PreviewIntegration
while target and target.ownerDocument
css = get_matched_css(target, is_ancestor, all_properties)
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
is_ancestor = true
return JSON.stringify(ans)

View File

@ -6,12 +6,194 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__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.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):
@ -19,21 +201,61 @@ class LiveCSS(QWidget):
QWidget.__init__(self, parent)
self.preview = preview
preview.refreshed.connect(self.update_data)
self.apply_theme()
self.setAutoFillBackground(True)
self.update_timer = QTimer(self)
self.update_timer.timeout.connect(self.update_data)
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):
pass # TODO: Implement this
self.stack.setCurrentIndex(0)
def show_data(self, editor_name, sourceline, tags):
if sourceline is None:
self.clear()
else:
data = self.read_data(sourceline, tags)
if data is None:
if data is None or len(data['computed_css']) < 1:
self.clear()
return
data['html_name'] = editor_name
self.box.show_data(data)
self.stack.setCurrentIndex(1)
def read_data(self, sourceline, tags):
mf = self.preview.view.page().mainFrame()

View File

@ -261,6 +261,7 @@ class Main(MainWindow):
area = getattr(Qt, '%sDockWidgetArea' % capitalize({'vertical':h, 'horizontal':v}[pref]))
self.setCorner(getattr(Qt, '%s%sCorner' % tuple(map(capitalize, (v, h)))), area)
self.preview.apply_settings()
self.live_css.apply_theme()
def show_status_message(self, msg, timeout=5):
self.status_bar.showMessage(msg, int(timeout*1000))