Edit Book: Shortcut change of current paragraph to heading

Edit Book: Add a tool to easily change the current paragraph (the
paragraph containing the cursor) to a heading. Simply click the button
with 'H1' on it and choose the heading level you want the current
paragraph changed to.
This commit is contained in:
Kovid Goyal 2014-02-04 19:23:29 +05:30
parent b34eb8f460
commit 1e3a46ba82
4 changed files with 95 additions and 5 deletions

View File

@ -6,13 +6,16 @@ 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 sys import sys, re
from operator import itemgetter from operator import itemgetter
from . import NullSmarts from . import NullSmarts
from PyQt4.Qt import QTextEdit from PyQt4.Qt import QTextEdit
from calibre.gui2 import error_dialog
get_offset = itemgetter(0) get_offset = itemgetter(0)
PARAGRAPH_SEPARATOR = '\u2029'
class Tag(object): class Tag(object):
@ -104,6 +107,27 @@ def find_closing_tag(tag, max_tags=sys.maxint):
max_tags -= 1 max_tags -= 1
return None return None
def select_tag(cursor, tag):
cursor.setPosition(tag.start_block.position() + tag.start_offset)
cursor.setPosition(tag.end_block.position() + tag.end_offset + 1, cursor.KeepAnchor)
return unicode(cursor.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
def rename_tag(cursor, opening_tag, closing_tag, new_name, insert=False):
cursor.beginEditBlock()
text = select_tag(cursor, closing_tag)
if insert:
text = '</%s>%s' % (new_name, text)
else:
text = re.sub(r'^<\s*/\s*[a-zA-Z0-9]+', '</%s' % new_name, text)
cursor.insertText(text)
text = select_tag(cursor, opening_tag)
if insert:
text += '<%s>' % new_name
else:
text = re.sub(r'^<\s*[a-zA-Z0-9]+', '<%s' % new_name, text)
cursor.insertText(text)
cursor.endEditBlock()
class HTMLSmarts(NullSmarts): class HTMLSmarts(NullSmarts):
def get_extra_selections(self, editor): def get_extra_selections(self, editor):
@ -126,3 +150,34 @@ class HTMLSmarts(NullSmarts):
add_tag(tag) add_tag(tag)
return ans return ans
def rename_block_tag(self, editor, new_name):
c = editor.textCursor()
block, offset = c.block(), c.positionInBlock()
tag = None
while True:
tag = find_closest_containing_tag(block, offset)
if tag is None:
break
block, offset = tag.start_block, tag.start_offset
if tag.name in {
'address', 'article', 'aside', 'blockquote', 'center',
'dir', 'fieldset', 'isindex', 'menu', 'noframes', 'hgroup',
'noscript', 'pre', 'section', 'h1', 'h2', 'h3', 'h4', 'h5',
'h6', 'header', 'p', 'div', 'dd', 'dl', 'ul', 'ol', 'li', 'body',
'td', 'th'}:
break
tag = None
if tag is not None:
closing_tag = find_closing_tag(tag)
if closing_tag is None:
return error_dialog(editor, _('Invalid HTML'), _(
'There is an unclosed %s tag. You should run the Fix HTML tool'
' before trying to rename tags.') % tag.name, show=True)
rename_tag(c, tag, closing_tag, new_name, insert=tag.name in {'body', 'td', 'th', 'li'})
else:
return error_dialog(editor, _('No found'), _(
'No suitable block level tag was found to rename'), show=True)

View File

@ -612,3 +612,7 @@ class TextEdit(PlainTextEdit):
c.movePosition(c.End, c.KeepAnchor) c.movePosition(c.End, c.KeepAnchor)
self.setTextCursor(c) self.setTextCursor(c)
def rename_block_tag(self, new_name):
if hasattr(self.smarts, 'rename_block_tag'):
self.smarts.rename_block_tag(self, new_name)

View File

@ -8,13 +8,30 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import unicodedata import unicodedata
from PyQt4.Qt import QMainWindow, Qt, QApplication, pyqtSignal, QMenu from PyQt4.Qt import (
QMainWindow, Qt, QApplication, pyqtSignal, QMenu, qDrawShadeRect, QPainter,
QImage, QColor, QIcon, QPixmap, QToolButton)
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
from calibre.gui2.tweak_book import actions, current_container from calibre.gui2.tweak_book import actions, current_container
from calibre.gui2.tweak_book.editor.text import TextEdit from calibre.gui2.tweak_book.editor.text import TextEdit
def register_text_editor_actions(reg): def create_icon(text, palette=None, sz=32, divider=2):
if palette is None:
palette = QApplication.palette()
img = QImage(sz, sz, QImage.Format_ARGB32)
img.fill(Qt.transparent)
p = QPainter(img)
p.setRenderHints(p.TextAntialiasing | p.Antialiasing)
qDrawShadeRect(p, img.rect(), palette, fill=QColor('#ffffff'), lineWidth=1, midLineWidth=1)
f = p.font()
f.setFamily('Liberation Sans'), f.setPixelSize(sz // divider), f.setBold(True)
p.setFont(f), p.setPen(Qt.black)
p.drawText(img.rect().adjusted(2, 2, -2, -2), Qt.AlignCenter, text)
p.end()
return QIcon(QPixmap.fromImage(img))
def register_text_editor_actions(reg, palette):
ac = reg('format-text-bold', _('&Bold'), ('format_text', 'bold'), 'format-text-bold', 'Ctrl+B', _('Make the selected text bold')) ac = reg('format-text-bold', _('&Bold'), ('format_text', 'bold'), 'format-text-bold', 'Ctrl+B', _('Make the selected text bold'))
ac.setToolTip(_('<h3>Bold</h3>Make the selected text bold')) ac.setToolTip(_('<h3>Bold</h3>Make the selected text bold'))
ac = reg('format-text-italic', _('&Italic'), ('format_text', 'italic'), 'format-text-italic', 'Ctrl+I', _('Make the selected text italic')) ac = reg('format-text-italic', _('&Italic'), ('format_text', 'italic'), 'format-text-italic', 'Ctrl+I', _('Make the selected text italic'))
@ -39,6 +56,12 @@ def register_text_editor_actions(reg):
ac = reg('view-image', _('&Insert image'), ('insert_resource', 'image'), 'insert-image', (), _('Insert an image into the text')) ac = reg('view-image', _('&Insert image'), ('insert_resource', 'image'), 'insert-image', (), _('Insert an image into the text'))
ac.setToolTip(_('<h3>Insert image</h3>Insert an image into the text')) ac.setToolTip(_('<h3>Insert image</h3>Insert an image into the text'))
for i, name in enumerate(('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p')):
text = ('&' + name) if name == 'p' else (name[0] + '&' + name[1])
desc = _('Convert the paragraph to &lt;%s&gt;') % name
ac = reg(create_icon(name), text, ('rename_block_tag', name), 'rename-block-tag-' + name, 'Ctrl+%d' % (i + 1), desc)
ac.setToolTip(desc)
class Editor(QMainWindow): class Editor(QMainWindow):
has_line_numbers = True has_line_numbers = True
@ -176,6 +199,12 @@ class Editor(QMainWindow):
self.format_bar = b = self.addToolBar(_('Format text')) self.format_bar = b = self.addToolBar(_('Format text'))
for x in ('bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'color', 'background-color'): for x in ('bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'color', 'background-color'):
b.addAction(actions['format-text-%s' % x]) b.addAction(actions['format-text-%s' % x])
ac = b.addAction(QIcon(I('format-text-heading.png')), _('Change paragraph to heading'))
m = QMenu()
ac.setMenu(m)
b.widgetForAction(ac).setPopupMode(QToolButton.InstantPopup)
for name in tuple('h%d' % d for d in range(1, 7)) + ('p',):
m.addAction(actions['rename-block-tag-%s' % name])
def break_cycles(self): def break_cycles(self):
self.modification_state_changed.disconnect() self.modification_state_changed.disconnect()

View File

@ -270,7 +270,9 @@ class Main(MainWindow):
group = _('Global Actions') group = _('Global Actions')
def reg(icon, text, target, sid, keys, description): def reg(icon, text, target, sid, keys, description):
ac = actions[sid] = QAction(QIcon(I(icon)), text, self) if icon else QAction(text, self) if not isinstance(icon, QIcon):
icon = QIcon(I(icon))
ac = actions[sid] = QAction(icon, text, self) if icon else QAction(text, self)
ac.setObjectName('action-' + sid) ac.setObjectName('action-' + sid)
if target is not None: if target is not None:
ac.triggered.connect(target) ac.triggered.connect(target)
@ -315,7 +317,7 @@ class Main(MainWindow):
def ereg(icon, text, target, sid, keys, description): def ereg(icon, text, target, sid, keys, description):
return reg(icon, text, partial(self.boss.editor_action, target), sid, keys, description) return reg(icon, text, partial(self.boss.editor_action, target), sid, keys, description)
register_text_editor_actions(ereg) register_text_editor_actions(ereg, self.palette())
# Tool actions # Tool actions
group = _('Tools') group = _('Tools')