Edit Book: Live CSS: Allow copying of CSS rules by right clicking on the Live CSS panel. Fixes #1485237 [[Enhancement] Make info in Live CSS window copiable](https://bugs.launchpad.net/calibre/+bug/1485237)

This commit is contained in:
Kovid Goyal 2015-08-16 08:13:49 +05:30
parent f945643acc
commit cecc7efb8a

View File

@ -11,7 +11,7 @@ import json
from PyQt5.Qt import ( from PyQt5.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,
QColor) QColor, QMenu, QApplication, QIcon)
from calibre.constants import iswindows from calibre.constants import iswindows
from calibre.gui2.tweak_book import editors, actions, current_container, tprefs from calibre.gui2.tweak_book import editors, actions, current_container, tprefs
@ -22,6 +22,7 @@ from css_selectors import parse, SelectorError
class Heading(QWidget): # {{{ class Heading(QWidget): # {{{
toggled = pyqtSignal(object) toggled = pyqtSignal(object)
context_menu_requested = pyqtSignal(object, object)
def __init__(self, text, expanded=True, parent=None): def __init__(self, text, expanded=True, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -32,6 +33,10 @@ class Heading(QWidget): # {{{
self.hovering = False self.hovering = False
self.do_layout() self.do_layout()
@property
def lines_for_copy(self):
return [self.text]
def do_layout(self): def do_layout(self):
try: try:
f = self.parent().font() f = self.parent().font()
@ -79,6 +84,9 @@ class Heading(QWidget): # {{{
self.hovering = False self.hovering = False
self.update() self.update()
return QWidget.leaveEvent(self, ev) return QWidget.leaveEvent(self, ev)
def contextMenuEvent(self, ev):
self.context_menu_requested.emit(self, ev)
# }}} # }}}
class Cell(object): # {{{ class Cell(object): # {{{
@ -117,6 +125,7 @@ class Cell(object): # {{{
class Declaration(QWidget): class Declaration(QWidget):
hyperlink_activated = pyqtSignal(object) hyperlink_activated = pyqtSignal(object)
context_menu_requested = pyqtSignal(object, object)
def __init__(self, html_name, data, is_first=False, parent=None): def __init__(self, html_name, data, is_first=False, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -124,6 +133,7 @@ class Declaration(QWidget):
self.data = data self.data = data
self.is_first = is_first self.is_first = is_first
self.html_name = html_name self.html_name = html_name
self.lines_for_copy = []
self.do_layout() self.do_layout()
self.setMouseTracking(True) self.setMouseTracking(True)
@ -149,6 +159,7 @@ class Declaration(QWidget):
Cell(sel, QRect(br1.right() + side_margin, ypos, br2.width(), br2.height()), right_align=True) Cell(sel, QRect(br1.right() + side_margin, ypos, br2.width(), br2.height()), right_align=True)
]) ])
ypos += max(br1.height(), br2.height()) + 2 * line_spacing ypos += max(br1.height(), br2.height()) + 2 * line_spacing
self.lines_for_copy.append(name + ' ' + sel)
for prop in self.data['properties']: for prop in self.data['properties']:
text = prop.name + ':\xa0' text = prop.name + ':\xa0'
@ -159,6 +170,7 @@ class Declaration(QWidget):
Cell(text, QRect(side_margin, ypos, br1.width(), br1.height()), color_role=QPalette.LinkVisited, is_overriden=prop.is_overriden), 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=prop.color, is_overriden=prop.is_overriden) Cell(vtext, QRect(br1.right() + side_margin, ypos, br2.width(), br2.height()), swatch=prop.color, is_overriden=prop.is_overriden)
]) ])
self.lines_for_copy.append(text + vtext)
ypos += max(br1.height(), br2.height()) + line_spacing ypos += max(br1.height(), br2.height()) + line_spacing
self.height_hint = ypos + line_spacing self.height_hint = ypos + line_spacing
@ -204,7 +216,7 @@ class Declaration(QWidget):
return QWidget.mouseMoveEvent(self, ev) return QWidget.mouseMoveEvent(self, ev)
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
if hasattr(self, 'hyperlink_rect'): if hasattr(self, 'hyperlink_rect') and ev.button() == Qt.LeftButton:
pos = ev.pos() pos = ev.pos()
if self.hyperlink_rect.contains(pos): if self.hyperlink_rect.contains(pos):
self.emit_hyperlink_activated() self.emit_hyperlink_activated()
@ -236,6 +248,9 @@ class Declaration(QWidget):
cell.override_color = QColor(Qt.red) if hovering else None cell.override_color = QColor(Qt.red) if hovering else None
self.update() self.update()
def contextMenuEvent(self, ev):
self.context_menu_requested.emit(self, ev)
class Box(QWidget): class Box(QWidget):
hyperlink_activated = pyqtSignal(object) hyperlink_activated = pyqtSignal(object)
@ -250,7 +265,7 @@ class Box(QWidget):
def show_data(self, data): def show_data(self, data):
for w in self.widgets: for w in self.widgets:
self.layout().removeWidget(w) self.layout().removeWidget(w)
for x in ('toggled', 'hyperlink_activated'): for x in ('toggled', 'hyperlink_activated', 'context_menu_requested'):
if hasattr(w, x): if hasattr(w, x):
try: try:
getattr(w, x).disconnect() getattr(w, x).disconnect()
@ -279,6 +294,8 @@ class Box(QWidget):
declaration = {'properties':[Property([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)
for w in self.widgets:
w.context_menu_requested.connect(self.context_menu_requested)
def heading_toggled(self, heading): def heading_toggled(self, heading):
for i, w in enumerate(self.widgets): for i, w in enumerate(self.widgets):
@ -294,6 +311,49 @@ class Box(QWidget):
w.do_layout() w.do_layout()
w.updateGeometry() w.updateGeometry()
@property
def lines_for_copy(self):
ans = []
for w in self.widgets:
ans += w.lines_for_copy
return ans
def context_menu_requested(self, widget, ev):
if isinstance(widget, Heading):
start = widget
else:
found = False
for w in reversed(self.widgets):
if w is widget:
found = True
elif found and isinstance(w, Heading):
start = w
break
else:
return
found = False
lines = []
for w in self.widgets:
if found and isinstance(w, Heading):
break
if w is start:
found = True
if found:
lines += w.lines_for_copy
if not lines:
return
block = '\n'.join(lines).replace('\xa0', ' ')
heading = lines[0]
m = QMenu(self)
m.addAction(QIcon(I('edit-copy.png')), _('Copy') + ' ' + heading.replace('\xa0', ' '), lambda : QApplication.instance().clipboard().setText(block))
all_lines = []
for w in self.widgets:
all_lines += w.lines_for_copy
all_text = '\n'.join(all_lines).replace('\xa0', ' ')
m.addAction(QIcon(I('edit-copy.png')), _('Copy everything'), lambda : QApplication.instance().clipboard().setText(all_text))
m.exec_(ev.globalPos())
class Property(object): class Property(object):
__slots__ = 'name', 'value', 'important', 'color', 'specificity', 'is_overriden' __slots__ = 'name', 'value', 'important', 'color', 'specificity', 'is_overriden'