mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-30 23:00:21 -04:00
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:
parent
30da5e1061
commit
637ddf7d4e
@ -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.
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user