Edit Book: Add a button to easily insert HTML tags. Useful if you want to quickly surround selected text with an arbitrary tag. You can right click the button to get a list of recently used tags.

This commit is contained in:
Kovid Goyal 2014-04-02 10:34:22 +05:30
parent fc4805f699
commit b078ab5b51
9 changed files with 200 additions and 3 deletions

1
imgsrc/code.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="32px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M14,6c0-0.984-0.813-2-2-2c-0.531,0-0.994,0.193-1.38,0.58l-9.958,9.958C0.334,14.866,0,15.271,0,16s0.279,1.08,0.646,1.447 l9.974,9.973C11.006,27.807,11.469,28,12,28c1.188,0,2-1.016,2-2c0-0.516-0.186-0.986-0.58-1.38L4.8,16l8.62-8.62 C13.814,6.986,14,6.516,14,6z M31.338,14.538L21.38,4.58C20.994,4.193,20.531,4,20,4c-1.188,0-2,1.016-2,2 c0,0.516,0.186,0.986,0.58,1.38L27.2,16l-8.62,8.62C18.186,25.014,18,25.484,18,26c0,0.984,0.813,2,2,2 c0.531,0,0.994-0.193,1.38-0.58l9.974-9.973C31.721,17.08,32,16.729,32,16S31.666,14.866,31.338,14.538z"/></svg>

After

Width:  |  Height:  |  Size: 896 B

BIN
resources/images/code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,111 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
html5_tags = ( # {{{
frozenset('''\
html
head
title
base
link
meta
style
script
noscript
body
section
nav
article
aside
h1
h2
h3
h4
h5
h6
header
footer
address
p
hr
br
pre
dialog
blockquote
ol
ul
li
dl
dt
dd
a
q
cite
em
strong
small
mark
dfn
abbr
time
progress
meter
code
var
samp
kbd
sub
sup
span
i
b
bdo
ruby
rt
rp
ins
del
figure
img
iframe
embed
object
param
video
audio
source
canvas
map
area
table
caption
colgroup
col
tbody
thead
tfoot
tr
td
th
form
fieldset
label
input
button
select
datalist
optgroup
option
textarea
output
details
command
bb
menu
legend
div'''.splitlines())) # }}}

View File

@ -42,6 +42,7 @@ d['folders_for_types'] = {'style':'styles', 'image':'images', 'font':'fonts', 'a
d['pretty_print_on_open'] = False d['pretty_print_on_open'] = False
d['disable_completion_popup_for_search'] = False d['disable_completion_popup_for_search'] = False
d['saved_searches'] = [] d['saved_searches'] = []
d['insert_tag_mru'] = ['p', 'div', 'li', 'h1', 'h2', 'h3', 'h4', 'em', 'strong', 'td', 'tr']
del d del d

View File

@ -39,7 +39,7 @@ from calibre.gui2.tweak_book.preferences import Preferences
from calibre.gui2.tweak_book.search import validate_search_request, run_search from calibre.gui2.tweak_book.search import validate_search_request, run_search
from calibre.gui2.tweak_book.widgets import ( from calibre.gui2.tweak_book.widgets import (
RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink, RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink,
InsertSemantics, BusyCursor) InsertSemantics, BusyCursor, InsertTag)
_diff_dialogs = [] _diff_dialogs = []
@ -642,6 +642,10 @@ class Boss(QObject):
d = InsertLink(current_container(), edname, initial_text=ed.get_smart_selection(), parent=self.gui) d = InsertLink(current_container(), edname, initial_text=ed.get_smart_selection(), parent=self.gui)
if d.exec_() == d.Accepted: if d.exec_() == d.Accepted:
ed.insert_hyperlink(d.href, d.text) ed.insert_hyperlink(d.href, d.text)
elif action[0] == 'insert_tag':
d = InsertTag(parent=self.gui)
if d.exec_() == d.Accepted:
ed.insert_tag(d.tag)
else: else:
ed.action_triggered(action) ed.action_triggered(action)

View File

@ -232,3 +232,12 @@ class HTMLSmarts(NullSmarts):
if text: if text:
c.insertText(text) c.insertText(text)
editor.setTextCursor(c) editor.setTextCursor(c)
def insert_tag(self, editor, name):
text = self.get_smart_selection(editor, update=True)
c = editor.textCursor()
pos = min(c.position(), c.anchor())
c.insertText('<{0}>{1}</{0}>'.format(name, text))
c.setPosition(pos + 1 + len(name))
editor.setTextCursor(c)

View File

@ -612,6 +612,10 @@ class TextEdit(PlainTextEdit):
if hasattr(self.smarts, 'insert_hyperlink'): if hasattr(self.smarts, 'insert_hyperlink'):
self.smarts.insert_hyperlink(self, target, text) self.smarts.insert_hyperlink(self, target, text)
def insert_tag(self, tag):
if hasattr(self.smarts, 'insert_tag'):
self.smarts.insert_tag(self, tag)
def keyPressEvent(self, ev): def keyPressEvent(self, ev):
if ev.key() == Qt.Key_X and ev.modifiers() == Qt.AltModifier: if ev.key() == Qt.Key_X and ev.modifiers() == Qt.AltModifier:
if self.replace_possible_unicode_sequence(): if self.replace_possible_unicode_sequence():

View File

@ -7,13 +7,14 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import unicodedata import unicodedata
from functools import partial
from PyQt4.Qt import ( from PyQt4.Qt import (
QMainWindow, Qt, QApplication, pyqtSignal, QMenu, qDrawShadeRect, QPainter, QMainWindow, Qt, QApplication, pyqtSignal, QMenu, qDrawShadeRect, QPainter,
QImage, QColor, QIcon, QPixmap, QToolButton) 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, tprefs
from calibre.gui2.tweak_book.editor.text import TextEdit from calibre.gui2.tweak_book.editor.text import TextEdit
def create_icon(text, palette=None, sz=32, divider=2): def create_icon(text, palette=None, sz=32, divider=2):
@ -65,6 +66,10 @@ def register_text_editor_actions(reg, palette):
ac = reg(create_icon(name), text, ('rename_block_tag', name), 'rename-block-tag-' + name, 'Ctrl+%d' % (i + 1), desc) ac = reg(create_icon(name), text, ('rename_block_tag', name), 'rename-block-tag-' + name, 'Ctrl+%d' % (i + 1), desc)
ac.setToolTip(desc) ac.setToolTip(desc)
ac = reg('code', _('Insert &tag'), ('insert_tag',), 'insert-tag', ('Ctrl+<'), _('Insert tag'))
ac.setToolTip(_('<h3>Insert tag</h3>Insert a tag, if some text is selected the tag will be inserted around the selected text'))
class Editor(QMainWindow): class Editor(QMainWindow):
has_line_numbers = True has_line_numbers = True
@ -147,6 +152,23 @@ class Editor(QMainWindow):
def insert_hyperlink(self, href, text): def insert_hyperlink(self, href, text):
self.editor.insert_hyperlink(href, text) self.editor.insert_hyperlink(href, text)
def _build_insert_tag_button_menu(self):
m = self.insert_tag_button.menu()
m.clear()
for name in tprefs['insert_tag_mru']:
m.addAction(name, partial(self.insert_tag, name))
def insert_tag(self, name):
self.editor.insert_tag(name)
mru = tprefs['insert_tag_mru']
try:
mru.remove(name)
except ValueError:
pass
mru.insert(0, name)
tprefs['insert_tag_mru'] = mru
self._build_insert_tag_button_menu()
def undo(self): def undo(self):
self.editor.undo() self.editor.undo()
@ -198,6 +220,7 @@ class Editor(QMainWindow):
for x in ('cut', 'copy', 'paste'): for x in ('cut', 'copy', 'paste'):
b.addAction(actions['editor-%s' % x]) b.addAction(actions['editor-%s' % x])
self.tools_bar = b = self.addToolBar(_('Editor tools')) self.tools_bar = b = self.addToolBar(_('Editor tools'))
b.setObjectName('tools_bar')
if self.syntax == 'html': if self.syntax == 'html':
b.addAction(actions['fix-html-current']) b.addAction(actions['fix-html-current'])
if self.syntax in {'xml', 'html', 'css'}: if self.syntax in {'xml', 'html', 'css'}:
@ -206,8 +229,18 @@ class Editor(QMainWindow):
b.addAction(actions['insert-image']) b.addAction(actions['insert-image'])
if self.syntax == 'html': if self.syntax == 'html':
b.addAction(actions['insert-hyperlink']) b.addAction(actions['insert-hyperlink'])
if self.syntax in {'xml', 'html'}:
b.addAction(actions['insert-tag'])
w = self.insert_tag_button = b.widgetForAction(actions['insert-tag'])
w.setPopupMode(QToolButton.MenuButtonPopup)
w.m = m = QMenu()
w.setMenu(m)
w.setContextMenuPolicy(Qt.CustomContextMenu)
w.customContextMenuRequested.connect(self.insert_tag_button.showMenu)
self._build_insert_tag_button_menu()
if self.syntax == 'html': if self.syntax == 'html':
self.format_bar = b = self.addToolBar(_('Format text')) self.format_bar = b = self.addToolBar(_('Format text'))
b.setObjectName('html_format_bar')
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')) ac = b.addAction(QIcon(I('format-text-heading.png')), _('Change paragraph to heading'))

View File

@ -22,6 +22,7 @@ from calibre.gui2 import error_dialog, choose_files, choose_save_file, NONE, inf
from calibre.gui2.tweak_book import tprefs from calibre.gui2.tweak_book import tprefs
from calibre.utils.icu import primary_sort_key, sort_key from calibre.utils.icu import primary_sort_key, sort_key
from calibre.utils.matcher import get_char, Matcher from calibre.utils.matcher import get_char, Matcher
from calibre.gui2.complete2 import EditWithComplete
ROOT = QModelIndex() ROOT = QModelIndex()
@ -69,6 +70,39 @@ class Dialog(QDialog):
def setup_ui(self): def setup_ui(self):
raise NotImplementedError('You must implement this method in Dialog subclasses') raise NotImplementedError('You must implement this method in Dialog subclasses')
class InsertTag(Dialog): # {{{
def __init__(self, parent=None):
Dialog.__init__(self, _('Choose tag name'), 'insert-tag', parent=parent)
def setup_ui(self):
from calibre.ebooks.constants import html5_tags
self.l = l = QVBoxLayout(self)
self.setLayout(l)
self.la = la = QLabel(_('Specify the name of the &tag to insert:'))
l.addWidget(la)
self.tag_input = ti = EditWithComplete(self)
ti.set_separator(None)
ti.all_items = html5_tags | frozenset(tprefs['insert_tag_mru'])
la.setBuddy(ti)
l.addWidget(ti)
l.addWidget(self.bb)
ti.setFocus(Qt.OtherFocusReason)
@property
def tag(self):
return unicode(self.tag_input.text()).strip()
@classmethod
def test(cls):
d = cls()
if d.exec_() == d.Accepted:
print (d.tag)
# }}}
class RationalizeFolders(Dialog): # {{{ class RationalizeFolders(Dialog): # {{{
TYPE_MAP = ( TYPE_MAP = (
@ -852,4 +886,4 @@ class InsertSemantics(Dialog):
if __name__ == '__main__': if __name__ == '__main__':
app = QApplication([]) app = QApplication([])
InsertSemantics.test() InsertTag.test()