23
23
from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all
24
24
from calibre.ebooks.oeb.polish.replace import rename_files, replace_file, get_recommended_folders, rationalize_folders
25
25
from calibre.ebooks.oeb.polish.split import split, merge, AbortError, multisplit
26
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, find_existing_toc
26
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, find_existing_toc, create_inline_toc
27
27
from calibre.ebooks.oeb.polish.utils import link_stylesheets, setup_cssutils_serialization as scs
28
28
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file
29
29
from calibre.gui2.dialogs.confirm_delete import confirm
30
from calibre.gui2.dialogs.message_box import MessageBox
30
31
from calibre.gui2.tweak_book import set_current_container, current_container, tprefs, actions, editors
31
32
from calibre.gui2.tweak_book.undo import GlobalUndoHistory
32
33
from calibre.gui2.tweak_book.file_list import NewFileDialog
33
from calibre.gui2.tweak_book.save import SaveManager, save_container
34
from calibre.gui2.tweak_book.save import SaveManager, save_container, find_first_existing_ancestor
34
35
from calibre.gui2.tweak_book.preview import parse_worker, font_cache
35
36
from calibre.gui2.tweak_book.toc import TOCEditor
36
37
from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
157
158
d = NewBook(self.gui)
158
159
if d.exec_() == d.Accepted:
160
161
path = choose_save_file(self.gui, 'edit-book-new-book', _('Choose file location'),
161
162
filters=[(fmt.upper(), (fmt,))], all_files=False)
162
163
if path is not None:
164
if not path.lower().endswith('.' + fmt):
165
path = path + '.' + fmt
163
166
from calibre.ebooks.oeb.polish.create import create_book
164
167
create_book(d.mi, path, fmt=fmt)
165
168
self.open_book(path=path)
231
234
self.gui.file_list.request_edit(ef)
232
235
self.gui.toc_view.update_if_visible()
236
self.add_savepoint(_('Start of editing session'))
234
238
def update_editors_from_container(self, container=None, names=None):
235
239
c = container or current_container()
256
260
def delete_requested(self, spine_items, other_items):
257
self.commit_all_editors_to_container()
258
self.add_savepoint(_('Delete files'))
261
self.add_savepoint(_('Before: Delete files'))
259
262
c = current_container()
260
263
c.remove_from_spine(spine_items)
261
264
for name in other_items:
280
283
self.commit_editor_to_container(c.opf_name)
282
285
def reorder_spine(self, items):
283
self.commit_dirty_opf()
284
self.add_savepoint(_('Re-order text'))
286
self.add_savepoint(_('Before: Re-order text'))
285
287
c = current_container()
286
288
c.set_spine(items)
287
289
self.set_modified()
299
301
d = NewFileDialog(self.gui)
300
302
if d.exec_() != d.Accepted:
302
self.add_savepoint(_('Add file %s') % self.gui.elided_text(d.file_name))
304
self.add_savepoint(_('Before: Add file %s') % self.gui.elided_text(d.file_name))
303
305
c = current_container()
304
306
data = d.file_data
305
307
if d.using_template:
333
335
folder_map = get_recommended_folders(current_container(), files)
334
336
files = {x:('/'.join((folder, os.path.basename(x))) if folder else os.path.basename(x))
335
337
for x, folder in folder_map.iteritems()}
336
self.commit_dirty_opf()
337
self.add_savepoint(_('Add files'))
338
self.add_savepoint(_('Before Add files'))
338
339
c = current_container()
339
340
for path, name in files.iteritems():
354
355
self.set_modified()
356
357
def edit_toc(self):
357
self.commit_all_editors_to_container()
358
self.add_savepoint(_('Edit Table of Contents'))
358
self.add_savepoint(_('Before: Edit Table of Contents'))
359
359
d = TOCEditor(title=self.current_metadata.title, parent=self.gui)
360
360
if d.exec_() != d.Accepted:
361
361
self.rewind_savepoint()
365
365
self.update_editors_from_container()
366
366
self.gui.toc_view.update_if_visible()
368
def insert_inline_toc(self):
369
self.commit_all_editors_to_container()
370
self.add_savepoint(_('Before: Insert inline Table of Contents'))
371
name = create_inline_toc(current_container())
373
self.rewind_savepoint()
374
return error_dialog(self.gui, _('No Table of Contents'), _(
375
'Cannot create an inline Table of Contents as this book has no existing'
376
' Table of Contents. You must first create a Table of Contents using the'
377
' Edit Table of Contents tool.'), show=True)
378
self.apply_container_update_to_gui()
379
self.edit_file(name, 'html')
368
381
def polish(self, action, name):
369
self.commit_all_editors_to_container()
370
382
with BusyCursor():
371
self.add_savepoint(name)
383
self.add_savepoint(_('Before: %s') % name)
373
385
report, changed = tweak_polish(current_container(), {action:True})
385
399
d.e.setHtml(report)
386
400
d.bb = QDialogButtonBox(QDialogButtonBox.Close)
388
b = d.b = d.bb.addButton(_('See what changed'), d.bb.AcceptRole)
389
b.setIcon(QIcon(I('diff.png')))
402
b = d.b = d.bb.addButton(_('See what &changed'), d.bb.AcceptRole)
403
b.setIcon(QIcon(I('diff.png'))), b.setAutoDefault(False)
390
404
b.clicked.connect(partial(self.show_current_diff, allow_revert=True))
405
d.bb.button(d.bb.Close).setDefault(True)
391
406
d.l.addWidget(d.bb)
392
407
d.bb.rejected.connect(d.reject)
393
408
d.bb.accepted.connect(d.accept)
411
426
return info_dialog(self.gui, _('Nothing to do'), _(
412
427
'The files in this book are already arranged into folders'), show=True)
413
self.add_savepoint(_('Arrange into folders'))
428
self.add_savepoint(_('Before: Arrange into folders'))
414
429
self.gui.blocking_job(
415
430
'rationalize_folders', _('Renaming and updating links...'), partial(self.rename_done, name_map),
416
431
rename_files, current_container(), name_map)
434
449
'<pre>%s</pre>'%newname, '<pre>%s</pre>' % urlnormalize(newname)),
435
450
'confirm-urlunsafe-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs):
437
self.add_savepoint(_('Rename %s') % oldname)
452
self.add_savepoint(_('Before: Rename %s') % oldname)
438
453
name_map = {oldname:newname}
439
454
self.gui.blocking_job(
440
455
'rename_file', _('Renaming and updating links...'), partial(self.rename_done, name_map),
441
456
rename_files, current_container(), name_map)
443
458
def bulk_rename_requested(self, name_map):
444
self.commit_all_editors_to_container()
445
self.add_savepoint(_('Bulk rename'))
459
self.add_savepoint(_('Before: Bulk rename'))
446
460
self.gui.blocking_job(
447
461
'bulk_rename_files', _('Renaming and updating links...'), partial(self.rename_done, name_map),
448
462
rename_files, current_container(), name_map)
479
493
def update_global_history_actions(self):
480
494
gu = self.global_undo
481
for x, text in (('undo', _('&Revert to before')), ('redo', '&Revert to after')):
495
for x, text in (('undo', _('&Revert to')), ('redo', '&Revert to')):
482
496
ac = getattr(self.gui, 'action_global_%s' % x)
483
497
ac.setEnabled(getattr(gu, 'can_' + x))
484
ac.setText(text + ' ' + (getattr(gu, x + '_msg') or '...'))
498
ac.setText(text + ' "%s"'%(getattr(gu, x + '_msg') or '...'))
486
500
def add_savepoint(self, msg):
501
self.commit_all_editors_to_container()
487
502
nc = clone_container(current_container(), self.mkdtemp())
488
503
self.global_undo.add_savepoint(nc, msg)
489
504
set_current_container(nc)
554
569
if hasattr(ed, 'fix_html'):
557
self.commit_all_editors_to_container()
558
572
with BusyCursor():
559
self.add_savepoint(_('Fix HTML'))
573
self.add_savepoint(_('Before: Fix HTML'))
560
574
fix_all_html(current_container())
561
575
self.update_editors_from_container()
562
576
self.set_modified()
570
584
ed.pretty_print(name)
572
self.commit_all_editors_to_container()
573
586
with BusyCursor():
574
self.add_savepoint(_('Beautify files'))
587
self.add_savepoint(_('Before: Beautify files'))
575
588
pretty_all(current_container())
576
589
self.update_editors_from_container()
577
590
self.set_modified()
694
707
if editor is not None:
695
if editor.find(pat, marked=marked):
708
if editor.find(pat, marked=marked, save_match='gui'):
698
711
if not state['wrap']:
699
712
return no_match()
700
return editor.find(pat, wrap=True, marked=marked) or no_match()
713
return editor.find(pat, wrap=True, marked=marked, save_match='gui') or no_match()
701
714
for fname, syntax in files.iteritems():
702
715
if fname in editors:
703
if not editors[fname].find(pat, complete=True):
716
if not editors[fname].find(pat, complete=True, save_match='gui'):
705
718
return self.show_editor(fname)
706
719
raw = current_container().raw_data(fname)
707
720
if pat.search(raw) is not None:
708
721
self.edit_file(fname, syntax)
709
if editors[fname].find(pat, complete=True):
722
if editors[fname].find(pat, complete=True, save_match='gui'):
711
724
return no_match()
722
735
def do_replace():
723
736
if editor is None:
724
737
return no_replace()
725
if not editor.replace(pat, state['replace']):
738
if not editor.replace(pat, state['replace'], saved_match='gui'):
726
739
return no_replace(_(
727
740
'Currently selected text does not match the search query.'))
730
def count_message(action, count):
743
def count_message(action, count, show_diff=False):
731
744
msg = _('%(action)s %(num)s occurrences of %(query)s' % dict(num=count, query=state['find'], action=action))
732
info_dialog(self.gui, _('Searching done'), prepare_string_for_xml(msg), show=True)
745
if show_diff and count > 0:
746
d = MessageBox(MessageBox.INFO, _('Searching done'), prepare_string_for_xml(msg), parent=self.gui, show_copy_button=False)
747
d.diffb = b = d.bb.addButton(_('See what &changed'), d.bb.ActionRole)
748
b.setIcon(QIcon(I('diff.png'))), d.set_details(None), b.clicked.connect(d.accept)
749
b.clicked.connect(partial(self.show_current_diff, allow_revert=True))
752
info_dialog(self.gui, _('Searching done'), prepare_string_for_xml(msg), show=True)
734
754
def do_all(replace=True):
754
774
with current_container().open(n, 'wb') as f:
755
775
f.write(raw.encode('utf-8'))
756
776
QApplication.restoreOverrideCursor()
757
count_message(_('Replaced') if replace else _('Found'), count)
777
count_message(_('Replaced') if replace else _('Found'), count, show_diff=replace)
760
780
with BusyCursor():
767
787
if action == 'replace-all':
769
789
return count_message(_('Replaced'), editor.all_in_marked(pat, state['replace']))
770
self.add_savepoint(_('Replace all'))
790
self.add_savepoint(_('Before: Replace all'))
773
793
self.rewind_savepoint()
809
829
if ed.is_modified or not ed.is_synced_to_container:
810
830
self.commit_editor_to_container(name, c)
811
831
ed.is_modified = False
832
path_to_ebook = os.path.abspath(c.path_to_ebook)
833
destdir = os.path.dirname(path_to_ebook)
834
if not os.path.exists(destdir):
835
info_dialog(self.gui, _('Path does not exist'), _(
836
'The file you are editing (%s) no longer exists. You have to choose a new save location.') % path_to_ebook,
837
show_copy_button=False, show=True)
838
fmt = path_to_ebook.rpartition('.')[-1].lower()
839
start_dir = find_first_existing_ancestor(path_to_ebook)
840
path = choose_save_file(self.gui, 'choose-new-save-location', _('Choose file location'), initial_dir=start_dir,
841
filters=[(fmt.upper(), (fmt,))], all_files=False)
843
if not path.lower().endswith('.' + fmt):
844
path = path + '.' + fmt
845
path = os.path.abspath(path)
846
c.path_to_ebook = path
847
self.global_undo.update_path_to_ebook(path)
812
850
self.gui.action_save.setEnabled(False)
813
851
tdir = self.mkdtemp(prefix='save-')
814
852
container = clone_container(c, tdir)
849
887
error_dialog(self.gui, _('Could not save'),
850
888
_('Saving of the book failed. Click "Show Details"'
851
' for more information.'), det_msg=tb, show=True)
889
' for more information. You can try to save a copy'
890
' to a different location, via File->Save a Copy'), det_msg=tb, show=True)
853
892
def go_to_line_number(self):
854
893
ed = self.gui.central.current_editor
863
902
self.gui.preview.do_start_split()
866
def split_requested(self, name, loc):
867
self.commit_all_editors_to_container()
868
self.add_savepoint(_('Split %s') % self.gui.elided_text(name))
905
def split_requested(self, name, loc, totals):
906
self.add_savepoint(_('Before: Split %s') % self.gui.elided_text(name))
870
bottom_name = split(current_container(), name, loc)
908
bottom_name = split(current_container(), name, loc, totals=totals)
871
909
except AbortError:
872
910
self.rewind_savepoint()
888
926
d = MultiSplit(self.gui)
889
927
if d.exec_() == d.Accepted:
890
928
with BusyCursor():
891
self.commit_all_editors_to_container()
892
self.add_savepoint(_('Split %s') % self.gui.elided_text(name))
929
self.add_savepoint(_('Before: Split %s') % self.gui.elided_text(name))
894
931
multisplit(current_container(), name, d.xpath)
895
932
except AbortError:
947
984
def fix_requested(self, errors):
948
self.commit_all_editors_to_container()
949
self.add_savepoint(_('Auto-fix errors'))
985
self.add_savepoint(_('Before: Auto-fix errors'))
950
986
c = self.gui.check_book
951
987
c.parent().show()
952
988
c.parent().raise_()
961
997
def merge_requested(self, category, names, master):
962
self.commit_all_editors_to_container()
963
self.add_savepoint(_('Merge files into %s') % self.gui.elided_text(master))
998
self.add_savepoint(_('Before: Merge files into %s') % self.gui.elided_text(master))
965
1000
merge(current_container(), category, names, master)
966
1001
except AbortError:
974
1009
def link_stylesheets_requested(self, names, sheets, remove):
975
self.commit_all_editors_to_container()
976
self.add_savepoint(_('Link stylesheets'))
1010
self.add_savepoint(_('Before: Link stylesheets'))
977
1011
changed_names = link_stylesheets(current_container(), names, sheets, remove)
978
1012
if changed_names:
979
1013
self.update_editors_from_container(names=changed_names)
990
1024
def replace_requested(self, name, path, basename, force_mt):
991
self.commit_all_editors_to_container()
992
self.add_savepoint(_('Replace %s') % name)
1025
self.add_savepoint(_('Before: Replace %s') % name)
993
1026
replace_file(current_container(), name, path, basename, force_mt)
994
1027
self.apply_container_update_to_gui()