mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
ec9e8e1c58
commit
02970401ea
@ -12,6 +12,7 @@ from future_builtins import map
|
|||||||
from calibre.utils.config import JSONConfig
|
from calibre.utils.config import JSONConfig
|
||||||
from calibre.spell.dictionary import Dictionaries, parse_lang_code
|
from calibre.spell.dictionary import Dictionaries, parse_lang_code
|
||||||
|
|
||||||
|
CONTAINER_DND_MIMETYPE = 'application/x-calibre-container-name-list'
|
||||||
tprefs = JSONConfig('tweak_book_gui')
|
tprefs = JSONConfig('tweak_book_gui')
|
||||||
d = tprefs.defaults
|
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):
|
def capitalize(x):
|
||||||
return ucase_map[x[0]] + x[1:]
|
return ucase_map[x[0]] + x[1:]
|
||||||
|
|
||||||
|
|
||||||
_current_container = None
|
_current_container = None
|
||||||
|
|
||||||
|
|
||||||
@ -102,6 +104,7 @@ class NonReplaceDict(dict):
|
|||||||
raise ValueError('The key %s is already present' % k)
|
raise ValueError('The key %s is already present' % k)
|
||||||
dict.__setitem__(self, k, v)
|
dict.__setitem__(self, k, v)
|
||||||
|
|
||||||
|
|
||||||
actions = NonReplaceDict()
|
actions = NonReplaceDict()
|
||||||
editors = NonReplaceDict()
|
editors = NonReplaceDict()
|
||||||
toolbar_actions = NonReplaceDict()
|
toolbar_actions = NonReplaceDict()
|
||||||
|
@ -1,33 +1,46 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
from __future__ import (unicode_literals, division, absolute_import,
|
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
print_function)
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
import importlib
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
import os
|
||||||
|
import re
|
||||||
import re, importlib
|
import textwrap
|
||||||
import textwrap, unicodedata
|
import unicodedata
|
||||||
from future_builtins import map
|
from future_builtins import map
|
||||||
|
|
||||||
import regex
|
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QPlainTextEdit, QFontDatabase, QToolTip, QPalette, QFont, QKeySequence,
|
QColor, QColorDialog, QFont, QFontDatabase, QKeySequence, QPainter, QPalette,
|
||||||
QTextEdit, QTextFormat, QWidget, QSize, QPainter, Qt, QRect, QColor,
|
QPlainTextEdit, QRect, QSize, Qt, QTextEdit, QTextFormat, QTimer, QToolTip,
|
||||||
QColorDialog, QTimer, pyqtSignal)
|
QWidget, pyqtSignal
|
||||||
|
)
|
||||||
|
|
||||||
|
import regex
|
||||||
from calibre import prepare_string_for_xml
|
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.completion.popup import CompletionPopup
|
||||||
from calibre.gui2.tweak_book.editor import (
|
from calibre.gui2.tweak_book.editor import (
|
||||||
SYNTAX_PROPERTY, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale, LINK_PROPERTY)
|
LINK_PROPERTY, SPELL_LOCALE_PROPERTY, SPELL_PROPERTY, SYNTAX_PROPERTY,
|
||||||
from calibre.gui2.tweak_book.editor.themes import get_theme, theme_color, theme_format
|
store_locale
|
||||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
)
|
||||||
from calibre.gui2.tweak_book.editor.smarts import NullSmarts
|
from calibre.gui2.tweak_book.editor.smarts import NullSmarts
|
||||||
from calibre.gui2.tweak_book.editor.snippets import SnippetManager
|
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.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
|
from calibre.utils.titlecase import titlecase
|
||||||
|
|
||||||
|
|
||||||
@ -110,6 +123,83 @@ class TextEdit(PlainTextEdit):
|
|||||||
self.blockCountChanged[int].connect(self.update_line_number_area_width)
|
self.blockCountChanged[int].connect(self.update_line_number_area_width)
|
||||||
self.updateRequest.connect(self.update_line_number_area)
|
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
|
@dynamic_property
|
||||||
def is_modified(self):
|
def is_modified(self):
|
||||||
''' True if the document has been modified since it was loaded or since
|
''' True if the document has been modified since it was loaded or since
|
||||||
|
@ -1,35 +1,40 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
from __future__ import (unicode_literals, division, absolute_import,
|
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
print_function)
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
import os
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
import posixpath
|
||||||
|
|
||||||
import os, posixpath
|
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from collections import OrderedDict, defaultdict, Counter
|
from collections import Counter, OrderedDict, defaultdict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
import sip
|
import sip
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon, QFont,
|
QCheckBox, QDialog, QDialogButtonBox, QFont, QFormLayout, QGridLayout, QIcon,
|
||||||
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal, QMenu, QTimer,
|
QInputDialog, QLabel, QLineEdit, QListWidget, QListWidgetItem, QMenu, QPainter,
|
||||||
QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout, QScrollArea, QInputDialog,
|
QPixmap, QRadioButton, QScrollArea, QSize, QSpinBox, QStyle, QStyledItemDelegate,
|
||||||
QRadioButton, QFormLayout, QSpinBox, QListWidget, QListWidgetItem, QCheckBox)
|
Qt, QTimer, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal
|
||||||
|
)
|
||||||
|
|
||||||
from calibre import human_readable, sanitize_file_name_unicode, plugins
|
from calibre import human_readable, plugins, sanitize_file_name_unicode
|
||||||
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS
|
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
|
||||||
from calibre.ebooks.oeb.polish.container import guess_type, OEB_FONTS
|
from calibre.ebooks.oeb.polish.container import OEB_FONTS, guess_type
|
||||||
from calibre.ebooks.oeb.polish.replace import get_recommended_folders
|
|
||||||
from calibre.ebooks.oeb.polish.cover import (
|
from calibre.ebooks.oeb.polish.cover import (
|
||||||
get_cover_page_name, get_raster_cover_name, is_raster_image)
|
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
|
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.editor import syntax_from_mime
|
||||||
from calibre.gui2.tweak_book.templates import template_for
|
from calibre.gui2.tweak_book.templates import template_for
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
|
|
||||||
|
|
||||||
TOP_ICON_SIZE = 24
|
TOP_ICON_SIZE = 24
|
||||||
NAME_ROLE = Qt.UserRole
|
NAME_ROLE = Qt.UserRole
|
||||||
CATEGORY_ROLE = NAME_ROLE + 1
|
CATEGORY_ROLE = NAME_ROLE + 1
|
||||||
@ -210,6 +215,17 @@ class FileList(QTreeWidget):
|
|||||||
}.iteritems()}
|
}.iteritems()}
|
||||||
self.itemActivated.connect(self.item_double_clicked)
|
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
|
@property
|
||||||
def current_name(self):
|
def current_name(self):
|
||||||
ci = self.currentItem()
|
ci = self.currentItem()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user