From cecc7efb8a829a6eecb8a91b35fffed9d70d4a51 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 16 Aug 2015 08:13:49 +0530 Subject: [PATCH] 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) --- src/calibre/gui2/tweak_book/live_css.py | 66 +++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/tweak_book/live_css.py b/src/calibre/gui2/tweak_book/live_css.py index 0446e3c05d..a4e1542b44 100644 --- a/src/calibre/gui2/tweak_book/live_css.py +++ b/src/calibre/gui2/tweak_book/live_css.py @@ -11,7 +11,7 @@ import json from PyQt5.Qt import ( QWidget, QTimer, QStackedLayout, QLabel, QScrollArea, QVBoxLayout, QPainter, Qt, QPalette, QRect, QSize, QSizePolicy, pyqtSignal, - QColor) + QColor, QMenu, QApplication, QIcon) from calibre.constants import iswindows from calibre.gui2.tweak_book import editors, actions, current_container, tprefs @@ -22,6 +22,7 @@ from css_selectors import parse, SelectorError class Heading(QWidget): # {{{ toggled = pyqtSignal(object) + context_menu_requested = pyqtSignal(object, object) def __init__(self, text, expanded=True, parent=None): QWidget.__init__(self, parent) @@ -32,6 +33,10 @@ class Heading(QWidget): # {{{ self.hovering = False self.do_layout() + @property + def lines_for_copy(self): + return [self.text] + def do_layout(self): try: f = self.parent().font() @@ -79,6 +84,9 @@ class Heading(QWidget): # {{{ self.hovering = False self.update() return QWidget.leaveEvent(self, ev) + + def contextMenuEvent(self, ev): + self.context_menu_requested.emit(self, ev) # }}} class Cell(object): # {{{ @@ -117,6 +125,7 @@ class Cell(object): # {{{ class Declaration(QWidget): hyperlink_activated = pyqtSignal(object) + context_menu_requested = pyqtSignal(object, object) def __init__(self, html_name, data, is_first=False, parent=None): QWidget.__init__(self, parent) @@ -124,6 +133,7 @@ class Declaration(QWidget): self.data = data self.is_first = is_first self.html_name = html_name + self.lines_for_copy = [] self.do_layout() 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) ]) ypos += max(br1.height(), br2.height()) + 2 * line_spacing + self.lines_for_copy.append(name + ' ' + sel) for prop in self.data['properties']: 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(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 self.height_hint = ypos + line_spacing @@ -204,7 +216,7 @@ class Declaration(QWidget): return QWidget.mouseMoveEvent(self, ev) def mousePressEvent(self, ev): - if hasattr(self, 'hyperlink_rect'): + if hasattr(self, 'hyperlink_rect') and ev.button() == Qt.LeftButton: pos = ev.pos() if self.hyperlink_rect.contains(pos): self.emit_hyperlink_activated() @@ -236,6 +248,9 @@ class Declaration(QWidget): cell.override_color = QColor(Qt.red) if hovering else None self.update() + def contextMenuEvent(self, ev): + self.context_menu_requested.emit(self, ev) + class Box(QWidget): hyperlink_activated = pyqtSignal(object) @@ -250,7 +265,7 @@ class Box(QWidget): def show_data(self, data): for w in self.widgets: self.layout().removeWidget(w) - for x in ('toggled', 'hyperlink_activated'): + for x in ('toggled', 'hyperlink_activated', 'context_menu_requested'): if hasattr(w, x): try: 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)]} d = Declaration(None, declaration, is_first=True, parent=self) 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): for i, w in enumerate(self.widgets): @@ -294,6 +311,49 @@ class Box(QWidget): w.do_layout() 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): __slots__ = 'name', 'value', 'important', 'color', 'specificity', 'is_overriden'