Edit book: Allow drag and drop of image files/stylesheets into the editor to insert the appropriate <img> and <link> tags and add the files to the book automatically.

This commit is contained in:
Kovid Goyal 2017-02-21 12:44:00 +05:30
parent ec9e8e1c58
commit 02970401ea
3 changed files with 144 additions and 35 deletions

View File

@ -12,6 +12,7 @@ from future_builtins import map
from calibre.utils.config import JSONConfig
from calibre.spell.dictionary import Dictionaries, parse_lang_code
CONTAINER_DND_MIMETYPE = 'application/x-calibre-container-name-list'
tprefs = JSONConfig('tweak_book_gui')
d = tprefs.defaults
@ -83,6 +84,7 @@ ucase_map = {l:string.ascii_uppercase[i] for i, l in enumerate(string.ascii_lowe
def capitalize(x):
return ucase_map[x[0]] + x[1:]
_current_container = None
@ -102,6 +104,7 @@ class NonReplaceDict(dict):
raise ValueError('The key %s is already present' % k)
dict.__setitem__(self, k, v)
actions = NonReplaceDict()
editors = NonReplaceDict()
toolbar_actions = NonReplaceDict()

View File

@ -1,33 +1,46 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
print_function)
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import re, importlib
import textwrap, unicodedata
import importlib
import os
import re
import textwrap
import unicodedata
from future_builtins import map
import regex
from PyQt5.Qt import (
QPlainTextEdit, QFontDatabase, QToolTip, QPalette, QFont, QKeySequence,
QTextEdit, QTextFormat, QWidget, QSize, QPainter, Qt, QRect, QColor,
QColorDialog, QTimer, pyqtSignal)
QColor, QColorDialog, QFont, QFontDatabase, QKeySequence, QPainter, QPalette,
QPlainTextEdit, QRect, QSize, Qt, QTextEdit, QTextFormat, QTimer, QToolTip,
QWidget, pyqtSignal
)
import regex
from calibre import prepare_string_for_xml
from calibre.gui2.tweak_book import tprefs, TOP, current_container
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
from calibre.ebooks.oeb.polish.replace import get_recommended_folders
from calibre.ebooks.oeb.polish.utils import guess_type
from calibre.gui2.tweak_book import (
CONTAINER_DND_MIMETYPE, TOP, current_container, tprefs
)
from calibre.gui2.tweak_book.completion.popup import CompletionPopup
from calibre.gui2.tweak_book.editor import (
SYNTAX_PROPERTY, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale, LINK_PROPERTY)
from calibre.gui2.tweak_book.editor.themes import get_theme, theme_color, theme_format
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
LINK_PROPERTY, SPELL_LOCALE_PROPERTY, SPELL_PROPERTY, SYNTAX_PROPERTY,
store_locale
)
from calibre.gui2.tweak_book.editor.smarts import NullSmarts
from calibre.gui2.tweak_book.editor.snippets import SnippetManager
from calibre.gui2.tweak_book.widgets import PlainTextEdit, PARAGRAPH_SEPARATOR
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
from calibre.gui2.tweak_book.editor.themes import (
get_theme, theme_color, theme_format
)
from calibre.gui2.tweak_book.widgets import PARAGRAPH_SEPARATOR, PlainTextEdit
from calibre.spell.break_iterator import index_of
from calibre.utils.icu import safe_chr, string_length, capitalize, upper, lower, swapcase
from calibre.utils.icu import (
capitalize, lower, safe_chr, string_length, swapcase, upper
)
from calibre.utils.img import image_to_data
from calibre.utils.titlecase import titlecase
@ -110,6 +123,83 @@ class TextEdit(PlainTextEdit):
self.blockCountChanged[int].connect(self.update_line_number_area_width)
self.updateRequest.connect(self.update_line_number_area)
def get_droppable_files(self, md):
def is_mt_ok(mt):
return self.syntax == 'html' and (
mt in OEB_DOCS or mt in OEB_STYLES or mt.startswith('image/')
)
if md.hasFormat(CONTAINER_DND_MIMETYPE):
for line in bytes(md.data(CONTAINER_DND_MIMETYPE)).decode('utf-8').splitlines():
mt = current_container().mime_map.get(line, 'application/octet-stream')
if is_mt_ok(mt):
yield line, mt, True
return
for qurl in md.urls():
if qurl.isLocalFile() and os.access(qurl.toLocalFile(), os.R_OK):
path = qurl.toLocalFile()
mt = guess_type(path)
if is_mt_ok(mt):
yield path, mt, False
def canInsertFromMimeData(self, md):
if md.hasText() or (md.hasHtml() and self.syntax == 'html') or md.hasImage():
return True
elif tuple(self.get_droppable_files(md)):
return True
return False
def insertFromMimeData(self, md):
files = tuple(self.get_droppable_files(md))
base = self.highlighter.doc_name or None
def get_name(name):
return get_recommended_folders(current_container(), (name,))[name] + '/' + name
def get_href(name):
return current_container().name_to_href(name, base)
def insert_text(text):
c = self.textCursor()
c.insertText(text)
self.setTextCursor(c)
def add_file(name, data, mt=None):
from calibre.gui2.tweak_book.boss import get_boss
name = current_container().add_file(name, data, media_type=mt, modify_name_if_needed=True)
get_boss().refresh_file_list()
return name
if files:
for path, mt, is_name in files:
if is_name:
name = path
else:
name = get_name(os.path.basename(path))
with lopen(path, 'rb') as f:
name = add_file(name, f.read(), mt)
href = get_href(name)
if mt.startswith('image/'):
self.insert_image(href)
elif mt in OEB_STYLES:
insert_text('<link href="{}" rel="stylesheet" type="text/css"/>'.format(href))
elif mt in OEB_DOCS:
self.insert_hyperlink(href, name)
return
if md.hasImage():
img = md.imageData()
if img.isValid():
data = image_to_data(img, fmt='PNG')
name = add_file(get_name('dropped_image.png', data))
self.insert_image(get_href(name))
return
if md.hasHtml():
insert_text(md.html())
return
if md.hasText():
return insert_text(md.text())
@dynamic_property
def is_modified(self):
''' True if the document has been modified since it was loaded or since

View File

@ -1,35 +1,40 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
print_function)
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import os, posixpath
import os
import posixpath
from binascii import hexlify
from collections import OrderedDict, defaultdict, Counter
from collections import Counter, OrderedDict, defaultdict
from functools import partial
import sip
from PyQt5.Qt import (
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon, QFont,
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal, QMenu, QTimer,
QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout, QScrollArea, QInputDialog,
QRadioButton, QFormLayout, QSpinBox, QListWidget, QListWidgetItem, QCheckBox)
QCheckBox, QDialog, QDialogButtonBox, QFont, QFormLayout, QGridLayout, QIcon,
QInputDialog, QLabel, QLineEdit, QListWidget, QListWidgetItem, QMenu, QPainter,
QPixmap, QRadioButton, QScrollArea, QSize, QSpinBox, QStyle, QStyledItemDelegate,
Qt, QTimer, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal
)
from calibre import human_readable, sanitize_file_name_unicode, plugins
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS
from calibre.ebooks.oeb.polish.container import guess_type, OEB_FONTS
from calibre.ebooks.oeb.polish.replace import get_recommended_folders
from calibre import human_readable, plugins, sanitize_file_name_unicode
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
from calibre.ebooks.oeb.polish.container import OEB_FONTS, guess_type
from calibre.ebooks.oeb.polish.cover import (
get_cover_page_name, get_raster_cover_name, is_raster_image)
from calibre.gui2 import error_dialog, choose_files, question_dialog, elided_text, choose_save_file
from calibre.gui2.tweak_book import current_container, tprefs, editors
get_cover_page_name, get_raster_cover_name, is_raster_image
)
from calibre.ebooks.oeb.polish.replace import get_recommended_folders
from calibre.gui2 import (
choose_files, choose_save_file, elided_text, error_dialog, question_dialog
)
from calibre.gui2.tweak_book import (
CONTAINER_DND_MIMETYPE, current_container, editors, tprefs
)
from calibre.gui2.tweak_book.editor import syntax_from_mime
from calibre.gui2.tweak_book.templates import template_for
from calibre.utils.icu import sort_key
TOP_ICON_SIZE = 24
NAME_ROLE = Qt.UserRole
CATEGORY_ROLE = NAME_ROLE + 1
@ -210,6 +215,17 @@ class FileList(QTreeWidget):
}.iteritems()}
self.itemActivated.connect(self.item_double_clicked)
def mimeTypes(self):
ans = QTreeWidget.mimeTypes(self)
ans.append(CONTAINER_DND_MIMETYPE)
return ans
def mimeData(self, indices):
ans = QTreeWidget.mimeData(self, indices)
names = (idx.data(0, NAME_ROLE) for idx in indices if idx.data(0, MIME_ROLE))
ans.setData(CONTAINER_DND_MIMETYPE, '\n'.join(filter(None, names)).encode('utf-8'))
return ans
@property
def current_name(self):
ci = self.currentItem()