Live CSS: Indicate which CSS properties are overriden by higher priority rules

This commit is contained in:
Kovid Goyal 2014-05-24 11:31:09 +05:30
parent 6786df58d1
commit dec4720fb6
2 changed files with 70 additions and 18 deletions

View File

@ -533,8 +533,9 @@ where the style was defined, in case you wish to make any changes to the style
rules. Style rules that apply directly to the tag, as well as rules that are rules. Style rules that apply directly to the tag, as well as rules that are
inherited from parent tags are shown. inherited from parent tags are shown.
The panel also shows you what the finally calculated styles for the tag are, so The panel also shows you what the finally calculated styles for the tag are.
you can see which rules were applied and which were overridden. Properties in the list that are superseded by higher priority rules are shown
with a line through them.
You can enable the Live CSS panel via :guilabel:`View->Live CSS`. You can enable the Live CSS panel via :guilabel:`View->Live CSS`.

View File

@ -8,6 +8,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import json import json
from cssselect import parse
from PyQt4.Qt import ( from PyQt4.Qt import (
QWidget, QTimer, QStackedLayout, QLabel, QScrollArea, QVBoxLayout, QWidget, QTimer, QStackedLayout, QLabel, QScrollArea, QVBoxLayout,
QPainter, Qt, QPalette, QRect, QSize, QSizePolicy, pyqtSignal, QPainter, Qt, QPalette, QRect, QSize, QSizePolicy, pyqtSignal,
@ -82,14 +83,15 @@ class Heading(QWidget): # {{{
class Cell(object): # {{{ class Cell(object): # {{{
__slots__ = ('rect', 'text', 'right_align', 'color_role', 'override_color', 'swatch') __slots__ = ('rect', 'text', 'right_align', 'color_role', 'override_color', 'swatch', 'is_overriden')
SIDE_MARGIN = 5 SIDE_MARGIN = 5
FLAGS = Qt.AlignVCenter | Qt.TextSingleLine | Qt.TextIncludeTrailingSpaces FLAGS = Qt.AlignVCenter | Qt.TextSingleLine | Qt.TextIncludeTrailingSpaces
def __init__(self, text, rect, right_align=False, color_role=QPalette.WindowText, swatch=None): def __init__(self, text, rect, right_align=False, color_role=QPalette.WindowText, swatch=None, is_overriden=False):
self.rect, self.text = rect, text self.rect, self.text = rect, text
self.right_align = right_align self.right_align = right_align
self.is_overriden = is_overriden
self.color_role = color_role self.color_role = color_role
self.override_color = None self.override_color = None
self.swatch = swatch self.swatch = swatch
@ -106,6 +108,10 @@ class Cell(object): # {{{
if self.swatch is not None: if self.swatch is not None:
r = QRect(br.right() + self.SIDE_MARGIN // 2, br.top() + 2, br.height() - 4, br.height() - 4) r = QRect(br.right() + self.SIDE_MARGIN // 2, br.top() + 2, br.height() - 4, br.height() - 4)
painter.fillRect(r, self.swatch) painter.fillRect(r, self.swatch)
br.setRight(r.right())
if self.is_overriden:
painter.setPen(palette.color(QPalette.WindowText))
painter.drawLine(br.left(), br.top() + br.height() // 2, br.right(), br.top() + br.height() // 2)
# }}} # }}}
class Declaration(QWidget): class Declaration(QWidget):
@ -144,14 +150,14 @@ class Declaration(QWidget):
]) ])
ypos += max(br1.height(), br2.height()) + 2 * line_spacing ypos += max(br1.height(), br2.height()) + 2 * line_spacing
for (name, value, important, color) in self.data['properties']: for prop in self.data['properties']:
text = name + ':\xa0' text = prop.name + ':\xa0'
br1 = bounding_rect(text) br1 = bounding_rect(text)
vtext = value + '\xa0' + ('!' if important else '') + important vtext = prop.value + '\xa0' + ('!' if prop.important else '') + prop.important
br2 = bounding_rect(vtext) br2 = bounding_rect(vtext)
self.rows.append([ self.rows.append([
Cell(text, QRect(side_margin, ypos, br1.width(), br1.height()), color_role=QPalette.LinkVisited), Cell(text, QRect(side_margin, ypos, br1.width(), br1.height()), color_role=QPalette.LinkVisited, is_overriden=prop.is_overriden),
Cell(vtext, QRect(br1.right() + side_margin, ypos, br2.width(), br2.height()), swatch=color) Cell(vtext, QRect(br1.right() + side_margin, ypos, br2.width(), br2.height()), swatch=prop.color, is_overriden=prop.is_overriden)
]) ])
ypos += max(br1.height(), br2.height()) + line_spacing ypos += max(br1.height(), br2.height()) + line_spacing
@ -270,7 +276,7 @@ class Box(QWidget):
h.toggled.connect(self.heading_toggled) h.toggled.connect(self.heading_toggled)
self.widgets.append(h), self.layout().addWidget(h) self.widgets.append(h), self.layout().addWidget(h)
ccss = data['computed_css'] ccss = data['computed_css']
declaration = {'properties':[[k, ccss[k][0], '', ccss[k][1]] for k in sorted(ccss)]} declaration = {'properties':[Property([k, ccss[k][0], '', ccss[k][1]]) for k in sorted(ccss)]}
d = Declaration(None, declaration, is_first=True, parent=self) d = Declaration(None, declaration, is_first=True, parent=self)
self.widgets.append(d), self.layout().addWidget(d) self.widgets.append(d), self.layout().addWidget(d)
@ -288,6 +294,19 @@ class Box(QWidget):
w.do_layout() w.do_layout()
w.updateGeometry() w.updateGeometry()
class Property(object):
__slots__ = 'name', 'value', 'important', 'color', 'specificity', 'is_overriden'
def __init__(self, prop, specificity=()):
self.name, self.value, self.important, self.color = prop
self.specificity = tuple(specificity)
self.is_overriden = False
def __repr__(self):
return '<Property name=%s value=%s important=%s color=%s specificity=%s is_overriden=%s>' % (
self.name, self.value, self.important, self.color, self.specificity, self.is_overriden)
class LiveCSS(QWidget): class LiveCSS(QWidget):
goto_declaration = pyqtSignal(object) goto_declaration = pyqtSignal(object)
@ -393,17 +412,49 @@ class LiveCSS(QWidget):
json.dumps(sourceline), json.dumps(tags))).toString()) json.dumps(sourceline), json.dumps(tags))).toString())
result = json.loads(result) result = json.loads(result)
if result is not None: if result is not None:
maximum_specificities = {}
for node in result['nodes']: for node in result['nodes']:
for item in node['css']: is_ancestor = node['is_ancestor']
href = item['href'] for rule in node['css']:
if hasattr(href, 'startswith') and href.startswith('file://'): self.process_rule(rule, is_ancestor, maximum_specificities)
href = href[len('file://'):] for node in result['nodes']:
if iswindows and href.startswith('/'): for rule in node['css']:
href = href[1:] for prop in rule['properties']:
if href: if prop.specificity < maximum_specificities[prop.name]:
item['href'] = current_container().abspath_to_name(href, root=self.preview.current_root) prop.is_overriden = True
return result return result
def process_rule(self, rule, is_ancestor, maximum_specificities):
selector = rule['selector']
sheet_index = rule['sheet_index']
rule_address = rule['rule_address'] or ()
if selector is not None:
try:
specificity = [0] + list(parse(selector)[0].specificity())
except (AttributeError, TypeError):
specificity = [0, 0, 0, 0]
else: # style attribute
specificity = [1, 0, 0, 0]
specificity.extend((sheet_index, tuple(rule_address)))
ancestor_specificity = 0 if is_ancestor else 1
properties = []
for prop in rule['properties']:
important = 1 if prop[-1] == 'important' else 0
p = Property(prop, [ancestor_specificity] + [important] + specificity)
properties.append(p)
if p.specificity > maximum_specificities.get(p.name, (0,0,0,0,0,0)):
maximum_specificities[p.name] = p.specificity
rule['properties'] = properties
href = rule['href']
if hasattr(href, 'startswith') and href.startswith('file://'):
href = href[len('file://'):]
if iswindows and href.startswith('/'):
href = href[1:]
if href:
rule['href'] = current_container().abspath_to_name(href, root=self.preview.current_root)
@property @property
def current_name(self): def current_name(self):
return self.preview.current_name return self.preview.current_name