mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Merge branch 'master' of https://github.com/kovidgoyal/calibre
This commit is contained in:
commit
73b613fac1
1
imgsrc/code.svg
Normal file
1
imgsrc/code.svg
Normal 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
BIN
resources/images/code.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -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']
|
||||
|
||||
|
@ -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'
|
||||
|
||||
|
111
src/calibre/ebooks/constants.py
Normal file
111
src/calibre/ebooks/constants.py
Normal 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())) # }}}
|
@ -7,7 +7,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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():
|
||||
|
@ -7,13 +7,14 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
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(_('<h3>Insert tag</h3>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'))
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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_):
|
||||
|
@ -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)),
|
||||
|
Loading…
x
Reference in New Issue
Block a user