class TreeTOCContainer(TOCContainer):

    # Constructor
    # parent (EBookViewer) - the main interface
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        w = self
        w.l = QVBoxLayout(w)
        self.toc = TOCView(w)
        self.toc_search = TOCSearch(self.toc, parent=w)
        w.l.addWidget(self.toc)
        w.l.addWidget(self.toc_search)
        w.l.setContentsMargins(0, 0, 0, 0)
        
    # Connects the click method of the TOC interface to a method from EBookViewer
    # toc_clicked_method (method) - the method to connect the action to
    def connect_toc_actions(self, toc_clicked_method):
        self.toc.pressed[QModelIndex].connect(toc_clicked_method)
        self.toc.searched.connect(partial(toc_clicked_method, force=True))
        
    # Sets the hierarchy of information for display
    # toc_sections (TOCSections) - a object that determines how the sections of the ebook are grouped
    # toc_model (calibre.gui2.viewer.TOC) - the object storing all information about the hierarchy
    # title (string) - the title of the ebook
    # pathtoebook (string) - the full path to the ebook's location
    def setup_ebook(self, toc_sections, toc_model, title, pathtoebook):
        self.toc.setModel(toc_model)
        
    # Updates the selected item in the hierarchy
    # item_index (int) - the index to highlight
    def scroll_to(self, item_index):
        self.toc.scrollTo(item_index)
        
 def __init__(self, parent):
     QWidget.__init__(self, parent)
     w = self
     w.l = QVBoxLayout(w)
     self.toc = TOCView(w)
     self.toc_search = TOCSearch(self.toc, parent=w)
     w.l.addWidget(self.toc)
     w.l.addWidget(self.toc_search)
     w.l.setContentsMargins(0, 0, 0, 0)
示例#3
0
    def __init__(self, open_at=None, continue_reading=None, force_reload=False, calibre_book_data=None):
        MainWindow.__init__(self, None)
        self.annotations_saver = None
        self.calibre_book_data_for_first_book = calibre_book_data
        self.shutting_down = self.close_forced = self.shutdown_done = False
        self.force_reload = force_reload
        connect_lambda(self.book_preparation_started, self, lambda self: self.loading_overlay(_(
            'Preparing book for first read, please wait')), type=Qt.ConnectionType.QueuedConnection)
        self.maximized_at_last_fullscreen = False
        self.save_pos_timer = t = QTimer(self)
        t.setSingleShot(True), t.setInterval(3000), t.setTimerType(Qt.TimerType.VeryCoarseTimer)
        connect_lambda(t.timeout, self, lambda self: self.save_annotations(in_book_file=False))
        self.pending_open_at = open_at
        self.base_window_title = _('E-book viewer')
        self.setDockOptions(QMainWindow.DockOption.AnimatedDocks | QMainWindow.DockOption.AllowTabbedDocks | QMainWindow.DockOption.AllowNestedDocks)
        self.setWindowTitle(self.base_window_title)
        self.in_full_screen_mode = None
        self.image_popup = ImagePopup(self)
        self.actions_toolbar = at = ActionsToolBar(self)
        at.open_book_at_path.connect(self.ask_for_open)
        self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, at)
        try:
            os.makedirs(annotations_dir)
        except EnvironmentError:
            pass
        self.current_book_data = {}
        get_current_book_data(self.current_book_data)
        self.book_prepared.connect(self.load_finished, type=Qt.ConnectionType.QueuedConnection)
        self.dock_defs = dock_defs()

        def create_dock(title, name, area, areas=Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea):
            ans = QDockWidget(title, self)
            ans.setObjectName(name)
            self.addDockWidget(area, ans)
            ans.setVisible(False)
            ans.visibilityChanged.connect(self.dock_visibility_changed)
            return ans

        for dock_def in itervalues(self.dock_defs):
            setattr(self, '{}_dock'.format(dock_def.name.partition('-')[0]), create_dock(
                dock_def.title, dock_def.name, dock_def.initial_area, dock_def.allowed_areas))

        self.toc_container = w = QWidget(self)
        w.l = QVBoxLayout(w)
        self.toc = TOCView(w)
        self.toc.clicked[QModelIndex].connect(self.toc_clicked)
        self.toc.searched.connect(self.toc_searched)
        self.toc_search = TOCSearch(self.toc, parent=w)
        w.l.addWidget(self.toc), w.l.addWidget(self.toc_search), w.l.setContentsMargins(0, 0, 0, 0)
        self.toc_dock.setWidget(w)

        self.search_widget = w = SearchPanel(self)
        w.search_requested.connect(self.start_search)
        w.hide_search_panel.connect(self.search_dock.close)
        w.count_changed.connect(self.search_results_count_changed)
        w.goto_cfi.connect(self.goto_cfi)
        self.search_dock.setWidget(w)
        self.search_dock.visibilityChanged.connect(self.search_widget.visibility_changed)

        self.lookup_widget = w = Lookup(self)
        self.lookup_dock.visibilityChanged.connect(self.lookup_widget.visibility_changed)
        self.lookup_dock.setWidget(w)

        self.bookmarks_widget = w = BookmarkManager(self)
        connect_lambda(
            w.create_requested, self,
            lambda self: self.web_view.trigger_shortcut('new_bookmark'))
        w.edited.connect(self.bookmarks_edited)
        w.activated.connect(self.bookmark_activated)
        w.toggle_requested.connect(self.toggle_bookmarks)
        self.bookmarks_dock.setWidget(w)

        self.highlights_widget = w = HighlightsPanel(self)
        self.highlights_dock.setWidget(w)
        w.toggle_requested.connect(self.toggle_highlights)

        self.web_view = WebView(self)
        self.web_view.cfi_changed.connect(self.cfi_changed)
        self.web_view.reload_book.connect(self.reload_book)
        self.web_view.toggle_toc.connect(self.toggle_toc)
        self.web_view.show_search.connect(self.show_search)
        self.web_view.find_next.connect(self.search_widget.find_next_requested)
        self.search_widget.show_search_result.connect(self.web_view.show_search_result)
        self.web_view.search_result_not_found.connect(self.search_widget.search_result_not_found)
        self.web_view.search_result_discovered.connect(self.search_widget.search_result_discovered)
        self.web_view.toggle_bookmarks.connect(self.toggle_bookmarks)
        self.web_view.toggle_highlights.connect(self.toggle_highlights)
        self.web_view.new_bookmark.connect(self.bookmarks_widget.create_new_bookmark)
        self.web_view.toggle_inspector.connect(self.toggle_inspector)
        self.web_view.toggle_lookup.connect(self.toggle_lookup)
        self.web_view.quit.connect(self.quit)
        self.web_view.update_current_toc_nodes.connect(self.toc.update_current_toc_nodes)
        self.web_view.toggle_full_screen.connect(self.toggle_full_screen)
        self.web_view.ask_for_open.connect(self.ask_for_open, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.selection_changed.connect(self.lookup_widget.selected_text_changed, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.selection_changed.connect(self.highlights_widget.selected_text_changed, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.view_image.connect(self.view_image, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.copy_image.connect(self.copy_image, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.show_loading_message.connect(self.show_loading_message)
        self.web_view.show_error.connect(self.show_error)
        self.web_view.print_book.connect(self.print_book, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.reset_interface.connect(self.reset_interface, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.quit.connect(self.quit, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.shortcuts_changed.connect(self.shortcuts_changed)
        self.web_view.scrollbar_context_menu.connect(self.scrollbar_context_menu)
        self.web_view.close_prep_finished.connect(self.close_prep_finished)
        self.web_view.highlights_changed.connect(self.highlights_changed)
        self.web_view.edit_book.connect(self.edit_book)
        self.actions_toolbar.initialize(self.web_view, self.search_dock.toggleViewAction())
        at.update_action_state(False)
        self.setCentralWidget(self.web_view)
        self.loading_overlay = LoadingOverlay(self)
        self.restore_state()
        self.actions_toolbar.update_visibility()
        self.dock_visibility_changed()
        self.highlights_widget.request_highlight_action.connect(self.web_view.highlight_action)
        self.highlights_widget.web_action.connect(self.web_view.generic_action)
        if continue_reading:
            self.continue_reading()
        self.setup_mouse_auto_hide()
示例#4
0
class EbookViewer(MainWindow):

    msg_from_anotherinstance = pyqtSignal(object)
    book_preparation_started = pyqtSignal()
    book_prepared = pyqtSignal(object, object)
    MAIN_WINDOW_STATE_VERSION = 1

    def __init__(self, open_at=None, continue_reading=None, force_reload=False, calibre_book_data=None):
        MainWindow.__init__(self, None)
        self.annotations_saver = None
        self.calibre_book_data_for_first_book = calibre_book_data
        self.shutting_down = self.close_forced = self.shutdown_done = False
        self.force_reload = force_reload
        connect_lambda(self.book_preparation_started, self, lambda self: self.loading_overlay(_(
            'Preparing book for first read, please wait')), type=Qt.ConnectionType.QueuedConnection)
        self.maximized_at_last_fullscreen = False
        self.save_pos_timer = t = QTimer(self)
        t.setSingleShot(True), t.setInterval(3000), t.setTimerType(Qt.TimerType.VeryCoarseTimer)
        connect_lambda(t.timeout, self, lambda self: self.save_annotations(in_book_file=False))
        self.pending_open_at = open_at
        self.base_window_title = _('E-book viewer')
        self.setDockOptions(QMainWindow.DockOption.AnimatedDocks | QMainWindow.DockOption.AllowTabbedDocks | QMainWindow.DockOption.AllowNestedDocks)
        self.setWindowTitle(self.base_window_title)
        self.in_full_screen_mode = None
        self.image_popup = ImagePopup(self)
        self.actions_toolbar = at = ActionsToolBar(self)
        at.open_book_at_path.connect(self.ask_for_open)
        self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, at)
        try:
            os.makedirs(annotations_dir)
        except EnvironmentError:
            pass
        self.current_book_data = {}
        get_current_book_data(self.current_book_data)
        self.book_prepared.connect(self.load_finished, type=Qt.ConnectionType.QueuedConnection)
        self.dock_defs = dock_defs()

        def create_dock(title, name, area, areas=Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea):
            ans = QDockWidget(title, self)
            ans.setObjectName(name)
            self.addDockWidget(area, ans)
            ans.setVisible(False)
            ans.visibilityChanged.connect(self.dock_visibility_changed)
            return ans

        for dock_def in itervalues(self.dock_defs):
            setattr(self, '{}_dock'.format(dock_def.name.partition('-')[0]), create_dock(
                dock_def.title, dock_def.name, dock_def.initial_area, dock_def.allowed_areas))

        self.toc_container = w = QWidget(self)
        w.l = QVBoxLayout(w)
        self.toc = TOCView(w)
        self.toc.clicked[QModelIndex].connect(self.toc_clicked)
        self.toc.searched.connect(self.toc_searched)
        self.toc_search = TOCSearch(self.toc, parent=w)
        w.l.addWidget(self.toc), w.l.addWidget(self.toc_search), w.l.setContentsMargins(0, 0, 0, 0)
        self.toc_dock.setWidget(w)

        self.search_widget = w = SearchPanel(self)
        w.search_requested.connect(self.start_search)
        w.hide_search_panel.connect(self.search_dock.close)
        w.count_changed.connect(self.search_results_count_changed)
        w.goto_cfi.connect(self.goto_cfi)
        self.search_dock.setWidget(w)
        self.search_dock.visibilityChanged.connect(self.search_widget.visibility_changed)

        self.lookup_widget = w = Lookup(self)
        self.lookup_dock.visibilityChanged.connect(self.lookup_widget.visibility_changed)
        self.lookup_dock.setWidget(w)

        self.bookmarks_widget = w = BookmarkManager(self)
        connect_lambda(
            w.create_requested, self,
            lambda self: self.web_view.trigger_shortcut('new_bookmark'))
        w.edited.connect(self.bookmarks_edited)
        w.activated.connect(self.bookmark_activated)
        w.toggle_requested.connect(self.toggle_bookmarks)
        self.bookmarks_dock.setWidget(w)

        self.highlights_widget = w = HighlightsPanel(self)
        self.highlights_dock.setWidget(w)
        w.toggle_requested.connect(self.toggle_highlights)

        self.web_view = WebView(self)
        self.web_view.cfi_changed.connect(self.cfi_changed)
        self.web_view.reload_book.connect(self.reload_book)
        self.web_view.toggle_toc.connect(self.toggle_toc)
        self.web_view.show_search.connect(self.show_search)
        self.web_view.find_next.connect(self.search_widget.find_next_requested)
        self.search_widget.show_search_result.connect(self.web_view.show_search_result)
        self.web_view.search_result_not_found.connect(self.search_widget.search_result_not_found)
        self.web_view.search_result_discovered.connect(self.search_widget.search_result_discovered)
        self.web_view.toggle_bookmarks.connect(self.toggle_bookmarks)
        self.web_view.toggle_highlights.connect(self.toggle_highlights)
        self.web_view.new_bookmark.connect(self.bookmarks_widget.create_new_bookmark)
        self.web_view.toggle_inspector.connect(self.toggle_inspector)
        self.web_view.toggle_lookup.connect(self.toggle_lookup)
        self.web_view.quit.connect(self.quit)
        self.web_view.update_current_toc_nodes.connect(self.toc.update_current_toc_nodes)
        self.web_view.toggle_full_screen.connect(self.toggle_full_screen)
        self.web_view.ask_for_open.connect(self.ask_for_open, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.selection_changed.connect(self.lookup_widget.selected_text_changed, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.selection_changed.connect(self.highlights_widget.selected_text_changed, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.view_image.connect(self.view_image, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.copy_image.connect(self.copy_image, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.show_loading_message.connect(self.show_loading_message)
        self.web_view.show_error.connect(self.show_error)
        self.web_view.print_book.connect(self.print_book, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.reset_interface.connect(self.reset_interface, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.quit.connect(self.quit, type=Qt.ConnectionType.QueuedConnection)
        self.web_view.shortcuts_changed.connect(self.shortcuts_changed)
        self.web_view.scrollbar_context_menu.connect(self.scrollbar_context_menu)
        self.web_view.close_prep_finished.connect(self.close_prep_finished)
        self.web_view.highlights_changed.connect(self.highlights_changed)
        self.web_view.edit_book.connect(self.edit_book)
        self.actions_toolbar.initialize(self.web_view, self.search_dock.toggleViewAction())
        at.update_action_state(False)
        self.setCentralWidget(self.web_view)
        self.loading_overlay = LoadingOverlay(self)
        self.restore_state()
        self.actions_toolbar.update_visibility()
        self.dock_visibility_changed()
        self.highlights_widget.request_highlight_action.connect(self.web_view.highlight_action)
        self.highlights_widget.web_action.connect(self.web_view.generic_action)
        if continue_reading:
            self.continue_reading()
        self.setup_mouse_auto_hide()

    def shortcuts_changed(self, smap):
        rmap = defaultdict(list)
        for k, v in iteritems(smap):
            rmap[v].append(k)
        self.actions_toolbar.set_tooltips(rmap)
        self.highlights_widget.set_tooltips(rmap)

    def resizeEvent(self, ev):
        self.loading_overlay.resize(self.size())
        return MainWindow.resizeEvent(self, ev)

    def scrollbar_context_menu(self, x, y, frac):
        m = QMenu(self)
        amap = {}

        def a(text, name):
            m.addAction(text)
            amap[text] = name

        a(_('Scroll here'), 'here')
        m.addSeparator()
        a(_('Start of book'), 'start_of_book')
        a(_('End of book'), 'end_of_book')
        m.addSeparator()
        a(_('Previous section'), 'previous_section')
        a(_('Next section'), 'next_section')
        m.addSeparator()
        a(_('Start of current file'), 'start_of_file')
        a(_('End of current file'), 'end_of_file')
        m.addSeparator()
        a(_('Hide this scrollbar'), 'toggle_scrollbar')

        q = m.exec_(QCursor.pos())
        if not q:
            return
        q = amap[q.text()]
        if q == 'here':
            self.web_view.goto_frac(frac)
        else:
            self.web_view.trigger_shortcut(q)

    # IPC {{{
    def handle_commandline_arg(self, arg):
        if arg:
            if os.path.isfile(arg) and os.access(arg, os.R_OK):
                self.load_ebook(arg)
            else:
                prints('Cannot read from:', arg, file=sys.stderr)

    def message_from_other_instance(self, msg):
        try:
            msg = json.loads(msg)
            path, open_at = msg
        except Exception as err:
            print('Invalid message from other instance', file=sys.stderr)
            print(err, file=sys.stderr)
            return
        self.load_ebook(path, open_at=open_at)
        self.raise_()
        self.activateWindow()
    # }}}

    # Fullscreen {{{
    def set_full_screen(self, on):
        if on:
            self.maximized_at_last_fullscreen = self.isMaximized()
            if not self.actions_toolbar.visible_in_fullscreen:
                self.actions_toolbar.setVisible(False)
            self.showFullScreen()
        else:
            self.actions_toolbar.update_visibility()
            if self.maximized_at_last_fullscreen:
                self.showMaximized()
            else:
                self.showNormal()

    def changeEvent(self, ev):
        if ev.type() == QEvent.Type.WindowStateChange:
            in_full_screen_mode = self.isFullScreen()
            if self.in_full_screen_mode is None or self.in_full_screen_mode != in_full_screen_mode:
                self.in_full_screen_mode = in_full_screen_mode
                self.web_view.notify_full_screen_state_change(self.in_full_screen_mode)
        return MainWindow.changeEvent(self, ev)

    def toggle_full_screen(self):
        self.set_full_screen(not self.isFullScreen())

    # }}}

    # Docks (ToC, Bookmarks, Lookup, etc.) {{{

    def toggle_inspector(self):
        visible = self.inspector_dock.toggleViewAction().isChecked()
        self.inspector_dock.setVisible(not visible)

    def toggle_toc(self):
        is_visible = self.toc_dock.isVisible()
        self.toc_dock.setVisible(not is_visible)
        if not is_visible:
            self.toc.scroll_to_current_toc_node()

    def show_search(self, text, trigger=False):
        self.search_dock.setVisible(True)
        self.search_dock.activateWindow()
        self.search_dock.raise_()
        self.search_widget.focus_input(text)
        if trigger:
            self.search_widget.trigger()

    def search_results_count_changed(self, num=-1):
        if num < 0:
            tt = _('Search')
        elif num == 0:
            tt = _('Search :: no matches')
        elif num == 1:
            tt = _('Search :: one match')
        else:
            tt = _('Search :: {} matches').format(num)
        self.search_dock.setWindowTitle(tt)

    def start_search(self, search_query):
        name = self.web_view.current_content_file
        if name:
            self.web_view.get_current_cfi(self.search_widget.set_anchor_cfi)
            self.search_widget.start_search(search_query, name)
            self.web_view.setFocus(Qt.FocusReason.OtherFocusReason)

    def toggle_bookmarks(self):
        is_visible = self.bookmarks_dock.isVisible()
        self.bookmarks_dock.setVisible(not is_visible)
        if is_visible:
            self.web_view.setFocus(Qt.FocusReason.OtherFocusReason)
        else:
            self.bookmarks_widget.bookmarks_list.setFocus(Qt.FocusReason.OtherFocusReason)

    def toggle_highlights(self):
        is_visible = self.highlights_dock.isVisible()
        self.highlights_dock.setVisible(not is_visible)
        if is_visible:
            self.web_view.setFocus(Qt.FocusReason.OtherFocusReason)
        else:
            self.highlights_widget.focus()

    def toggle_lookup(self, force_show=False):
        self.lookup_dock.setVisible(force_show or not self.lookup_dock.isVisible())
        if force_show and self.lookup_dock.isVisible():
            self.lookup_widget.on_forced_show()

    def toc_clicked(self, index):
        item = self.toc_model.itemFromIndex(index)
        self.web_view.goto_toc_node(item.node_id)

    def toc_searched(self, index):
        item = self.toc_model.itemFromIndex(index)
        self.web_view.goto_toc_node(item.node_id)

    def bookmarks_edited(self, bookmarks):
        self.current_book_data['annotations_map']['bookmark'] = bookmarks
        # annotations will be saved in book file on exit
        self.save_annotations(in_book_file=False)

    def goto_cfi(self, cfi):
        self.web_view.goto_cfi(cfi)

    def bookmark_activated(self, cfi):
        self.goto_cfi(cfi)

    def view_image(self, name):
        path = get_path_for_name(name)
        if path:
            pmap = QPixmap()
            if pmap.load(path):
                self.image_popup.current_img = pmap
                self.image_popup.current_url = QUrl.fromLocalFile(path)
                self.image_popup()
            else:
                error_dialog(self, _('Invalid image'), _(
                    "Failed to load the image {}").format(name), show=True)
        else:
            error_dialog(self, _('Image not found'), _(
                    "Failed to find the image {}").format(name), show=True)

    def copy_image(self, name):
        path = get_path_for_name(name)
        if not path:
            return error_dialog(self, _('Image not found'), _(
                "Failed to find the image {}").format(name), show=True)
        try:
            img = image_from_path(path)
        except Exception:
            return error_dialog(self, _('Invalid image'), _(
                "Failed to load the image {}").format(name), show=True)
        url = QUrl.fromLocalFile(path)
        md = QMimeData()
        md.setImageData(img)
        md.setUrls([url])
        QApplication.instance().clipboard().setMimeData(md)

    def dock_visibility_changed(self):
        vmap = {dock.objectName().partition('-')[0]: dock.toggleViewAction().isChecked() for dock in self.dock_widgets}
        self.actions_toolbar.update_dock_actions(vmap)
    # }}}

    # Load book {{{

    def show_loading_message(self, msg):
        if msg:
            self.loading_overlay(msg)
            self.actions_toolbar.update_action_state(False)
        else:
            if not hasattr(self, 'initial_loading_performace_reported'):
                performance_monitor('loading finished')
                self.initial_loading_performace_reported = True
            self.loading_overlay.hide()
            self.actions_toolbar.update_action_state(True)

    def show_error(self, title, msg, details):
        self.loading_overlay.hide()
        error_dialog(self, title, msg, det_msg=details or None, show=True)

    def print_book(self):
        from .printing import print_book
        print_book(set_book_path.pathtoebook, book_title=self.current_book_data['metadata']['title'], parent=self)

    @property
    def dock_widgets(self):
        return self.findChildren(QDockWidget, options=Qt.FindChildOption.FindDirectChildrenOnly)

    def reset_interface(self):
        for dock in self.dock_widgets:
            dock.setFloating(False)
            area = self.dock_defs[dock.objectName().partition('-')[0]].initial_area
            self.removeDockWidget(dock)
            self.addDockWidget(area, dock)
            dock.setVisible(False)

        for toolbar in self.findChildren(QToolBar):
            toolbar.setVisible(False)
            self.removeToolBar(toolbar)
            self.addToolBar(Qt.ToolBarArea.LeftToolBarArea, toolbar)

    def ask_for_open(self, path=None):
        if path is None:
            files = choose_files(
                self, 'ebook viewer open dialog',
                _('Choose e-book'), [(_('E-books'), available_input_formats())],
                all_files=False, select_only_single_file=True)
            if not files:
                return
            path = files[0]
        self.load_ebook(path)

    def continue_reading(self):
        rl = vprefs['session_data'].get('standalone_recently_opened')
        if rl:
            entry = rl[0]
            self.load_ebook(entry['pathtoebook'])

    def load_ebook(self, pathtoebook, open_at=None, reload_book=False):
        performance_monitor('Load of book started', reset=True)
        self.actions_toolbar.update_action_state(False)
        self.web_view.show_home_page_on_ready = False
        if open_at:
            self.pending_open_at = open_at
        self.setWindowTitle(_('Loading book') + '… — {}'.format(self.base_window_title))
        self.loading_overlay(_('Loading book, please wait'))
        self.save_annotations()
        self.current_book_data = {}
        get_current_book_data(self.current_book_data)
        self.search_widget.clear_searches()
        t = Thread(name='LoadBook', target=self._load_ebook_worker, args=(pathtoebook, open_at, reload_book or self.force_reload))
        t.daemon = True
        t.start()

    def reload_book(self):
        if self.current_book_data:
            self.load_ebook(self.current_book_data['pathtoebook'], reload_book=True)

    def _load_ebook_worker(self, pathtoebook, open_at, reload_book):
        try:
            ans = prepare_book(pathtoebook, force=reload_book, prepare_notify=self.prepare_notify)
        except WorkerError as e:
            self.book_prepared.emit(False, {'exception': e, 'tb': e.orig_tb, 'pathtoebook': pathtoebook})
        except Exception as e:
            import traceback
            self.book_prepared.emit(False, {'exception': e, 'tb': traceback.format_exc(), 'pathtoebook': pathtoebook})
        else:
            performance_monitor('prepared emitted')
            self.book_prepared.emit(True, {'base': ans, 'pathtoebook': pathtoebook, 'open_at': open_at, 'reloaded': reload_book})

    def prepare_notify(self):
        self.book_preparation_started.emit()

    def load_finished(self, ok, data):
        cbd = self.calibre_book_data_for_first_book
        self.calibre_book_data_for_first_book = None
        if self.shutting_down:
            return
        open_at, self.pending_open_at = self.pending_open_at, None
        self.web_view.clear_caches()
        if not ok:
            self.actions_toolbar.update_action_state(False)
            self.setWindowTitle(self.base_window_title)
            tb = as_unicode(data['tb'].strip(), errors='replace')
            tb = re.split(r'^calibre\.gui2\.viewer\.convert_book\.ConversionFailure:\s*', tb, maxsplit=1, flags=re.M)[-1]
            last_line = tuple(tb.strip().splitlines())[-1]
            if last_line.startswith('calibre.ebooks.DRMError'):
                DRMErrorMessage(self).exec_()
            else:
                error_dialog(self, _('Loading book failed'), _(
                    'Failed to open the book at {0}. Click "Show details" for more info.').format(data['pathtoebook']),
                    det_msg=tb, show=True)
            self.loading_overlay.hide()
            self.web_view.show_home_page()
            return
        try:
            set_book_path(data['base'], data['pathtoebook'])
        except Exception:
            if data['reloaded']:
                raise
            self.load_ebook(data['pathtoebook'], open_at=data['open_at'], reload_book=True)
            return
        self.current_book_data = data
        get_current_book_data(self.current_book_data)
        self.current_book_data['annotations_map'] = defaultdict(list)
        self.current_book_data['annotations_path_key'] = path_key(data['pathtoebook']) + '.json'
        self.load_book_data(cbd)
        self.update_window_title()
        initial_cfi = self.initial_cfi_for_current_book()
        initial_position = {'type': 'cfi', 'data': initial_cfi} if initial_cfi else None
        if open_at:
            if open_at.startswith('toc:'):
                initial_toc_node = self.toc_model.node_id_for_text(open_at[len('toc:'):])
                initial_position = {'type': 'toc', 'data': initial_toc_node}
            elif open_at.startswith('toc-href:'):
                initial_toc_node = self.toc_model.node_id_for_href(open_at[len('toc-href:'):], exact=True)
                initial_position = {'type': 'toc', 'data': initial_toc_node}
            elif open_at.startswith('toc-href-contains:'):
                initial_toc_node = self.toc_model.node_id_for_href(open_at[len('toc-href-contains:'):], exact=False)
                initial_position = {'type': 'toc', 'data': initial_toc_node}
            elif open_at.startswith('epubcfi(/'):
                initial_position = {'type': 'cfi', 'data': open_at}
            elif open_at.startswith('ref:'):
                initial_position = {'type': 'ref', 'data': open_at[len('ref:'):]}
            elif is_float(open_at):
                initial_position = {'type': 'bookpos', 'data': float(open_at)}
        highlights = self.current_book_data['annotations_map']['highlight']
        self.highlights_widget.load(highlights)
        self.web_view.start_book_load(initial_position=initial_position, highlights=highlights, current_book_data=self.current_book_data)
        performance_monitor('webview loading requested')

    def load_book_data(self, calibre_book_data=None):
        self.current_book_data['book_library_details'] = get_book_library_details(self.current_book_data['pathtoebook'])
        if calibre_book_data is not None:
            self.current_book_data['calibre_book_id'] = calibre_book_data['book_id']
            self.current_book_data['calibre_book_uuid'] = calibre_book_data['uuid']
            self.current_book_data['calibre_book_fmt'] = calibre_book_data['fmt']
            self.current_book_data['calibre_library_id'] = calibre_book_data['library_id']
        self.load_book_annotations(calibre_book_data)
        path = os.path.join(self.current_book_data['base'], 'calibre-book-manifest.json')
        with open(path, 'rb') as f:
            raw = f.read()
        self.current_book_data['manifest'] = manifest = json.loads(raw)
        toc = manifest.get('toc')
        self.toc_model = TOC(toc)
        self.toc.setModel(self.toc_model)
        self.bookmarks_widget.set_bookmarks(self.current_book_data['annotations_map']['bookmark'])
        self.current_book_data['metadata'] = set_book_path.parsed_metadata
        self.current_book_data['manifest'] = set_book_path.parsed_manifest

    def load_book_annotations(self, calibre_book_data=None):
        amap = self.current_book_data['annotations_map']
        path = os.path.join(self.current_book_data['base'], 'calibre-book-annotations.json')
        if os.path.exists(path):
            with open(path, 'rb') as f:
                raw = f.read()
            merge_annotations(parse_annotations(raw), amap)
        path = os.path.join(annotations_dir, self.current_book_data['annotations_path_key'])
        if os.path.exists(path):
            with open(path, 'rb') as f:
                raw = f.read()
            merge_annotations(parse_annotations(raw), amap)
        if calibre_book_data is None:
            bld = self.current_book_data['book_library_details']
            if bld is not None:
                lib_amap = load_annotations_map_from_library(bld)
                sau = get_session_pref('sync_annots_user', default='')
                if sau:
                    other_amap = load_annotations_map_from_library(bld, user_type='web', user=sau)
                    if other_amap:
                        merge_annotations(other_amap, lib_amap)
                if lib_amap:
                    for annot_type, annots in iteritems(lib_amap):
                        merge_annotations(annots, amap)
        else:
            for annot_type, annots in iteritems(calibre_book_data['annotations_map']):
                merge_annotations(annots, amap)

    def update_window_title(self):
        try:
            title = self.current_book_data['metadata']['title']
        except Exception:
            title = _('Unknown')
        book_format = self.current_book_data['manifest']['book_format']
        title = '{} [{}] — {}'.format(title, book_format, self.base_window_title)
        self.setWindowTitle(title)
    # }}}

    # CFI management {{{
    def initial_cfi_for_current_book(self):
        lrp = self.current_book_data['annotations_map']['last-read']
        if lrp and get_session_pref('remember_last_read', default=True):
            lrp = lrp[0]
            if lrp['pos_type'] == 'epubcfi':
                return lrp['pos']

    def cfi_changed(self, cfi):
        if not self.current_book_data:
            return
        self.current_book_data['annotations_map']['last-read'] = [{
            'pos': cfi, 'pos_type': 'epubcfi', 'timestamp': utcnow().isoformat()}]
        self.save_pos_timer.start()
    # }}}

    # State serialization {{{
    def save_annotations(self, in_book_file=True):
        if not self.current_book_data:
            return
        if self.annotations_saver is None:
            self.annotations_saver = AnnotationsSaveWorker()
            self.annotations_saver.start()
        self.annotations_saver.save_annotations(
            self.current_book_data,
            in_book_file and get_session_pref('save_annotations_in_ebook', default=True),
            get_session_pref('sync_annots_user', default='')
        )

    def highlights_changed(self, highlights):
        if not self.current_book_data:
            return
        amap = self.current_book_data['annotations_map']
        amap['highlight'] = highlights
        self.highlights_widget.refresh(highlights)
        self.save_annotations()

    def edit_book(self, file_name, progress_frac, selected_text):
        import subprocess

        from calibre.ebooks.oeb.polish.main import SUPPORTED
        from calibre.utils.ipc.launch import exe_path, macos_edit_book_bundle_path
        try:
            path = set_book_path.pathtoebook
        except AttributeError:
            return error_dialog(self, _('Cannot edit book'), _(
                'No book is currently open'), show=True)
        fmt = path.rpartition('.')[-1].upper().replace('ORIGINAL_', '')
        if fmt not in SUPPORTED:
            return error_dialog(self, _('Cannot edit book'), _(
                'The book must be in the %s formats to edit.'
                '\n\nFirst convert the book to one of these formats.'
            ) % (_(' or ').join(SUPPORTED)), show=True)
        exe = 'ebook-edit'
        if ismacos:
            exe = os.path.join(macos_edit_book_bundle_path(), exe)
        else:
            exe = exe_path(exe)
        cmd = [exe]
        if selected_text:
            cmd += ['--select-text', selected_text]
        from calibre.gui2.tweak_book.widgets import BusyCursor
        with sanitize_env_vars():
            subprocess.Popen(cmd + [path, file_name])
            with BusyCursor():
                time.sleep(2)

    def save_state(self):
        with vprefs:
            vprefs['main_window_state'] = bytearray(self.saveState(self.MAIN_WINDOW_STATE_VERSION))
            vprefs['main_window_geometry'] = bytearray(self.saveGeometry())

    def restore_state(self):
        state = vprefs['main_window_state']
        geom = vprefs['main_window_geometry']
        if geom and get_session_pref('remember_window_geometry', default=False):
            QApplication.instance().safe_restore_geometry(self, geom)
        else:
            QApplication.instance().ensure_window_on_screen(self)
        if state:
            self.restoreState(state, self.MAIN_WINDOW_STATE_VERSION)
            self.inspector_dock.setVisible(False)
            if not get_session_pref('restore_docks', True):
                for dock_def in self.dock_defs.values():
                    d = getattr(self, '{}_dock'.format(dock_def.name.partition('-')[0]))
                    d.setVisible(False)

    def quit(self):
        self.close()

    def force_close(self):
        if not self.close_forced:
            self.close_forced = True
            self.quit()

    def close_prep_finished(self, cfi):
        if cfi:
            self.cfi_changed(cfi)
        self.force_close()

    def closeEvent(self, ev):
        if self.shutdown_done:
            return
        if self.current_book_data and self.web_view.view_is_ready and not self.close_forced:
            ev.ignore()
            if not self.shutting_down:
                self.shutting_down = True
                QTimer.singleShot(2000, self.force_close)
                self.web_view.prepare_for_close()
            return
        self.shutting_down = True
        self.search_widget.shutdown()
        self.web_view.shutdown()
        try:
            self.save_state()
            self.save_annotations()
            if self.annotations_saver is not None:
                self.annotations_saver.shutdown()
                self.annotations_saver = None
        except Exception:
            import traceback
            traceback.print_exc()
        clean_running_workers()
        self.shutdown_done = True
        return MainWindow.closeEvent(self, ev)
    # }}}

    # Auto-hide mouse cursor  {{{
    def setup_mouse_auto_hide(self):
        QApplication.instance().installEventFilter(self)
        self.cursor_hidden = False
        self.hide_cursor_timer = t = QTimer(self)
        t.setSingleShot(True), t.setInterval(3000)
        t.timeout.connect(self.hide_cursor)
        t.start()

    def eventFilter(self, obj, ev):
        et = ev.type()
        if et == QEvent.Type.MouseMove:
            if self.cursor_hidden:
                self.cursor_hidden = False
                QApplication.instance().restoreOverrideCursor()
            self.hide_cursor_timer.start()
        elif et == QEvent.Type.FocusIn:
            if iswindows and obj and obj.objectName() == 'EbookViewerClassWindow' and self.isFullScreen():
                # See https://bugs.launchpad.net/calibre/+bug/1918591
                self.web_view.repair_after_fullscreen_switch()
        return False

    def hide_cursor(self):
        if get_session_pref('auto_hide_mouse', True):
            self.cursor_hidden = True
            QApplication.instance().setOverrideCursor(Qt.CursorShape.BlankCursor)
示例#5
0
class EbookViewer(MainWindow):

    msg_from_anotherinstance = pyqtSignal(object)
    book_preparation_started = pyqtSignal()
    book_prepared = pyqtSignal(object, object)
    MAIN_WINDOW_STATE_VERSION = 1

    def __init__(self,
                 open_at=None,
                 continue_reading=None,
                 force_reload=False):
        MainWindow.__init__(self, None)
        self.shutting_down = False
        self.force_reload = force_reload
        connect_lambda(self.book_preparation_started,
                       self,
                       lambda self: self.loading_overlay(
                           _('Preparing book for first read, please wait')),
                       type=Qt.QueuedConnection)
        self.maximized_at_last_fullscreen = False
        self.pending_open_at = open_at
        self.base_window_title = _('E-book viewer')
        self.setWindowTitle(self.base_window_title)
        self.in_full_screen_mode = None
        self.image_popup = ImagePopup(self)
        self.actions_toolbar = at = ActionsToolBar(self)
        at.open_book_at_path.connect(self.ask_for_open)
        self.addToolBar(Qt.LeftToolBarArea, at)
        try:
            os.makedirs(annotations_dir)
        except EnvironmentError:
            pass
        self.current_book_data = {}
        self.book_prepared.connect(self.load_finished,
                                   type=Qt.QueuedConnection)
        self.dock_defs = dock_defs()

        def create_dock(title,
                        name,
                        area,
                        areas=Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea):
            ans = QDockWidget(title, self)
            ans.setObjectName(name)
            self.addDockWidget(area, ans)
            ans.setVisible(False)
            ans.visibilityChanged.connect(self.dock_visibility_changed)
            return ans

        for dock_def in itervalues(self.dock_defs):
            setattr(
                self, '{}_dock'.format(dock_def.name.partition('-')[0]),
                create_dock(dock_def.title, dock_def.name,
                            dock_def.initial_area, dock_def.allowed_areas))

        self.toc_container = w = QWidget(self)
        w.l = QVBoxLayout(w)
        self.toc = TOCView(w)
        self.toc.clicked[QModelIndex].connect(self.toc_clicked)
        self.toc.searched.connect(self.toc_searched)
        self.toc_search = TOCSearch(self.toc, parent=w)
        w.l.addWidget(self.toc), w.l.addWidget(
            self.toc_search), w.l.setContentsMargins(0, 0, 0, 0)
        self.toc_dock.setWidget(w)

        self.search_widget = w = SearchPanel(self)
        w.search_requested.connect(self.start_search)
        self.search_dock.setWidget(w)
        self.search_dock.visibilityChanged.connect(
            self.search_widget.visibility_changed)

        self.lookup_widget = w = Lookup(self)
        self.lookup_dock.visibilityChanged.connect(
            self.lookup_widget.visibility_changed)
        self.lookup_dock.setWidget(w)

        self.bookmarks_widget = w = BookmarkManager(self)
        connect_lambda(
            w.create_requested, self, lambda self: self.web_view.
            get_current_cfi(self.bookmarks_widget.create_new_bookmark))
        w.edited.connect(self.bookmarks_edited)
        w.activated.connect(self.bookmark_activated)
        w.toggle_requested.connect(self.toggle_bookmarks)
        self.bookmarks_dock.setWidget(w)

        self.web_view = WebView(self)
        self.web_view.cfi_changed.connect(self.cfi_changed)
        self.web_view.reload_book.connect(self.reload_book)
        self.web_view.toggle_toc.connect(self.toggle_toc)
        self.web_view.show_search.connect(self.show_search)
        self.web_view.find_next.connect(self.search_widget.find_next_requested)
        self.search_widget.show_search_result.connect(
            self.web_view.show_search_result)
        self.web_view.search_result_not_found.connect(
            self.search_widget.search_result_not_found)
        self.web_view.toggle_bookmarks.connect(self.toggle_bookmarks)
        self.web_view.toggle_inspector.connect(self.toggle_inspector)
        self.web_view.toggle_lookup.connect(self.toggle_lookup)
        self.web_view.quit.connect(self.quit)
        self.web_view.update_current_toc_nodes.connect(
            self.toc.update_current_toc_nodes)
        self.web_view.toggle_full_screen.connect(self.toggle_full_screen)
        self.web_view.ask_for_open.connect(self.ask_for_open,
                                           type=Qt.QueuedConnection)
        self.web_view.selection_changed.connect(
            self.lookup_widget.selected_text_changed, type=Qt.QueuedConnection)
        self.web_view.view_image.connect(self.view_image,
                                         type=Qt.QueuedConnection)
        self.web_view.copy_image.connect(self.copy_image,
                                         type=Qt.QueuedConnection)
        self.web_view.show_loading_message.connect(self.show_loading_message)
        self.web_view.show_error.connect(self.show_error)
        self.web_view.print_book.connect(self.print_book,
                                         type=Qt.QueuedConnection)
        self.web_view.reset_interface.connect(self.reset_interface,
                                              type=Qt.QueuedConnection)
        self.web_view.quit.connect(self.quit, type=Qt.QueuedConnection)
        self.web_view.shortcuts_changed.connect(self.shortcuts_changed)
        self.actions_toolbar.initialize(self.web_view,
                                        self.search_dock.toggleViewAction())
        self.setCentralWidget(self.web_view)
        self.loading_overlay = LoadingOverlay(self)
        self.restore_state()
        self.actions_toolbar.update_visibility()
        self.dock_visibility_changed()
        if continue_reading:
            self.continue_reading()

    def shortcuts_changed(self, smap):
        rmap = defaultdict(list)
        for k, v in iteritems(smap):
            rmap[v].append(k)
        self.actions_toolbar.set_tooltips(rmap)

    def toggle_inspector(self):
        visible = self.inspector_dock.toggleViewAction().isChecked()
        self.inspector_dock.setVisible(not visible)

    def resizeEvent(self, ev):
        self.loading_overlay.resize(self.size())
        return MainWindow.resizeEvent(self, ev)

    # IPC {{{
    def handle_commandline_arg(self, arg):
        if arg:
            if os.path.isfile(arg) and os.access(arg, os.R_OK):
                self.load_ebook(arg)
            else:
                prints('Cannot read from:', arg, file=sys.stderr)

    def another_instance_wants_to_talk(self, msg):
        try:
            path, open_at = msg
        except Exception:
            return
        self.load_ebook(path, open_at=open_at)
        self.raise_()

    # }}}

    # Fullscreen {{{
    def set_full_screen(self, on):
        if on:
            self.maximized_at_last_fullscreen = self.isMaximized()
            if not self.actions_toolbar.visible_in_fullscreen:
                self.actions_toolbar.setVisible(False)
            self.showFullScreen()
        else:
            self.actions_toolbar.update_visibility()
            if self.maximized_at_last_fullscreen:
                self.showMaximized()
            else:
                self.showNormal()

    def changeEvent(self, ev):
        if ev.type() == QEvent.WindowStateChange:
            in_full_screen_mode = self.isFullScreen()
            if self.in_full_screen_mode is None or self.in_full_screen_mode != in_full_screen_mode:
                self.in_full_screen_mode = in_full_screen_mode
                self.web_view.notify_full_screen_state_change(
                    self.in_full_screen_mode)
        return MainWindow.changeEvent(self, ev)

    def toggle_full_screen(self):
        self.set_full_screen(not self.isFullScreen())

    # }}}

    # Docks (ToC, Bookmarks, Lookup, etc.) {{{

    def toggle_toc(self):
        self.toc_dock.setVisible(not self.toc_dock.isVisible())

    def show_search(self):
        self.search_dock.setVisible(True)
        self.search_dock.activateWindow()
        self.search_dock.raise_()
        self.search_widget.focus_input()

    def start_search(self, search_query):
        name = self.web_view.current_content_file
        if name:
            self.search_widget.start_search(search_query, name)
            self.web_view.setFocus(Qt.OtherFocusReason)

    def toggle_bookmarks(self):
        is_visible = self.bookmarks_dock.isVisible()
        self.bookmarks_dock.setVisible(not is_visible)
        if is_visible:
            self.web_view.setFocus(Qt.OtherFocusReason)
        else:
            self.bookmarks_widget.bookmarks_list.setFocus(Qt.OtherFocusReason)

    def toggle_lookup(self):
        self.lookup_dock.setVisible(not self.lookup_dock.isVisible())

    def toc_clicked(self, index):
        item = self.toc_model.itemFromIndex(index)
        self.web_view.goto_toc_node(item.node_id)

    def toc_searched(self, index):
        item = self.toc_model.itemFromIndex(index)
        self.web_view.goto_toc_node(item.node_id)

    def bookmarks_edited(self, bookmarks):
        self.current_book_data['annotations_map']['bookmark'] = bookmarks
        # annotations will be saved in book file on exit
        self.save_annotations(in_book_file=False)

    def bookmark_activated(self, cfi):
        self.web_view.goto_cfi(cfi)

    def view_image(self, name):
        path = get_path_for_name(name)
        if path:
            pmap = QPixmap()
            if pmap.load(path):
                self.image_popup.current_img = pmap
                self.image_popup.current_url = QUrl.fromLocalFile(path)
                self.image_popup()
            else:
                error_dialog(self,
                             _('Invalid image'),
                             _("Failed to load the image {}").format(name),
                             show=True)
        else:
            error_dialog(self,
                         _('Image not found'),
                         _("Failed to find the image {}").format(name),
                         show=True)

    def copy_image(self, name):
        path = get_path_for_name(name)
        if not path:
            return error_dialog(self,
                                _('Image not found'),
                                _("Failed to find the image {}").format(name),
                                show=True)
        try:
            img = image_from_path(path)
        except Exception:
            return error_dialog(self,
                                _('Invalid image'),
                                _("Failed to load the image {}").format(name),
                                show=True)
        url = QUrl.fromLocalFile(path)
        md = QMimeData()
        md.setImageData(img)
        md.setUrls([url])
        QApplication.instance().clipboard().setMimeData(md)

    def dock_visibility_changed(self):
        vmap = {
            dock.objectName().partition('-')[0]:
            dock.toggleViewAction().isChecked()
            for dock in self.dock_widgets
        }
        self.actions_toolbar.update_dock_actions(vmap)

    # }}}

    # Load book {{{

    def show_loading_message(self, msg):
        if msg:
            self.loading_overlay(msg)
        else:
            self.loading_overlay.hide()

    def show_error(self, title, msg, details):
        self.loading_overlay.hide()
        error_dialog(self, title, msg, det_msg=details or None, show=True)

    def print_book(self):
        from .printing import print_book
        print_book(set_book_path.pathtoebook,
                   book_title=self.current_book_data['metadata']['title'],
                   parent=self)

    @property
    def dock_widgets(self):
        return self.findChildren(QDockWidget,
                                 options=Qt.FindDirectChildrenOnly)

    def reset_interface(self):
        for dock in self.dock_widgets:
            dock.setFloating(False)
            area = self.dock_defs[dock.objectName().partition('-')
                                  [0]].initial_area
            self.removeDockWidget(dock)
            self.addDockWidget(area, dock)
            dock.setVisible(False)

        for toolbar in self.findChildren(QToolBar):
            toolbar.setVisible(False)
            self.removeToolBar(toolbar)
            self.addToolBar(Qt.LeftToolBarArea, toolbar)

    def ask_for_open(self, path=None):
        if path is None:
            files = choose_files(self,
                                 'ebook viewer open dialog',
                                 _('Choose e-book'),
                                 [(_('E-books'), available_input_formats())],
                                 all_files=False,
                                 select_only_single_file=True)
            if not files:
                return
            path = files[0]
        self.load_ebook(path)

    def continue_reading(self):
        rl = vprefs['session_data'].get('standalone_recently_opened')
        if rl:
            entry = rl[0]
            self.load_ebook(entry['pathtoebook'])

    def load_ebook(self, pathtoebook, open_at=None, reload_book=False):
        self.web_view.show_home_page_on_ready = False
        if open_at:
            self.pending_open_at = open_at
        self.setWindowTitle(
            _('Loading book') + '… — {}'.format(self.base_window_title))
        self.loading_overlay(_('Loading book, please wait'))
        self.save_annotations()
        self.current_book_data = {}
        self.search_widget.clear_searches()
        t = Thread(name='LoadBook',
                   target=self._load_ebook_worker,
                   args=(pathtoebook, open_at, reload_book
                         or self.force_reload))
        t.daemon = True
        t.start()

    def reload_book(self):
        if self.current_book_data:
            self.load_ebook(self.current_book_data['pathtoebook'],
                            reload_book=True)

    def _load_ebook_worker(self, pathtoebook, open_at, reload_book):
        if DEBUG:
            start_time = monotonic()
        try:
            ans = prepare_book(pathtoebook,
                               force=reload_book,
                               prepare_notify=self.prepare_notify)
        except WorkerError as e:
            self.book_prepared.emit(False, {
                'exception': e,
                'tb': e.orig_tb,
                'pathtoebook': pathtoebook
            })
        except Exception as e:
            import traceback
            self.book_prepared.emit(
                False, {
                    'exception': e,
                    'tb': traceback.format_exc(),
                    'pathtoebook': pathtoebook
                })
        else:
            if DEBUG:
                print('Book prepared in {:.2f} seconds'.format(monotonic() -
                                                               start_time))
            self.book_prepared.emit(
                True, {
                    'base': ans,
                    'pathtoebook': pathtoebook,
                    'open_at': open_at,
                    'reloaded': reload_book
                })

    def prepare_notify(self):
        self.book_preparation_started.emit()

    def load_finished(self, ok, data):
        if self.shutting_down:
            return
        open_at, self.pending_open_at = self.pending_open_at, None
        self.web_view.clear_caches()
        if not ok:
            self.setWindowTitle(self.base_window_title)
            tb = data['tb'].strip()
            tb = re.split(
                r'^calibre\.gui2\.viewer\.convert_book\.ConversionFailure:\s*',
                tb,
                maxsplit=1,
                flags=re.M)[-1]
            last_line = tuple(tb.strip().splitlines())[-1]
            if last_line.startswith('calibre.ebooks.DRMError'):
                DRMErrorMessage(self).exec_()
            else:
                error_dialog(
                    self,
                    _('Loading book failed'),
                    _('Failed to open the book at {0}. Click "Show details" for more info.'
                      ).format(data['pathtoebook']),
                    det_msg=tb,
                    show=True)
            self.loading_overlay.hide()
            self.web_view.show_home_page()
            return
        try:
            set_book_path(data['base'], data['pathtoebook'])
        except Exception:
            if data['reloaded']:
                raise
            self.load_ebook(data['pathtoebook'],
                            open_at=data['open_at'],
                            reload_book=True)
            return
        self.current_book_data = data
        self.current_book_data['annotations_map'] = defaultdict(list)
        self.current_book_data['annotations_path_key'] = path_key(
            data['pathtoebook']) + '.json'
        self.load_book_data()
        self.update_window_title()
        initial_cfi = self.initial_cfi_for_current_book()
        initial_position = {
            'type': 'cfi',
            'data': initial_cfi
        } if initial_cfi else None
        if open_at:
            if open_at.startswith('toc:'):
                initial_toc_node = self.toc_model.node_id_for_text(
                    open_at[len('toc:'):])
                initial_position = {'type': 'toc', 'data': initial_toc_node}
            elif open_at.startswith('toc-href:'):
                initial_toc_node = self.toc_model.node_id_for_href(
                    open_at[len('toc-href:'):], exact=True)
                initial_position = {'type': 'toc', 'data': initial_toc_node}
            elif open_at.startswith('toc-href-contains:'):
                initial_toc_node = self.toc_model.node_id_for_href(
                    open_at[len('toc-href-contains:'):], exact=False)
                initial_position = {'type': 'toc', 'data': initial_toc_node}
            elif open_at.startswith('epubcfi(/'):
                initial_position = {'type': 'cfi', 'data': open_at}
            elif open_at.startswith('ref:'):
                initial_position = {
                    'type': 'ref',
                    'data': open_at[len('ref:'):]
                }
            elif is_float(open_at):
                initial_position = {'type': 'bookpos', 'data': float(open_at)}
        self.web_view.start_book_load(initial_position=initial_position)

    def load_book_data(self):
        self.load_book_annotations()
        path = os.path.join(self.current_book_data['base'],
                            'calibre-book-manifest.json')
        with open(path, 'rb') as f:
            raw = f.read()
        self.current_book_data['manifest'] = manifest = json.loads(raw)
        toc = manifest.get('toc')
        self.toc_model = TOC(toc)
        self.toc.setModel(self.toc_model)
        self.bookmarks_widget.set_bookmarks(
            self.current_book_data['annotations_map']['bookmark'])
        self.current_book_data['metadata'] = set_book_path.parsed_metadata
        self.current_book_data['manifest'] = set_book_path.parsed_manifest

    def load_book_annotations(self):
        amap = self.current_book_data['annotations_map']
        path = os.path.join(self.current_book_data['base'],
                            'calibre-book-annotations.json')
        if os.path.exists(path):
            with open(path, 'rb') as f:
                raw = f.read()
            merge_annotations(json_loads(raw), amap)
        path = os.path.join(annotations_dir,
                            self.current_book_data['annotations_path_key'])
        if os.path.exists(path):
            with open(path, 'rb') as f:
                raw = f.read()
            merge_annotations(parse_annotations(raw), amap)

    def update_window_title(self):
        try:
            title = self.current_book_data['metadata']['title']
        except Exception:
            title = _('Unknown')
        book_format = self.current_book_data['manifest']['book_format']
        title = '{} [{}] — {}'.format(title, book_format,
                                      self.base_window_title)
        self.setWindowTitle(title)

    # }}}

    # CFI management {{{
    def initial_cfi_for_current_book(self):
        lrp = self.current_book_data['annotations_map']['last-read']
        if lrp and get_session_pref('remember_last_read', default=True):
            lrp = lrp[0]
            if lrp['pos_type'] == 'epubcfi':
                return lrp['pos']

    def cfi_changed(self, cfi):
        if not self.current_book_data:
            return
        self.current_book_data['annotations_map']['last-read'] = [{
            'pos':
            cfi,
            'pos_type':
            'epubcfi',
            'timestamp':
            utcnow()
        }]

    # }}}

    # State serialization {{{
    def save_annotations(self, in_book_file=True):
        if not self.current_book_data:
            return
        amap = self.current_book_data['annotations_map']
        annots = as_bytes(serialize_annotations(amap))
        with open(
                os.path.join(annotations_dir,
                             self.current_book_data['annotations_path_key']),
                'wb') as f:
            f.write(annots)
        if in_book_file and self.current_book_data.get(
                'pathtoebook',
                '').lower().endswith('.epub') and get_session_pref(
                    'save_annotations_in_ebook', default=True):
            path = self.current_book_data['pathtoebook']
            if os.access(path, os.W_OK):
                before_stat = os.stat(path)
                save_annots_to_epub(path, annots)
                update_book(path, before_stat,
                            {'calibre-book-annotations.json': annots})

    def save_state(self):
        with vprefs:
            vprefs['main_window_state'] = bytearray(
                self.saveState(self.MAIN_WINDOW_STATE_VERSION))
            vprefs['main_window_geometry'] = bytearray(self.saveGeometry())

    def restore_state(self):
        state = vprefs['main_window_state']
        geom = vprefs['main_window_geometry']
        if geom and get_session_pref('remember_window_geometry',
                                     default=False):
            QApplication.instance().safe_restore_geometry(self, geom)
        if state:
            self.restoreState(state, self.MAIN_WINDOW_STATE_VERSION)
            self.inspector_dock.setVisible(False)

    def quit(self):
        self.close()

    def closeEvent(self, ev):
        self.shutting_down = True
        self.search_widget.shutdown()
        try:
            self.save_annotations()
            self.save_state()
        except Exception:
            import traceback
            traceback.print_exc()
        clean_running_workers()
        return MainWindow.closeEvent(self, ev)
示例#6
0
    def __init__(self, debug_javascript):
        MainWindow.__init__(self, None)
        self.setWindowTitle(_('E-book viewer'))
        self.base_window_title = unicode(self.windowTitle())
        self.setObjectName('EbookViewer')
        self.setWindowIcon(QIcon(I('viewer.png')))
        self.setDockOptions(self.AnimatedDocks | self.AllowTabbedDocks)

        self.centralwidget = c = QWidget(self)
        c.setObjectName('centralwidget')
        self.setCentralWidget(c)
        self.central_layout = cl = QGridLayout(c)
        cl.setSpacing(0)
        c.setLayout(cl), cl.setContentsMargins(0, 0, 0, 0)

        self.view = v = DocumentView(self)
        v.setMinimumSize(100, 100)
        self.view.initialize_view(debug_javascript)
        v.setObjectName('view')
        cl.addWidget(v)

        self.vertical_scrollbar = vs = QScrollBar(c)
        vs.setOrientation(Qt.Vertical), vs.setObjectName("vertical_scrollbar")
        cl.addWidget(vs, 0, 1, 2, 1)

        self.horizontal_scrollbar = hs = QScrollBar(c)
        hs.setOrientation(Qt.Vertical), hs.setObjectName("horizontal_scrollbar")
        cl.addWidget(hs, 1, 0, 1, 1)

        self.tool_bar = tb = QToolBar(self)
        tb.setObjectName('tool_bar'), tb.setIconSize(QSize(32, 32))
        self.addToolBar(Qt.LeftToolBarArea, tb)

        self.tool_bar2 = tb2 = QToolBar(self)
        tb2.setObjectName('tool_bar2')
        self.addToolBar(Qt.TopToolBarArea, tb2)
        self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
        self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)

        self.pos = DoubleSpinBox()
        self.pos.setDecimals(1)
        self.pos.setSuffix('/'+_('Unknown')+'     ')
        self.pos.setMinimum(1.)
        self.tool_bar2.addWidget(self.pos)
        self.tool_bar2.addSeparator()
        self.reference = Reference()
        self.tool_bar2.addWidget(self.reference)
        self.tool_bar2.addSeparator()
        self.search = SearchBox2(self)
        self.search.setMinimumContentsLength(20)
        self.search.initialize('viewer_search_history')
        self.search.setToolTip(_('Search for text in book'))
        self.search.setMinimumWidth(200)
        self.tool_bar2.addWidget(self.search)

        self.toc_dock = d = QDockWidget(_('Table of Contents'), self)
        self.toc = TOCView(self)
        d.setObjectName('toc-dock')
        d.setWidget(self.toc)
        d.close()  # starts out hidden
        self.addDockWidget(Qt.LeftDockWidgetArea, d)
        d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.bookmarks_dock = d = QDockWidget(_('Bookmarks'), self)
        self.bookmarks = BookmarkManager(self)
        d.setObjectName('bookmarks-dock')
        d.setWidget(self.bookmarks)
        d.close()  # starts out hidden
        self.addDockWidget(Qt.RightDockWidgetArea, d)
        d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.create_actions()

        self.metadata = Metadata(self.centralwidget)
        self.history = History(self.action_back, self.action_forward)

        self.full_screen_label = QLabel('''
                <center>
                <h1>%s</h1>
                <h3>%s</h3>
                <h3>%s</h3>
                <h3>%s</h3>
                </center>
                '''%(_('Full screen mode'),
                    _('Right click to show controls'),
                    _('Tap in the left or right page margin to turn pages'),
                    _('Press Esc to quit')),
                    self.centralWidget())
        self.full_screen_label.setVisible(False)
        self.full_screen_label.final_height = 200
        self.full_screen_label.setFocusPolicy(Qt.NoFocus)
        self.full_screen_label.setStyleSheet('''
        QLabel {
            text-align: center;
            background-color: white;
            color: black;
            border-width: 1px;
            border-style: solid;
            border-radius: 20px;
        }
        ''')
        self.clock_label = QLabel('99:99', self.centralWidget())
        self.clock_label.setVisible(False)
        self.clock_label.setFocusPolicy(Qt.NoFocus)
        self.info_label_style = '''
            QLabel {
                text-align: center;
                border-width: 1px;
                border-style: solid;
                border-radius: 8px;
                background-color: %s;
                color: %s;
                font-family: monospace;
                font-size: larger;
                padding: 5px;
        }'''
        self.pos_label = QLabel('2000/4000', self.centralWidget())
        self.pos_label.setVisible(False)
        self.pos_label.setFocusPolicy(Qt.NoFocus)

        self.resize(653, 746)

        if workaround_broken_under_mouse is not None:
            for bar in (self.tool_bar, self.tool_bar2):
                for ac in bar.actions():
                    m = ac.menu()
                    if m is not None:
                        m.aboutToHide.connect(partial(workaround_broken_under_mouse, bar.widgetForAction(ac)))
示例#7
0
    def __init__(self,
                 open_at=None,
                 continue_reading=None,
                 force_reload=False):
        MainWindow.__init__(self, None)
        self.shutting_down = False
        self.force_reload = force_reload
        connect_lambda(self.book_preparation_started,
                       self,
                       lambda self: self.loading_overlay(
                           _('Preparing book for first read, please wait')),
                       type=Qt.QueuedConnection)
        self.maximized_at_last_fullscreen = False
        self.pending_open_at = open_at
        self.base_window_title = _('E-book viewer')
        self.setWindowTitle(self.base_window_title)
        self.in_full_screen_mode = None
        self.image_popup = ImagePopup(self)
        self.actions_toolbar = at = ActionsToolBar(self)
        at.open_book_at_path.connect(self.ask_for_open)
        self.addToolBar(Qt.LeftToolBarArea, at)
        try:
            os.makedirs(annotations_dir)
        except EnvironmentError:
            pass
        self.current_book_data = {}
        self.book_prepared.connect(self.load_finished,
                                   type=Qt.QueuedConnection)
        self.dock_defs = dock_defs()

        def create_dock(title,
                        name,
                        area,
                        areas=Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea):
            ans = QDockWidget(title, self)
            ans.setObjectName(name)
            self.addDockWidget(area, ans)
            ans.setVisible(False)
            ans.visibilityChanged.connect(self.dock_visibility_changed)
            return ans

        for dock_def in itervalues(self.dock_defs):
            setattr(
                self, '{}_dock'.format(dock_def.name.partition('-')[0]),
                create_dock(dock_def.title, dock_def.name,
                            dock_def.initial_area, dock_def.allowed_areas))

        self.toc_container = w = QWidget(self)
        w.l = QVBoxLayout(w)
        self.toc = TOCView(w)
        self.toc.clicked[QModelIndex].connect(self.toc_clicked)
        self.toc.searched.connect(self.toc_searched)
        self.toc_search = TOCSearch(self.toc, parent=w)
        w.l.addWidget(self.toc), w.l.addWidget(
            self.toc_search), w.l.setContentsMargins(0, 0, 0, 0)
        self.toc_dock.setWidget(w)

        self.lookup_widget = w = Lookup(self)
        self.lookup_dock.visibilityChanged.connect(
            self.lookup_widget.visibility_changed)
        self.lookup_dock.setWidget(w)

        self.bookmarks_widget = w = BookmarkManager(self)
        connect_lambda(
            w.create_requested, self, lambda self: self.web_view.
            get_current_cfi(self.bookmarks_widget.create_new_bookmark))
        w.edited.connect(self.bookmarks_edited)
        w.activated.connect(self.bookmark_activated)
        w.toggle_requested.connect(self.toggle_bookmarks)
        self.bookmarks_dock.setWidget(w)

        self.web_view = WebView(self)
        self.web_view.cfi_changed.connect(self.cfi_changed)
        self.web_view.reload_book.connect(self.reload_book)
        self.web_view.toggle_toc.connect(self.toggle_toc)
        self.web_view.toggle_bookmarks.connect(self.toggle_bookmarks)
        self.web_view.toggle_inspector.connect(self.toggle_inspector)
        self.web_view.toggle_lookup.connect(self.toggle_lookup)
        self.web_view.quit.connect(self.quit)
        self.web_view.update_current_toc_nodes.connect(
            self.toc.update_current_toc_nodes)
        self.web_view.toggle_full_screen.connect(self.toggle_full_screen)
        self.web_view.ask_for_open.connect(self.ask_for_open,
                                           type=Qt.QueuedConnection)
        self.web_view.selection_changed.connect(
            self.lookup_widget.selected_text_changed, type=Qt.QueuedConnection)
        self.web_view.view_image.connect(self.view_image,
                                         type=Qt.QueuedConnection)
        self.web_view.copy_image.connect(self.copy_image,
                                         type=Qt.QueuedConnection)
        self.web_view.show_loading_message.connect(self.show_loading_message)
        self.web_view.show_error.connect(self.show_error)
        self.web_view.print_book.connect(self.print_book,
                                         type=Qt.QueuedConnection)
        self.web_view.reset_interface.connect(self.reset_interface,
                                              type=Qt.QueuedConnection)
        self.web_view.shortcuts_changed.connect(self.shortcuts_changed)
        self.actions_toolbar.initialize(self.web_view)
        self.setCentralWidget(self.web_view)
        self.loading_overlay = LoadingOverlay(self)
        self.restore_state()
        self.actions_toolbar.update_visibility()
        self.dock_visibility_changed()
        if continue_reading:
            self.continue_reading()
示例#8
0
文件: ui.py 项目: zyhong/calibre
class EbookViewer(MainWindow):

    msg_from_anotherinstance = pyqtSignal(object)
    book_prepared = pyqtSignal(object, object)
    MAIN_WINDOW_STATE_VERSION = 1

    def __init__(self, open_at=None, continue_reading=None):
        MainWindow.__init__(self, None)
        self.pending_open_at = open_at
        self.base_window_title = _('E-book viewer')
        self.setWindowTitle(self.base_window_title)
        self.in_full_screen_mode = None
        self.image_popup = ImagePopup(self)
        try:
            os.makedirs(annotations_dir)
        except EnvironmentError:
            pass
        self.current_book_data = {}
        self.book_prepared.connect(self.load_finished,
                                   type=Qt.QueuedConnection)
        self.dock_defs = dock_defs()

        def create_dock(title,
                        name,
                        area,
                        areas=Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea):
            ans = QDockWidget(title, self)
            ans.setObjectName(name)
            self.addDockWidget(area, ans)
            ans.setVisible(False)
            return ans

        for dock_def in itervalues(self.dock_defs):
            setattr(
                self, '{}_dock'.format(dock_def.name.partition('-')[0]),
                create_dock(dock_def.title, dock_def.name,
                            dock_def.initial_area, dock_def.allowed_areas))

        self.toc_container = w = QWidget(self)
        w.l = QVBoxLayout(w)
        self.toc = TOCView(w)
        self.toc.clicked[QModelIndex].connect(self.toc_clicked)
        self.toc.searched.connect(self.toc_searched)
        self.toc_search = TOCSearch(self.toc, parent=w)
        w.l.addWidget(self.toc), w.l.addWidget(
            self.toc_search), w.l.setContentsMargins(0, 0, 0, 0)
        self.toc_dock.setWidget(w)

        self.lookup_widget = w = Lookup(self)
        self.lookup_dock.visibilityChanged.connect(
            self.lookup_widget.visibility_changed)
        self.lookup_dock.setWidget(w)

        self.bookmarks_widget = w = BookmarkManager(self)
        connect_lambda(
            w.create_requested, self, lambda self: self.web_view.
            get_current_cfi(self.bookmarks_widget.create_new_bookmark))
        self.bookmarks_widget.edited.connect(self.bookmarks_edited)
        self.bookmarks_widget.activated.connect(self.bookmark_activated)
        self.bookmarks_dock.setWidget(w)

        self.web_view = WebView(self)
        self.web_view.cfi_changed.connect(self.cfi_changed)
        self.web_view.reload_book.connect(self.reload_book)
        self.web_view.toggle_toc.connect(self.toggle_toc)
        self.web_view.toggle_bookmarks.connect(self.toggle_bookmarks)
        self.web_view.toggle_inspector.connect(self.toggle_inspector)
        self.web_view.toggle_lookup.connect(self.toggle_lookup)
        self.web_view.update_current_toc_nodes.connect(
            self.toc.update_current_toc_nodes)
        self.web_view.toggle_full_screen.connect(self.toggle_full_screen)
        self.web_view.ask_for_open.connect(self.ask_for_open,
                                           type=Qt.QueuedConnection)
        self.web_view.selection_changed.connect(
            self.lookup_widget.selected_text_changed, type=Qt.QueuedConnection)
        self.web_view.view_image.connect(self.view_image,
                                         type=Qt.QueuedConnection)
        self.setCentralWidget(self.web_view)
        self.restore_state()
        if continue_reading:
            self.continue_reading()

    def toggle_inspector(self):
        visible = self.inspector_dock.toggleViewAction().isChecked()
        self.inspector_dock.setVisible(not visible)

    # IPC {{{
    def handle_commandline_arg(self, arg):
        if arg:
            if os.path.isfile(arg) and os.access(arg, os.R_OK):
                self.load_ebook(arg)
            else:
                prints('Cannot read from:', arg, file=sys.stderr)

    def another_instance_wants_to_talk(self, msg):
        try:
            path, open_at = msg
        except Exception:
            return
        self.load_ebook(path, open_at=open_at)
        self.raise_()

    # }}}

    # Fullscreen {{{
    def set_full_screen(self, on):
        if on:
            self.showFullScreen()
        else:
            self.showNormal()

    def changeEvent(self, ev):
        if ev.type() == QEvent.WindowStateChange:
            in_full_screen_mode = self.isFullScreen()
            if self.in_full_screen_mode is None or self.in_full_screen_mode != in_full_screen_mode:
                self.in_full_screen_mode = in_full_screen_mode
                self.web_view.notify_full_screen_state_change(
                    self.in_full_screen_mode)
        return MainWindow.changeEvent(self, ev)

    def toggle_full_screen(self):
        self.set_full_screen(not self.isFullScreen())

    # }}}

    # Docks (ToC, Bookmarks, Lookup, etc.) {{{

    def toggle_toc(self):
        self.toc_dock.setVisible(not self.toc_dock.isVisible())

    def toggle_bookmarks(self):
        self.bookmarks_dock.setVisible(not self.bookmarks_dock.isVisible())

    def toggle_lookup(self):
        self.lookup_dock.setVisible(not self.lookup_dock.isVisible())

    def toc_clicked(self, index):
        item = self.toc_model.itemFromIndex(index)
        self.web_view.goto_toc_node(item.node_id)

    def toc_searched(self, index):
        item = self.toc_model.itemFromIndex(index)
        self.web_view.goto_toc_node(item.node_id)

    def bookmarks_edited(self, bookmarks):
        self.current_book_data['annotations_map']['bookmark'] = bookmarks

    def bookmark_activated(self, cfi):
        self.web_view.goto_cfi(cfi)

    def view_image(self, name):
        path = get_path_for_name(name)
        if path:
            pmap = QPixmap()
            if pmap.load(path):
                self.image_popup.current_img = pmap
                self.image_popup.current_url = QUrl.fromLocalFile(path)
                self.image_popup()
            else:
                error_dialog(self,
                             _('Invalid image'),
                             _("Failed to load the image {}").format(name),
                             show=True)
        else:
            error_dialog(self,
                         _('Image not found'),
                         _("Failed to find the image {}").format(name),
                         show=True)

    # }}}

    # Load book {{{

    def ask_for_open(self, path=None):
        if path is None:
            files = choose_files(self,
                                 'ebook viewer open dialog',
                                 _('Choose e-book'),
                                 [(_('E-books'), available_input_formats())],
                                 all_files=False,
                                 select_only_single_file=True)
            if not files:
                return
            path = files[0]
        self.load_ebook(path)

    def continue_reading(self):
        rl = vprefs['session_data'].get('standalone_recently_opened')
        if rl:
            entry = rl[0]
            self.load_ebook(entry['pathtoebook'])

    def load_ebook(self, pathtoebook, open_at=None, reload_book=False):
        if open_at:
            self.pending_open_at = open_at
        self.setWindowTitle(
            _('Loading book') + '… — {}'.format(self.base_window_title))
        self.web_view.show_preparing_message()
        self.save_annotations()
        self.current_book_data = {}
        t = Thread(name='LoadBook',
                   target=self._load_ebook_worker,
                   args=(pathtoebook, open_at, reload_book))
        t.daemon = True
        t.start()

    def reload_book(self):
        if self.current_book_data:
            self.load_ebook(self.current_book_data['pathtoebook'],
                            reload_book=True)

    def _load_ebook_worker(self, pathtoebook, open_at, reload_book):
        try:
            ans = prepare_book(pathtoebook, force=reload_book)
        except WorkerError as e:
            self.book_prepared.emit(False, {
                'exception': e,
                'tb': e.orig_tb,
                'pathtoebook': pathtoebook
            })
        except Exception as e:
            import traceback
            self.book_prepared.emit(
                False, {
                    'exception': e,
                    'tb': traceback.format_exc(),
                    'pathtoebook': pathtoebook
                })
        else:
            self.book_prepared.emit(True, {
                'base': ans,
                'pathtoebook': pathtoebook,
                'open_at': open_at
            })

    def load_finished(self, ok, data):
        open_at, self.pending_open_at = self.pending_open_at, None
        if not ok:
            self.setWindowTitle(self.base_window_title)
            error_dialog(
                self,
                _('Loading book failed'),
                _('Failed to open the book at {0}. Click "Show details" for more info.'
                  ).format(data['pathtoebook']),
                det_msg=data['tb'],
                show=True)
            self.web_view.show_home_page()
            return
        set_book_path(data['base'], data['pathtoebook'])
        self.current_book_data = data
        self.current_book_data['annotations_map'] = defaultdict(list)
        self.current_book_data['annotations_path_key'] = path_key(
            data['pathtoebook']) + '.json'
        self.load_book_data()
        self.update_window_title()
        initial_cfi = self.initial_cfi_for_current_book()
        initial_toc_node = None
        if open_at:
            if open_at.startswith('toc:'):
                initial_toc_node = self.toc_model.node_id_for_text(
                    open_at[len('toc:'):])
            elif open_at.startswith('epubcfi(/'):
                initial_cfi = open_at
        self.web_view.start_book_load(initial_cfi=initial_cfi,
                                      initial_toc_node=initial_toc_node)

    def load_book_data(self):
        self.load_book_annotations()
        path = os.path.join(self.current_book_data['base'],
                            'calibre-book-manifest.json')
        with open(path, 'rb') as f:
            raw = f.read()
        self.current_book_data['manifest'] = manifest = json.loads(raw)
        toc = manifest.get('toc')
        self.toc_model = TOC(toc)
        self.toc.setModel(self.toc_model)
        self.bookmarks_widget.set_bookmarks(
            self.current_book_data['annotations_map']['bookmark'])
        self.current_book_data['metadata'] = set_book_path.parsed_metadata
        self.current_book_data['manifest'] = set_book_path.parsed_manifest

    def load_book_annotations(self):
        amap = self.current_book_data['annotations_map']
        path = os.path.join(self.current_book_data['base'],
                            'calibre-book-annotations.json')
        if os.path.exists(path):
            with open(path, 'rb') as f:
                raw = f.read()
            merge_annotations(json_loads(raw), amap)
        path = os.path.join(annotations_dir,
                            self.current_book_data['annotations_path_key'])
        if os.path.exists(path):
            with open(path, 'rb') as f:
                raw = f.read()
            merge_annotations(parse_annotations(raw), amap)

    def update_window_title(self):
        title = self.current_book_data['metadata']['title']
        book_format = self.current_book_data['manifest']['book_format']
        title = '{} [{}] — {}'.format(title, book_format,
                                      self.base_window_title)
        self.setWindowTitle(title)

    # }}}

    # CFI management {{{
    def initial_cfi_for_current_book(self):
        lrp = self.current_book_data['annotations_map']['last-read']
        if lrp and get_session_pref('remember_last_read', default=True):
            lrp = lrp[0]
            if lrp['pos_type'] == 'epubcfi':
                return lrp['pos']

    def cfi_changed(self, cfi):
        if not self.current_book_data:
            return
        self.current_book_data['annotations_map']['last-read'] = [{
            'pos':
            cfi,
            'pos_type':
            'epubcfi',
            'timestamp':
            utcnow()
        }]

    # }}}

    # State serialization {{{
    def save_annotations(self):
        if not self.current_book_data:
            return
        amap = self.current_book_data['annotations_map']
        annots = as_bytes(serialize_annotations(amap))
        with open(
                os.path.join(annotations_dir,
                             self.current_book_data['annotations_path_key']),
                'wb') as f:
            f.write(annots)
        if self.current_book_data.get(
                'pathtoebook',
                '').lower().endswith('.epub') and get_session_pref(
                    'save_annotations_in_ebook', default=True):
            path = self.current_book_data['pathtoebook']
            if os.access(path, os.W_OK):
                before_stat = os.stat(path)
                save_annots_to_epub(path, annots)
                update_book(path, before_stat,
                            {'calibre-book-annotations.json': annots})

    def save_state(self):
        with vprefs:
            vprefs['main_window_state'] = bytearray(
                self.saveState(self.MAIN_WINDOW_STATE_VERSION))
            vprefs['main_window_geometry'] = bytearray(self.saveGeometry())

    def restore_state(self):
        state = vprefs['main_window_state']
        geom = vprefs['main_window_geometry']
        if geom and get_session_pref('remember_window_geometry',
                                     default=False):
            self.restoreGeometry(geom)
        if state:
            self.restoreState(state, self.MAIN_WINDOW_STATE_VERSION)
            self.inspector_dock.setVisible(False)

    def closeEvent(self, ev):
        try:
            self.save_annotations()
            self.save_state()
        except Exception:
            import traceback
            traceback.print_exc()
        return MainWindow.closeEvent(self, ev)
示例#9
0
    def setupUi(self, EbookViewer):
        EbookViewer.setObjectName(_fromUtf8("EbookViewer"))
        EbookViewer.resize(653, 672)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(_fromUtf8(I("viewer.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        EbookViewer.setWindowIcon(icon)
        self.centralwidget = QtGui.QWidget(EbookViewer)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget)
        self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
        self.splitter = QtGui.QSplitter(self.centralwidget)
        self.splitter.setOrientation(QtCore.Qt.Horizontal)
        self.splitter.setObjectName(_fromUtf8("splitter"))
        self.toc = TOCView(self.splitter)
        self.toc.setObjectName(_fromUtf8("toc"))
        self.frame = QtGui.QFrame(self.splitter)
        self.frame.setFrameShape(QtGui.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtGui.QFrame.Raised)
        self.frame.setObjectName(_fromUtf8("frame"))
        self.gridLayout = QtGui.QGridLayout(self.frame)
        self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
        self.vertical_scrollbar = QtGui.QScrollBar(self.frame)
        self.vertical_scrollbar.setOrientation(QtCore.Qt.Vertical)
        self.vertical_scrollbar.setObjectName(_fromUtf8("vertical_scrollbar"))
        self.gridLayout.addWidget(self.vertical_scrollbar, 1, 1, 1, 1)
        self.horizontal_scrollbar = QtGui.QScrollBar(self.frame)
        self.horizontal_scrollbar.setOrientation(QtCore.Qt.Horizontal)
        self.horizontal_scrollbar.setObjectName(_fromUtf8("horizontal_scrollbar"))
        self.gridLayout.addWidget(self.horizontal_scrollbar, 2, 0, 1, 1)
        self.dictionary_box = QtGui.QFrame(self.frame)
        self.dictionary_box.setFrameShape(QtGui.QFrame.StyledPanel)
        self.dictionary_box.setFrameShadow(QtGui.QFrame.Raised)
        self.dictionary_box.setObjectName(_fromUtf8("dictionary_box"))
        self.horizontalLayout = QtGui.QHBoxLayout(self.dictionary_box)
        self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
        self.dictionary_view = QtWebKit.QWebView(self.dictionary_box)
        self.dictionary_view.setMinimumSize(QtCore.QSize(0, 150))
        self.dictionary_view.setUrl(QtCore.QUrl(_fromUtf8("about:blank")))
        self.dictionary_view.setObjectName(_fromUtf8("dictionary_view"))
        self.horizontalLayout.addWidget(self.dictionary_view)
        self.close_dictionary_view = QtGui.QToolButton(self.dictionary_box)
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap(_fromUtf8(I("window-close.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.close_dictionary_view.setIcon(icon1)
        self.close_dictionary_view.setObjectName(_fromUtf8("close_dictionary_view"))
        self.horizontalLayout.addWidget(self.close_dictionary_view)
        self.gridLayout.addWidget(self.dictionary_box, 3, 0, 1, 2)
        self.view = DocumentView(self.frame)
        self.view.setObjectName(_fromUtf8("view"))
        self.gridLayout.addWidget(self.view, 1, 0, 1, 1)
        self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1)
        EbookViewer.setCentralWidget(self.centralwidget)
        self.tool_bar = QtGui.QToolBar(EbookViewer)
        self.tool_bar.setIconSize(QtCore.QSize(32, 32))
        self.tool_bar.setObjectName(_fromUtf8("tool_bar"))
        EbookViewer.addToolBar(QtCore.Qt.LeftToolBarArea, self.tool_bar)
        self.tool_bar2 = QtGui.QToolBar(EbookViewer)
        self.tool_bar2.setObjectName(_fromUtf8("tool_bar2"))
        EbookViewer.addToolBar(QtCore.Qt.TopToolBarArea, self.tool_bar2)
        self.action_back = QtGui.QAction(EbookViewer)
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap(_fromUtf8(I("back.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_back.setIcon(icon2)
        self.action_back.setObjectName(_fromUtf8("action_back"))
        self.action_forward = QtGui.QAction(EbookViewer)
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap(_fromUtf8(I("forward.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_forward.setIcon(icon3)
        self.action_forward.setObjectName(_fromUtf8("action_forward"))
        self.action_next_page = QtGui.QAction(EbookViewer)
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap(_fromUtf8(I("next.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_next_page.setIcon(icon4)
        self.action_next_page.setObjectName(_fromUtf8("action_next_page"))
        self.action_previous_page = QtGui.QAction(EbookViewer)
        icon5 = QtGui.QIcon()
        icon5.addPixmap(QtGui.QPixmap(_fromUtf8(I("previous.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_previous_page.setIcon(icon5)
        self.action_previous_page.setObjectName(_fromUtf8("action_previous_page"))
        self.action_font_size_larger = QtGui.QAction(EbookViewer)
        icon6 = QtGui.QIcon()
        icon6.addPixmap(QtGui.QPixmap(_fromUtf8(I("font_size_larger.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_font_size_larger.setIcon(icon6)
        self.action_font_size_larger.setObjectName(_fromUtf8("action_font_size_larger"))
        self.action_font_size_smaller = QtGui.QAction(EbookViewer)
        icon7 = QtGui.QIcon()
        icon7.addPixmap(QtGui.QPixmap(_fromUtf8(I("font_size_smaller.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_font_size_smaller.setIcon(icon7)
        self.action_font_size_smaller.setObjectName(_fromUtf8("action_font_size_smaller"))
        self.action_table_of_contents = QtGui.QAction(EbookViewer)
        icon8 = QtGui.QIcon()
        icon8.addPixmap(QtGui.QPixmap(_fromUtf8(I("highlight_only_on.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_table_of_contents.setIcon(icon8)
        self.action_table_of_contents.setObjectName(_fromUtf8("action_table_of_contents"))
        self.action_metadata = QtGui.QAction(EbookViewer)
        icon9 = QtGui.QIcon()
        icon9.addPixmap(QtGui.QPixmap(_fromUtf8(I("dialog_information.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_metadata.setIcon(icon9)
        self.action_metadata.setObjectName(_fromUtf8("action_metadata"))
        self.action_open_ebook = QtGui.QAction(EbookViewer)
        icon10 = QtGui.QIcon()
        icon10.addPixmap(QtGui.QPixmap(_fromUtf8(I("document_open.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_open_ebook.setIcon(icon10)
        self.action_open_ebook.setObjectName(_fromUtf8("action_open_ebook"))
        self.action_find_next = QtGui.QAction(EbookViewer)
        icon11 = QtGui.QIcon()
        icon11.addPixmap(QtGui.QPixmap(_fromUtf8(I("arrow-down.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_find_next.setIcon(icon11)
        self.action_find_next.setObjectName(_fromUtf8("action_find_next"))
        self.action_copy = QtGui.QAction(EbookViewer)
        icon12 = QtGui.QIcon()
        icon12.addPixmap(QtGui.QPixmap(_fromUtf8(I("edit-copy.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_copy.setIcon(icon12)
        self.action_copy.setObjectName(_fromUtf8("action_copy"))
        self.action_preferences = QtGui.QAction(EbookViewer)
        icon13 = QtGui.QIcon()
        icon13.addPixmap(QtGui.QPixmap(_fromUtf8(I("config.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_preferences.setIcon(icon13)
        self.action_preferences.setObjectName(_fromUtf8("action_preferences"))
        self.action_reference_mode = QtGui.QAction(EbookViewer)
        icon14 = QtGui.QIcon()
        icon14.addPixmap(QtGui.QPixmap(_fromUtf8(I("lookfeel.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_reference_mode.setIcon(icon14)
        self.action_reference_mode.setObjectName(_fromUtf8("action_reference_mode"))
        self.action_bookmark = QtGui.QAction(EbookViewer)
        icon15 = QtGui.QIcon()
        icon15.addPixmap(QtGui.QPixmap(_fromUtf8(I("bookmarks.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_bookmark.setIcon(icon15)
        self.action_bookmark.setObjectName(_fromUtf8("action_bookmark"))
        self.action_full_screen = QtGui.QAction(EbookViewer)
        icon16 = QtGui.QIcon()
        icon16.addPixmap(QtGui.QPixmap(_fromUtf8(I("page.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_full_screen.setIcon(icon16)
        self.action_full_screen.setObjectName(_fromUtf8("action_full_screen"))
        self.action_print = QtGui.QAction(EbookViewer)
        icon17 = QtGui.QIcon()
        icon17.addPixmap(QtGui.QPixmap(_fromUtf8(I("print.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_print.setIcon(icon17)
        self.action_print.setObjectName(_fromUtf8("action_print"))
        self.action_find_previous = QtGui.QAction(EbookViewer)
        icon18 = QtGui.QIcon()
        icon18.addPixmap(QtGui.QPixmap(_fromUtf8(I("arrow-up.png"))), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.action_find_previous.setIcon(icon18)
        self.action_find_previous.setObjectName(_fromUtf8("action_find_previous"))
        self.tool_bar.addAction(self.action_back)
        self.tool_bar.addAction(self.action_forward)
        self.tool_bar.addSeparator()
        self.tool_bar.addAction(self.action_open_ebook)
        self.tool_bar.addAction(self.action_copy)
        self.tool_bar.addAction(self.action_font_size_larger)
        self.tool_bar.addAction(self.action_font_size_smaller)
        self.tool_bar.addAction(self.action_table_of_contents)
        self.tool_bar.addAction(self.action_full_screen)
        self.tool_bar.addSeparator()
        self.tool_bar.addAction(self.action_previous_page)
        self.tool_bar.addAction(self.action_next_page)
        self.tool_bar.addSeparator()
        self.tool_bar.addAction(self.action_bookmark)
        self.tool_bar.addAction(self.action_reference_mode)
        self.tool_bar.addSeparator()
        self.tool_bar.addAction(self.action_preferences)
        self.tool_bar.addAction(self.action_metadata)
        self.tool_bar.addSeparator()
        self.tool_bar.addAction(self.action_print)
        self.tool_bar2.addAction(self.action_find_next)
        self.tool_bar2.addAction(self.action_find_previous)

        self.retranslateUi(EbookViewer)
        QtCore.QMetaObject.connectSlotsByName(EbookViewer)
    def load_javascript(self, evaljs):
        '''
        from calibre docs:
        This method is called every time a new HTML document is
        loaded in the viewer. Use it to load javascript libraries
        into the viewer.
        '''

        dlog('loading javascript...')
        for js_file in (
                'annotator-full.1.2.7/annotator-full.min.js',
                'store.js',
        ):
            evaljs(get_resources(js_file))
        dlog('javascript ok')

        # NOTE: EbookViewer.iterator is None upon init.  it doesn't get reified
        # until the book is ready to render (or at least javascript is ready to
        # load). that's why the annotation toc is populated at this late stage.
        if self._view.annotation_toc_model is None and \
                self._view.iterator is not None:

            # QtWidgets.QWidget(self._view)
            ui = self._view

            # not sure if needed?
            splitter = QtWidgets.QSplitter(self._view)
            splitter.setOrientation(QtCore.Qt.Horizontal)
            splitter.setChildrenCollapsible(False)
            splitter.setObjectName('annotation_toc_splitter')
            # not sure if correct:
            # an_list = TOCView(splitter)

            w = ui.annotation_toc_container
            an_list = TOCView(w)
            an_list.setMinimumSize(QtCore.QSize(150, 0))
            an_list.setMinimumWidth(80)
            an_list.setObjectName('annotation_toc')
            an_list.setCursor(Qt.PointingHandCursor)

            self._view.annotation_toc_model = AnnotationTOC(
                ui.iterator.spine, self._view.current_title)
            an_list.setModel(self._view.annotation_toc_model)

            ui.annotation_toc = an_list

            an_list.pressed[QModelIndex].connect(self.annotation_toc_clicked)
            an_list.setCursor(Qt.PointingHandCursor)

            w.l.addWidget(ui.annotation_toc)

            # search box setup
            # ref calibre/gui2/viewer/toc.py
            ui.annotation_toc_search = AnnotationSearchBox(self._view)
            ui.annotation_toc_search.setMinimumContentsLength(15)
            ui.annotation_toc_search.line_edit.setPlaceholderText(
                _('Search Annotations'))
            ui.annotation_toc_search.setToolTip(
                _('Search for text in the Annotations'))
            ui.annotation_toc_search.search.connect(self.do_annotation_search)
            w.l.addWidget(ui.annotation_toc_search)

            w.l.setContentsMargins(0, 0, 0, 0)
    def load_javascript(self, evaljs):
        '''
        from calibre docs:
        This method is called every time a new HTML document is
        loaded in the viewer. Use it to load javascript libraries
        into the viewer.
        '''

        dlog('loading javascript...')
        for js_file in (
                'annotator-full.1.2.7/annotator-full.min.js',
                'store.js',
                ):
            evaljs(get_resources(js_file))
        dlog('javascript ok')

        # NOTE: EbookViewer.iterator is None upon init.  it doesn't get reified
        # until the book is ready to render (or at least javascript is ready to
        # load). that's why the annotation toc is populated at this late stage.
        if self._view.annotation_toc_model is None and \
                self._view.iterator is not None:

            # QtWidgets.QWidget(self._view)
            ui = self._view

            # not sure if needed?
            splitter = QtWidgets.QSplitter(self._view)
            splitter.setOrientation(QtCore.Qt.Horizontal)
            splitter.setChildrenCollapsible(False)
            splitter.setObjectName('annotation_toc_splitter')
            # not sure if correct:
            # an_list = TOCView(splitter)

            w = ui.annotation_toc_container
            an_list = TOCView(w)
            an_list.setMinimumSize(QtCore.QSize(150, 0))
            an_list.setMinimumWidth(80)
            an_list.setObjectName('annotation_toc')
            an_list.setCursor(Qt.PointingHandCursor)

            self._view.annotation_toc_model = AnnotationTOC(ui.iterator.spine, self._view.current_title)
            an_list.setModel(self._view.annotation_toc_model)

            ui.annotation_toc = an_list

            an_list.pressed[QModelIndex].connect(self.annotation_toc_clicked)
            an_list.setCursor(Qt.PointingHandCursor)

            w.l.addWidget(ui.annotation_toc)

            # search box setup
            # ref calibre/gui2/viewer/toc.py
            ui.annotation_toc_search = AnnotationSearchBox(self._view)
            ui.annotation_toc_search.setMinimumContentsLength(15)
            ui.annotation_toc_search.line_edit.setPlaceholderText(_('Search Annotations'))
            ui.annotation_toc_search.setToolTip(_('Search for text in the Annotations'))
            ui.annotation_toc_search.search.connect(self.do_annotation_search)
            w.l.addWidget(ui.annotation_toc_search)

            w.l.setContentsMargins(0, 0, 0, 0)