Edit book: Allow copying files from one editor instance to another by right clicking on them in the File browser

This commit is contained in:
Kovid Goyal 2019-02-10 13:23:57 +05:30
parent 30da5e1061
commit 637ddf7d4e
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 115 additions and 42 deletions

View File

@ -175,6 +175,12 @@ book, by right clicking on the file again and choosing :guilabel:`Replace with
file...` which will allow you to replace the file in the book with
the previously exported file.
You can also copy files between multiple editor instances. Select
the files you want to copy in the :guilabel:`File browser`, then right click
and choose, :guilabel:`Copy selected files to another editor instance`. Then,
in the other editor instance, right click in the :guilabel:`File browser`
and choose :guilabel:`Paste file from other editor instance`.
Adding new images/fonts/etc. or creating new blank files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -726,7 +732,7 @@ Checking external links
You can use this tool to check all links in your book that point to external
websites. The tool will try to visit every externally linked website, and
if the visit fails, it will report all broken links in a convenient format for
if the visit fails, it will report all broken links in a convenient format for
you to fix.

View File

@ -1,55 +1,76 @@
#!/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 tempfile, shutil, sys, os, errno
import errno
import os
import shutil
import sys
import tempfile
from functools import partial, wraps
from urlparse import urlparse
from PyQt5.Qt import (
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt, QCheckBox,
QDialogButtonBox, QIcon, QInputDialog, QUrl, pyqtSignal, QVBoxLayout)
QApplication, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon,
QInputDialog, QLabel, QMimeData, QObject, QSize, Qt, QUrl, QVBoxLayout,
pyqtSignal
)
from calibre import prints, isbytestring
from calibre import isbytestring, prints
from calibre.constants import cache_dir, iswindows
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks.oeb.base import urlnormalize
from calibre.ebooks.oeb.polish.main import SUPPORTED, tweak_polish
from calibre.ebooks.oeb.polish.container import get_container as _gc, clone_container, guess_type, OEB_DOCS, OEB_STYLES
from calibre.ebooks.oeb.polish.cover import mark_as_cover, mark_as_titlepage, set_cover
from calibre.ebooks.oeb.polish.container import (
OEB_DOCS, OEB_STYLES, clone_container, get_container as _gc, guess_type
)
from calibre.ebooks.oeb.polish.cover import (
mark_as_cover, mark_as_titlepage, set_cover
)
from calibre.ebooks.oeb.polish.css import filter_css
from calibre.ebooks.oeb.polish.main import SUPPORTED, tweak_polish
from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all
from calibre.ebooks.oeb.polish.replace import rename_files, replace_file, get_recommended_folders, rationalize_folders
from calibre.ebooks.oeb.polish.split import split, merge, AbortError, multisplit
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, create_inline_toc
from calibre.ebooks.oeb.polish.utils import link_stylesheets, setup_css_parser_serialization as scs
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file, open_url, choose_dir, add_to_recent_docs
from calibre.ebooks.oeb.polish.replace import (
get_recommended_folders, rationalize_folders, rename_files, replace_file
)
from calibre.ebooks.oeb.polish.split import AbortError, merge, multisplit, split
from calibre.ebooks.oeb.polish.toc import create_inline_toc, remove_names_from_toc
from calibre.ebooks.oeb.polish.utils import (
link_stylesheets, setup_css_parser_serialization as scs
)
from calibre.gui2 import (
add_to_recent_docs, choose_dir, choose_files, choose_save_file, error_dialog,
info_dialog, open_url, question_dialog
)
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.tweak_book import (
set_current_container, current_container, tprefs, actions, editors,
set_book_locale, dictionaries, editor_name)
actions, current_container, dictionaries, editor_name, editors, set_book_locale,
set_current_container, tprefs
)
from calibre.gui2.tweak_book.completion.worker import completion_worker
from calibre.gui2.tweak_book.undo import GlobalUndoHistory
from calibre.gui2.tweak_book.file_list import NewFileDialog
from calibre.gui2.tweak_book.save import SaveManager, save_container, find_first_existing_ancestor
from calibre.gui2.tweak_book.preview import parse_worker
from calibre.gui2.tweak_book.toc import TOCEditor
from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data, NewBook
from calibre.gui2.tweak_book.editor.insert_resource import NewBook, get_resource_data
from calibre.gui2.tweak_book.file_list import FILE_COPY_MIME, NewFileDialog
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.spell import find_next as find_next_word, find_next_error
from calibre.gui2.tweak_book.preview import parse_worker
from calibre.gui2.tweak_book.save import (
SaveManager, find_first_existing_ancestor, save_container
)
from calibre.gui2.tweak_book.search import run_search, validate_search_request
from calibre.gui2.tweak_book.spell import (
find_next as find_next_word, find_next_error
)
from calibre.gui2.tweak_book.toc import TOCEditor
from calibre.gui2.tweak_book.undo import GlobalUndoHistory
from calibre.gui2.tweak_book.widgets import (
RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink,
InsertSemantics, BusyCursor, InsertTag, FilterCSS, AddCover)
AddCover, BusyCursor, FilterCSS, ImportForeign, InsertLink, InsertSemantics,
InsertTag, MultiSplit, QuickOpen, RationalizeFolders
)
from calibre.ptempfile import TemporaryDirectory
from calibre.utils.config import JSONConfig
from calibre.utils.icu import numeric_sort_key
from calibre.utils.imghdr import identify
from calibre.utils.tdir_in_cache import tdir_in_cache
from polyglot.builtins import iteritems
_diff_dialogs = []
last_used_transform_rules = []
@ -115,6 +136,8 @@ class Boss(QObject):
fl.export_requested.connect(self.export_requested)
fl.replace_requested.connect(self.replace_requested)
fl.link_stylesheets_requested.connect(self.link_stylesheets_requested)
fl.initiate_file_copy.connect(self.copy_files_to_clipboard)
fl.initiate_file_paste.connect(self.paste_files_from_clipboard)
self.gui.central.current_editor_changed.connect(self.apply_current_editor_state)
self.gui.central.close_requested.connect(self.editor_close_requested)
self.gui.central.search_panel.search_triggered.connect(self.search)
@ -1326,6 +1349,37 @@ class Boss(QObject):
raise
self.export_file(name, dest)
@in_thread_job
def copy_files_to_clipboard(self, names):
names = tuple(names)
for name in names:
if name in editors and not editors[name].is_synced_to_container:
self.commit_editor_to_container(name)
container = current_container()
md = QMimeData()
url_map = {
name:container.get_file_path_for_processing(name, allow_modification=False)
for name in names
}
md.setUrls(list(map(QUrl.fromLocalFile, url_map.values())))
import json
md.setData(FILE_COPY_MIME, json.dumps({
name: (url_map[name], container.mime_map.get(name)) for name in names
}))
QApplication.instance().clipboard().setMimeData(md)
@in_thread_job
def paste_files_from_clipboard(self):
md = QApplication.instance().clipboard().mimeData()
if md.hasUrls() and md.hasFormat(FILE_COPY_MIME):
import json
name_map = json.loads(bytes(md.data(FILE_COPY_MIME)))
container = current_container()
for name, (path, mt) in iteritems(name_map):
with lopen(path, 'rb') as f:
container.add_file(name, f.read(), media_type=mt)
self.apply_container_update_to_gui()
def export_file(self, name, path):
if name in editors and not editors[name].is_synced_to_container:
self.commit_editor_to_container(name)

View File

@ -12,15 +12,12 @@ from collections import Counter, OrderedDict, defaultdict
from functools import partial
from PyQt5.Qt import (
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
QApplication, 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
)
try:
from PyQt5 import sip
except ImportError:
import sip
from calibre import human_readable, plugins, sanitize_file_name_unicode
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
@ -42,7 +39,15 @@ from calibre.gui2.tweak_book import (
from calibre.gui2.tweak_book.editor import syntax_from_mime
from calibre.gui2.tweak_book.templates import template_for
from calibre.utils.icu import numeric_sort_key
from polyglot.builtins import iteritems
try:
from PyQt5 import sip
except ImportError:
import sip
FILE_COPY_MIME = 'application/calibre-edit-book-files'
TOP_ICON_SIZE = 24
NAME_ROLE = Qt.UserRole
CATEGORY_ROLE = NAME_ROLE + 1
@ -194,6 +199,8 @@ class FileList(QTreeWidget):
export_requested = pyqtSignal(object, object)
replace_requested = pyqtSignal(object, object, object, object)
link_stylesheets_requested = pyqtSignal(object, object, object)
initiate_file_copy = pyqtSignal(object)
initiate_file_paste = pyqtSignal()
def __init__(self, parent=None):
QTreeWidget.__init__(self, parent)
@ -228,13 +235,13 @@ class FileList(QTreeWidget):
self.rendered_emblem_cache = {}
self.top_level_pixmap_cache = {
name : QIcon(I(icon)).pixmap(TOP_ICON_SIZE, TOP_ICON_SIZE)
for name, icon in {
for name, icon in iteritems({
'text':'keyboard-prefs.png',
'styles':'lookfeel.png',
'fonts':'font.png',
'misc':'mimetypes/dir.png',
'images':'view-image.png',
}.iteritems()}
})}
self.itemActivated.connect(self.item_double_clicked)
def mimeTypes(self):
@ -527,6 +534,9 @@ class FileList(QTreeWidget):
'&Copy the selected file to another editor instance',
'&Copy the {} selected files to another editor instance', num).format(num), self.copy_selected_files)
m.addSeparator()
md = QApplication.instance().clipboard().mimeData()
if md.hasUrls() and md.hasFormat(FILE_COPY_MIME):
m.addAction(_('Paste files from other editor instance'), self.paste_from_other_instance)
selected_map = defaultdict(list)
for item in sel:
@ -648,7 +658,10 @@ class FileList(QTreeWidget):
return ans
def copy_selected_files(self):
pass
self.initiate_file_copy.emit(self.selected_names)
def paste_from_other_instance(self):
self.initiate_file_paste.emit()
def request_delete(self):
names = self.selected_names