diff --git a/imgsrc/code.svg b/imgsrc/code.svg
new file mode 100644
index 0000000000..c186a40dcb
--- /dev/null
+++ b/imgsrc/code.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/code.png b/resources/images/code.png
new file mode 100644
index 0000000000..3980b0e00c
Binary files /dev/null and b/resources/images/code.png differ
diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py
index 67a8bb5242..73b0bac313 100644
--- a/src/calibre/devices/eb600/driver.py
+++ b/src/calibre/devices/eb600/driver.py
@@ -53,9 +53,10 @@ class TOLINO(EB600):
name = 'Tolino Shine Device Interface'
gui_name = 'Tolino Shine'
- description = _('Communicate with the Tolino Shine reader.')
+ description = _('Communicate with the Tolino Shine and Vision readers')
FORMATS = ['epub', 'pdf', 'txt']
- BCD = [0x226]
+ PRODUCT_ID = EB600.PRODUCT_ID + [0x6033]
+ BCD = [0x226, 0x9999]
VENDOR_NAME = ['DEUTSCHE']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['_TELEKOMTOLINO']
diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py
index cfece66235..6678879b8c 100644
--- a/src/calibre/devices/smart_device_app/driver.py
+++ b/src/calibre/devices/smart_device_app/driver.py
@@ -226,7 +226,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
PURGE_CACHE_ENTRIES_DAYS = 30
- CURRENT_CC_VERSION = 73
+ CURRENT_CC_VERSION = 77
ZEROCONF_CLIENT_STRING = b'calibre wireless device client'
diff --git a/src/calibre/ebooks/constants.py b/src/calibre/ebooks/constants.py
new file mode 100644
index 0000000000..b01f44fa6d
--- /dev/null
+++ b/src/calibre/ebooks/constants.py
@@ -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 '
+
+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())) # }}}
diff --git a/src/calibre/ebooks/docx/numbering.py b/src/calibre/ebooks/docx/numbering.py
index df500ee65c..405c19434f 100644
--- a/src/calibre/ebooks/docx/numbering.py
+++ b/src/calibre/ebooks/docx/numbering.py
@@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal '
import re
-from collections import Counter
+from collections import Counter, defaultdict
from lxml.html.builder import OL, UL, SPAN
@@ -135,8 +135,9 @@ class Level(object):
class NumberingDefinition(object):
- def __init__(self, parent=None):
+ def __init__(self, parent=None, an_id=None):
self.levels = {}
+ self.abstract_numbering_definition_id = an_id
if parent is not None:
for lvl in XPath('./w:lvl')(parent):
try:
@@ -146,7 +147,7 @@ class NumberingDefinition(object):
self.levels[ilvl] = Level(lvl)
def copy(self):
- ans = NumberingDefinition()
+ ans = NumberingDefinition(an_id=self.abstract_numbering_definition_id)
for l, lvl in self.levels.iteritems():
ans.levels[l] = lvl.copy()
return ans
@@ -156,7 +157,8 @@ class Numbering(object):
def __init__(self):
self.definitions = {}
self.instances = {}
- self.counters = {}
+ self.counters = defaultdict(Counter)
+ self.starts = {}
self.pic_map = {}
def __call__(self, root, styles, rid_map):
@@ -174,13 +176,24 @@ class Numbering(object):
if nsl:
lazy_load[an_id] = get(nsl[0], 'w:val')
else:
- nd = NumberingDefinition(an)
+ nd = NumberingDefinition(an, an_id=an_id)
self.definitions[an_id] = nd
def create_instance(n, definition):
nd = definition.copy()
+ start_overrides = {}
for lo in XPath('./w:lvlOverride')(n):
- ilvl = get(lo, 'w:ilvl')
+ try:
+ ilvl = int(get(lo, 'w:ilvl'))
+ except (ValueError, TypeError):
+ ilvl = None
+ for so in XPath('./w:startOverride[@w:val]')(lo):
+ try:
+ start_override = int(get(so, 'w:val'))
+ except (TypeError, ValueError):
+ pass
+ else:
+ start_overrides[ilvl] = start_override
for lvl in XPath('./w:lvl')(lo)[:1]:
nilvl = get(lvl, 'w:ilvl')
ilvl = nilvl if ilvl is None else ilvl
@@ -188,6 +201,11 @@ class Numbering(object):
if alvl is None:
alvl = Level()
alvl.read_from_xml(lvl, override=True)
+ for ilvl, so in start_overrides.iteritems():
+ try:
+ nd.levels[ilvl].start = start_override
+ except KeyError:
+ pass
return nd
next_pass = {}
@@ -213,7 +231,7 @@ class Numbering(object):
self.instances[num_id] = create_instance(n, d)
for num_id, d in self.instances.iteritems():
- self.counters[num_id] = Counter({lvl:d.levels[lvl].start for lvl in d.levels})
+ self.starts[num_id] = {lvl:d.levels[lvl].start for lvl in d.levels}
def get_pstyle(self, num_id, style_id):
d = self.instances.get(num_id, None)
@@ -236,12 +254,17 @@ class Numbering(object):
counter[ilvl] = lvl.start
def apply_markup(self, items, body, styles, object_map, images):
+ seen_instances = set()
for p, num_id, ilvl in items:
d = self.instances.get(num_id, None)
if d is not None:
lvl = d.levels.get(ilvl, None)
if lvl is not None:
- counter = self.counters[num_id]
+ an_id = d.abstract_numbering_definition_id
+ counter = self.counters[an_id]
+ if ilvl not in counter or num_id not in seen_instances:
+ counter[ilvl] = self.starts[num_id][ilvl]
+ seen_instances.add(num_id)
p.tag = 'li'
p.set('value', '%s' % counter[ilvl])
p.set('list-lvl', str(ilvl))
diff --git a/src/calibre/ebooks/docx/styles.py b/src/calibre/ebooks/docx/styles.py
index 17c891423f..80be3ceb0b 100644
--- a/src/calibre/ebooks/docx/styles.py
+++ b/src/calibre/ebooks/docx/styles.py
@@ -262,6 +262,9 @@ class Styles(object):
if num_id is not None:
p.set('calibre_num_id', '%s:%s' % (lvl, num_id))
is_numbering = True
+ ps = self.numbering.get_para_style(num_id, lvl)
+ if ps is not None:
+ parent_styles.append(ps)
for attr in ans.all_properties:
if not (is_numbering and attr == 'text_indent'): # skip text-indent for lists
diff --git a/src/calibre/gui2/tweak_book/__init__.py b/src/calibre/gui2/tweak_book/__init__.py
index bfdc16b610..621c45eff2 100644
--- a/src/calibre/gui2/tweak_book/__init__.py
+++ b/src/calibre/gui2/tweak_book/__init__.py
@@ -42,6 +42,7 @@ d['folders_for_types'] = {'style':'styles', 'image':'images', 'font':'fonts', 'a
d['pretty_print_on_open'] = False
d['disable_completion_popup_for_search'] = False
d['saved_searches'] = []
+d['insert_tag_mru'] = ['p', 'div', 'li', 'h1', 'h2', 'h3', 'h4', 'em', 'strong', 'td', 'tr']
del d
diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py
index 9622757207..67c985b6fd 100644
--- a/src/calibre/gui2/tweak_book/boss.py
+++ b/src/calibre/gui2/tweak_book/boss.py
@@ -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.widgets import (
RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink,
- InsertSemantics, BusyCursor)
+ InsertSemantics, BusyCursor, InsertTag)
_diff_dialogs = []
@@ -642,6 +642,10 @@ class Boss(QObject):
d = InsertLink(current_container(), edname, initial_text=ed.get_smart_selection(), parent=self.gui)
if d.exec_() == d.Accepted:
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:
ed.action_triggered(action)
diff --git a/src/calibre/gui2/tweak_book/editor/__init__.py b/src/calibre/gui2/tweak_book/editor/__init__.py
index 94215621d4..270f45f2dc 100644
--- a/src/calibre/gui2/tweak_book/editor/__init__.py
+++ b/src/calibre/gui2/tweak_book/editor/__init__.py
@@ -16,7 +16,7 @@ def syntax_from_mime(name, mime):
return 'html'
if mime in OEB_STYLES:
return 'css'
- if mime in {guess_type('a.opf'), guess_type('a.ncx'), guess_type('a.xml'), 'application/oebps-page-map+xml'}:
+ if mime in {guess_type('a.svg'), guess_type('a.opf'), guess_type('a.ncx'), guess_type('a.xml'), 'application/oebps-page-map+xml'}:
return 'xml'
if mime.startswith('text/'):
return 'text'
diff --git a/src/calibre/gui2/tweak_book/editor/smart/html.py b/src/calibre/gui2/tweak_book/editor/smart/html.py
index a0b2a1ba77..32aa06d587 100644
--- a/src/calibre/gui2/tweak_book/editor/smart/html.py
+++ b/src/calibre/gui2/tweak_book/editor/smart/html.py
@@ -232,3 +232,12 @@ class HTMLSmarts(NullSmarts):
if text:
c.insertText(text)
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)
+
diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py
index fb949f63eb..ed90f821d7 100644
--- a/src/calibre/gui2/tweak_book/editor/text.py
+++ b/src/calibre/gui2/tweak_book/editor/text.py
@@ -612,6 +612,10 @@ class TextEdit(PlainTextEdit):
if hasattr(self.smarts, 'insert_hyperlink'):
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):
if ev.key() == Qt.Key_X and ev.modifiers() == Qt.AltModifier:
if self.replace_possible_unicode_sequence():
diff --git a/src/calibre/gui2/tweak_book/editor/widget.py b/src/calibre/gui2/tweak_book/editor/widget.py
index 81a54d17a9..175eb4d2b0 100644
--- a/src/calibre/gui2/tweak_book/editor/widget.py
+++ b/src/calibre/gui2/tweak_book/editor/widget.py
@@ -7,13 +7,14 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal '
import unicodedata
+from functools import partial
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.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
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.setToolTip(desc)
+ ac = reg('code', _('Insert &tag'), ('insert_tag',), 'insert-tag', ('Ctrl+<'), _('Insert tag'))
+ ac.setToolTip(_('Insert tag
Insert a tag, if some text is selected the tag will be inserted around the selected text'))
+
+
class Editor(QMainWindow):
has_line_numbers = True
@@ -147,6 +152,23 @@ class Editor(QMainWindow):
def insert_hyperlink(self, 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):
self.editor.undo()
@@ -198,6 +220,7 @@ class Editor(QMainWindow):
for x in ('cut', 'copy', 'paste'):
b.addAction(actions['editor-%s' % x])
self.tools_bar = b = self.addToolBar(_('Editor tools'))
+ b.setObjectName('tools_bar')
if self.syntax == 'html':
b.addAction(actions['fix-html-current'])
if self.syntax in {'xml', 'html', 'css'}:
@@ -206,8 +229,18 @@ class Editor(QMainWindow):
b.addAction(actions['insert-image'])
if self.syntax == 'html':
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':
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'):
b.addAction(actions['format-text-%s' % x])
ac = b.addAction(QIcon(I('format-text-heading.png')), _('Change paragraph to heading'))
diff --git a/src/calibre/gui2/tweak_book/widgets.py b/src/calibre/gui2/tweak_book/widgets.py
index e3ddc0d13b..fbb021830f 100644
--- a/src/calibre/gui2/tweak_book/widgets.py
+++ b/src/calibre/gui2/tweak_book/widgets.py
@@ -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.utils.icu import primary_sort_key, sort_key
from calibre.utils.matcher import get_char, Matcher
+from calibre.gui2.complete2 import EditWithComplete
ROOT = QModelIndex()
@@ -69,6 +70,39 @@ class Dialog(QDialog):
def setup_ui(self):
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): # {{{
TYPE_MAP = (
@@ -852,4 +886,4 @@ class InsertSemantics(Dialog):
if __name__ == '__main__':
app = QApplication([])
- InsertSemantics.test()
+ InsertTag.test()
diff --git a/src/calibre/library/check_library.py b/src/calibre/library/check_library.py
index 79c52cfe4e..024fe9a5bd 100644
--- a/src/calibre/library/check_library.py
+++ b/src/calibre/library/check_library.py
@@ -30,7 +30,7 @@ CHECKS = [('invalid_titles', _('Invalid titles'), True, False),
('missing_formats', _('Missing book formats'), False, True),
('extra_formats', _('Extra book formats'), True, False),
('extra_files', _('Unknown files in books'), True, False),
- ('missing_covers', _('Missing covers files'), False, True),
+ ('missing_covers', _('Missing cover files'), False, True),
('extra_covers', _('Cover files not in database'), True, True),
('failed_folders', _('Folders raising exception'), False, False)
]
@@ -175,7 +175,8 @@ class CheckLibrary(object):
def process_book(self, lib, book_info):
(db_path, title_dir, book_id) = book_info
filenames = frozenset([f for f in os.listdir(os.path.join(lib, db_path))
- if os.path.splitext(f)[1] not in self.ignore_ext])
+ if os.path.splitext(f)[1] not in self.ignore_ext or
+ f == 'cover.jpg'])
book_id = int(book_id)
formats = frozenset(filter(self.is_ebook_file, filenames))
book_formats = frozenset([x[0]+'.'+x[1].lower() for x in
diff --git a/src/calibre/web/feeds/recipes/collection.py b/src/calibre/web/feeds/recipes/collection.py
index 3d4ac952fb..5cfbb7899c 100644
--- a/src/calibre/web/feeds/recipes/collection.py
+++ b/src/calibre/web/feeds/recipes/collection.py
@@ -140,23 +140,29 @@ def update_custom_recipe(id_, title, script):
def add_custom_recipe(title, script):
+ add_custom_recipes({title:script})
+
+def add_custom_recipes(script_map):
from calibre.web.feeds.recipes import custom_recipes, \
custom_recipe_filename
id_ = 1000
keys = tuple(map(int, custom_recipes.iterkeys()))
if keys:
id_ = max(keys)+1
- id_ = str(id_)
- bdir = os.path.dirname(custom_recipes.file_path)
+ with custom_recipes:
+ for title, script in script_map.iteritems():
+ fid = str(id_)
+ bdir = os.path.dirname(custom_recipes.file_path)
- fname = custom_recipe_filename(id_, title)
- if isinstance(script, unicode):
- script = script.encode('utf-8')
+ fname = custom_recipe_filename(fid, title)
+ if isinstance(script, unicode):
+ script = script.encode('utf-8')
- custom_recipes[id_] = (title, fname)
+ custom_recipes[fid] = (title, fname)
- with open(os.path.join(bdir, fname), 'wb') as f:
- f.write(script)
+ with open(os.path.join(bdir, fname), 'wb') as f:
+ f.write(script)
+ id_ += 1
def remove_custom_recipe(id_):
diff --git a/src/qtcurve/style/qtcurve.cpp b/src/qtcurve/style/qtcurve.cpp
index 2c012ef196..3b36a95cf1 100644
--- a/src/qtcurve/style/qtcurve.cpp
+++ b/src/qtcurve/style/qtcurve.cpp
@@ -8148,7 +8148,8 @@ void Style::drawComplexControl(ComplexControl control, const QStyleOptionComplex
drawControl(CE_ToolButtonLabel, &label, painter, widget);
if (!(toolbutton->subControls&SC_ToolButtonMenu) &&
- (toolbutton->features&QStyleOptionToolButton::HasMenu))
+ (toolbutton->features&QStyleOptionToolButton::HasMenu &&
+ toolbutton->features & QStyleOptionToolButton::PopupDelay))
{
QRect arrow(r.right()-(LARGE_ARR_WIDTH+(etched ? 3 : 2)),
r.bottom()-(LARGE_ARR_HEIGHT+(etched ? 4 : 3)),