mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-07 09:01:38 -04:00
Edit Book: Add buttons to change the text alignment of the current paragraph. If you do not see them, go to Preferences->Toolbars and add them to the Text formatting actions toolbar.
This commit is contained in:
parent
c952bb5888
commit
251aa384a7
@ -55,8 +55,10 @@ d['global_tools_toolbar'] = ['check-book', 'spell-check-book', 'edit-toc', 'inse
|
||||
d['editor_css_toolbar'] = ['pretty-current', 'insert-image']
|
||||
d['editor_xml_toolbar'] = ['pretty-current', 'insert-tag']
|
||||
d['editor_html_toolbar'] = ['fix-html-current', 'pretty-current', 'insert-image', 'insert-hyperlink', 'insert-tag', 'change-paragraph']
|
||||
d['editor_format_toolbar'] = [('format-text-' + x) for x in (
|
||||
'bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'color', 'background-color')]
|
||||
d['editor_format_toolbar'] = [('format-text-' + x) if x else x for x in (
|
||||
'bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript',
|
||||
None, 'color', 'background-color', None, 'justify-left', 'justify-center',
|
||||
'justify-right', 'justify-fill')]
|
||||
d['spell_check_case_sensitive_search'] = False
|
||||
d['add_cover_preserve_aspect_ratio'] = False
|
||||
del d
|
||||
|
@ -8,14 +8,15 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import sys, re
|
||||
from operator import itemgetter
|
||||
from . import NullSmarts
|
||||
|
||||
from cssutils import parseStyle
|
||||
from PyQt4.Qt import QTextEdit
|
||||
|
||||
from calibre import prepare_string_for_xml
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.gui2.tweak_book.editor.syntax.html import ATTR_NAME, ATTR_END, ATTR_START, ATTR_VALUE
|
||||
from calibre.utils.icu import utf16_length
|
||||
from calibre.gui2.tweak_book.editor.smart import NullSmarts
|
||||
|
||||
get_offset = itemgetter(0)
|
||||
PARAGRAPH_SEPARATOR = '\u2029'
|
||||
@ -119,6 +120,7 @@ def find_containing_attribute(block, offset):
|
||||
return None
|
||||
|
||||
def find_attribute_in_tag(block, offset, attr_name):
|
||||
' Return the start of the attribute value as block, offset or None, None if attribute not found '
|
||||
end_block, boundary = next_tag_boundary(block, offset)
|
||||
if boundary.is_start:
|
||||
return None, None
|
||||
@ -140,6 +142,15 @@ def find_attribute_in_tag(block, offset, attr_name):
|
||||
found_attr = True
|
||||
current_offset += 1
|
||||
|
||||
def find_end_of_attribute(block, offset):
|
||||
' Find the end of an attribute that occurs somewhere after the position specified by (block, offset) '
|
||||
block, boundary = next_attr_boundary(block, offset)
|
||||
if block is None or boundary is None:
|
||||
return None, None
|
||||
if boundary.type is not ATTR_VALUE or boundary.data is not ATTR_END:
|
||||
return None, None
|
||||
return block, boundary.offset
|
||||
|
||||
def find_closing_tag(tag, max_tags=sys.maxint):
|
||||
''' Find the closing tag corresponding to the specified tag. To find it we
|
||||
search for the first closing tag after the specified tag that does not
|
||||
@ -209,11 +220,13 @@ def ensure_not_within_tag_definition(cursor, forward=True):
|
||||
|
||||
return False
|
||||
|
||||
def find_closest_containing_block_tag(block, offset, block_tag_names=frozenset((
|
||||
'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'))):
|
||||
BLOCK_TAG_NAMES = frozenset((
|
||||
'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'))
|
||||
|
||||
def find_closest_containing_block_tag(block, offset, block_tag_names=BLOCK_TAG_NAMES):
|
||||
while True:
|
||||
tag = find_closest_containing_tag(block, offset)
|
||||
if tag is None:
|
||||
@ -222,6 +235,31 @@ def find_closest_containing_block_tag(block, offset, block_tag_names=frozenset((
|
||||
return tag
|
||||
block, offset = tag.start_block, tag.start_offset
|
||||
|
||||
def set_style_property(tag, property_name, value, editor):
|
||||
'''
|
||||
Set a style property, i.e. a CSS property inside the style attribute of the tag.
|
||||
Any existing style attribute is updated or a new attribute is inserted.
|
||||
'''
|
||||
block, offset = find_attribute_in_tag(tag.start_block, tag.start_offset + 1, 'style')
|
||||
c = editor.textCursor()
|
||||
def css(d):
|
||||
return d.cssText.replace('\n', ' ')
|
||||
if block is None or offset is None:
|
||||
d = parseStyle('')
|
||||
d.setProperty(property_name, value)
|
||||
c.setPosition(tag.end_block.position() + tag.end_offset)
|
||||
c.insertText(' style="%s"' % css(d))
|
||||
else:
|
||||
c.setPosition(block.position() + offset - 1)
|
||||
end_block, end_offset = find_end_of_attribute(block, offset + 1)
|
||||
if end_block is None:
|
||||
return error_dialog(editor, _('Invalid markup'), _(
|
||||
'The current block tag has an existing unclosed style attribute. Run the Fix HTML'
|
||||
' tool first.'), show=True)
|
||||
c.setPosition(end_block.position() + end_offset, c.KeepAnchor)
|
||||
d = parseStyle(editor.selected_text_from_cursor(c)[1:-1])
|
||||
d.setProperty(property_name, value)
|
||||
c.insertText('"%s"' % css(d))
|
||||
|
||||
class HTMLSmarts(NullSmarts):
|
||||
|
||||
@ -427,3 +465,33 @@ class HTMLSmarts(NullSmarts):
|
||||
c.setPosition(ctag.start_block.position() + ctag.start_offset, c.KeepAnchor)
|
||||
return c
|
||||
|
||||
def set_text_alignment(self, editor, value):
|
||||
''' Set the text-align property on the current block tag(s) '''
|
||||
editor.highlighter.join()
|
||||
block_tag_names = BLOCK_TAG_NAMES - {'body'} # ignore body since setting text-align globally on body is almost never what is wanted
|
||||
tags = []
|
||||
c = editor.textCursor()
|
||||
if c.hasSelection():
|
||||
start, end = min(c.anchor(), c.position()), max(c.anchor(), c.position())
|
||||
c.setPosition(start)
|
||||
block = c.block()
|
||||
while block.isValid() and block.position() < end:
|
||||
ud = block.userData()
|
||||
if ud is not None:
|
||||
for tb in ud.tags:
|
||||
if tb.is_start and not tb.closing and tb.name.lower() in block_tag_names:
|
||||
nblock, boundary = next_tag_boundary(block, tb.offset)
|
||||
if boundary is not None and not boundary.is_start and not boundary.self_closing:
|
||||
tags.append(Tag(block, tb, nblock, boundary))
|
||||
block = block.next()
|
||||
if not tags:
|
||||
c = editor.textCursor()
|
||||
block, offset = c.block(), c.positionInBlock()
|
||||
tag = find_closest_containing_block_tag(block, offset, block_tag_names)
|
||||
if tag is None:
|
||||
return error_dialog(editor, _('Not in a block tag'), _(
|
||||
'Cannot change text alignment as the cursor is not inside a block level tag, such as a <p> or <div> tag.'), show=True)
|
||||
tags = [tag]
|
||||
for tag in reversed(tags):
|
||||
set_style_property(tag, 'text-align', value, editor)
|
||||
|
||||
|
@ -642,6 +642,8 @@ class TextEdit(PlainTextEdit):
|
||||
def format_text(self, formatting):
|
||||
if self.syntax != 'html':
|
||||
return
|
||||
if formatting.startswith('justify_'):
|
||||
return self.smarts.set_text_alignment(self, formatting.partition('_')[-1])
|
||||
color = 'currentColor'
|
||||
if formatting in {'color', 'background-color'}:
|
||||
color = QColorDialog.getColor(QColor(Qt.black if formatting == 'color' else Qt.white), self, _('Choose color'), QColorDialog.ShowAlphaChannel)
|
||||
|
@ -64,6 +64,14 @@ def register_text_editor_actions(_reg, palette):
|
||||
ac = reg('format-fill-color', _('&Background Color'), ('format_text', 'background-color'),
|
||||
'format-text-background-color', (), _('Change background color of text'))
|
||||
ac.setToolTip(_('<h3>Background Color</h3>Change the background color of the selected text'))
|
||||
ac = reg('format-justify-left', _('Align &left'), ('format_text', 'justify_left'), 'format-text-justify-left', (), _('Align left'))
|
||||
ac.setToolTip(_('<h3>Align left</h3>Align the paragraph to the left'))
|
||||
ac = reg('format-justify-center', _('&Center'), ('format_text', 'justify_center'), 'format-text-justify-center', (), _('Center'))
|
||||
ac.setToolTip(_('<h3>Center</h3>Center the paragraph'))
|
||||
ac = reg('format-justify-right', _('Align &right'), ('format_text', 'justify_right'), 'format-text-justify-right', (), _('Align right'))
|
||||
ac.setToolTip(_('<h3>Align right</h3>Align the paragraph to the right'))
|
||||
ac = reg('format-justify-fill', _('&Justify'), ('format_text', 'justify_justify'), 'format-text-justify-fill', (), _('Justify'))
|
||||
ac.setToolTip(_('<h3>Justify</h3>Align the paragraph to both the left and right margins'))
|
||||
|
||||
ac = reg('view-image', _('&Insert image'), ('insert_resource', 'image'), 'insert-image', (), _('Insert an image into the text'), syntaxes=('html', 'css'))
|
||||
ac.setToolTip(_('<h3>Insert image</h3>Insert an image into the text'))
|
||||
@ -86,6 +94,7 @@ def register_text_editor_actions(_reg, palette):
|
||||
editor_toolbar_actions['html']['change-paragraph'] = actions['change-paragraph'] = QAction(
|
||||
QIcon(I('format-text-heading.png')), _('Change paragraph to heading'), ac.parent())
|
||||
|
||||
|
||||
class Editor(QMainWindow):
|
||||
|
||||
has_line_numbers = True
|
||||
@ -258,6 +267,9 @@ class Editor(QMainWindow):
|
||||
def populate_toolbars(self):
|
||||
self.tools_bar.clear()
|
||||
def add_action(name, bar):
|
||||
if name is None:
|
||||
bar.addSeparator()
|
||||
return
|
||||
try:
|
||||
ac = actions[name]
|
||||
except KeyError:
|
||||
|
Loading…
x
Reference in New Issue
Block a user