~ubuntu-branches/debian/sid/calibre/sid

« back to all changes in this revision

Viewing changes to src/calibre/gui2/tweak_book/boss.py

  • Committer: Package Import Robot
  • Author(s): Martin Pitt
  • Date: 2014-02-27 07:48:06 UTC
  • mto: This revision was merged to the branch mainline in revision 74.
  • Revision ID: package-import@ubuntu.com-20140227074806-64wdebb3ptosxhhx
Tags: upstream-1.25.0+dfsg
ImportĀ upstreamĀ versionĀ 1.25.0+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
156
157
            return
157
158
        d = NewBook(self.gui)
158
159
        if d.exec_() == d.Accepted:
159
 
            fmt = d.fmt
 
160
            fmt = d.fmt.lower()
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)
230
233
            if ef:
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'))
233
237
 
234
238
    def update_editors_from_container(self, container=None, names=None):
235
239
        c = container or current_container()
254
258
 
255
259
    @in_thread_job
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)
281
284
 
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:
301
303
            return
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():
340
341
                i = 0
354
355
            self.set_modified()
355
356
 
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()
367
367
 
 
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())
 
372
        if name is None:
 
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')
 
380
 
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)
372
384
            try:
373
385
                report, changed = tweak_polish(current_container(), {action:True})
374
386
            except:
377
389
            self.apply_container_update_to_gui()
378
390
            from calibre.ebooks.markdown import markdown
379
391
            report = markdown('# %s\n\n'%self.current_metadata.title + '\n\n'.join(report), output_format='html4')
 
392
        if not changed:
 
393
            self.rewind_savepoint()
380
394
        d = QDialog(self.gui)
381
395
        d.l = QVBoxLayout()
382
396
        d.setLayout(d.l)
385
399
        d.e.setHtml(report)
386
400
        d.bb = QDialogButtonBox(QDialogButtonBox.Close)
387
401
        if changed:
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)
410
425
        if not name_map:
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):
436
451
                    return
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)
442
457
 
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)
478
492
 
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 '...'))
485
499
 
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'):
555
570
                ed.fix_html()
556
571
        else:
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()
569
583
                    break
570
584
            ed.pretty_print(name)
571
585
        else:
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()
692
705
 
693
706
        def do_find():
694
707
            if editor is not None:
695
 
                if editor.find(pat, marked=marked):
 
708
                if editor.find(pat, marked=marked, save_match='gui'):
696
709
                    return
697
710
                if not files:
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'):
704
717
                        continue
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'):
710
723
                        return
711
724
            return no_match()
712
725
 
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.'))
728
741
            return True
729
742
 
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))
 
750
                d.exec_()
 
751
            else:
 
752
                info_dialog(self.gui, _('Searching done'), prepare_string_for_xml(msg), show=True)
733
753
 
734
754
        def do_all(replace=True):
735
755
            count = 0
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)
758
778
            return count
759
779
 
760
780
        with BusyCursor():
767
787
            if action == 'replace-all':
768
788
                if marked:
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'))
771
791
                count = do_all()
772
792
                if count == 0:
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)
 
842
            if path is not None:
 
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)
 
848
            else:
 
849
                return
812
850
        self.gui.action_save.setEnabled(False)
813
851
        tdir = self.mkdtemp(prefix='save-')
814
852
        container = clone_container(c, tdir)
848
886
            return
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)
852
891
 
853
892
    def go_to_line_number(self):
854
893
        ed = self.gui.central.current_editor
863
902
        self.gui.preview.do_start_split()
864
903
 
865
904
    @in_thread_job
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))
869
907
        try:
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()
873
911
            raise
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))
893
930
                try:
894
931
                    multisplit(current_container(), name, d.xpath)
895
932
                except AbortError:
945
982
 
946
983
    @in_thread_job
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_()
959
995
 
960
996
    @in_thread_job
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))
964
999
        try:
965
1000
            merge(current_container(), category, names, master)
966
1001
        except AbortError:
972
1007
 
973
1008
    @in_thread_job
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)
988
1022
 
989
1023
    @in_thread_job
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()
995
1028
 
1177
1210
        if not self.confirm_quit():
1178
1211
            return
1179
1212
        self.save_state()
 
1213
        self.shutdown()
1180
1214
        QApplication.instance().quit()
1181
1215
 
1182
1216
    def confirm_quit(self):