Esempio n. 1
0
class Tab(QtWidgets.QWidget):
    def __init__(self, metadata, main_window, parent=None):
        super(Tab, self).__init__(parent)
        self._translate = QtCore.QCoreApplication.translate

        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        self.first_run = True
        self.main_window = main_window
        self.metadata = metadata  # Save progress data into this dictionary
        self.are_we_doing_images_only = self.metadata['images_only']
        self.is_fullscreen = False

        self.masterLayout = QtWidgets.QHBoxLayout(self)
        self.masterLayout.setContentsMargins(0, 0, 0, 0)

        self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime()

        if self.metadata['position']:
            if self.metadata['position']['is_read']:
                self.generate_position(True)
            current_chapter = self.metadata['position']['current_chapter']
        else:
            self.generate_position()
            current_chapter = 1

        chapter_content = self.metadata['content'][current_chapter - 1][1]

        # Create relevant containers
        if not self.metadata['annotations']:
            self.metadata['annotations'] = {}

        # See bookmark availability
        if not self.metadata['bookmarks']:
            self.metadata['bookmarks'] = {}

        # The content display widget is, by default a QTextBrowser.
        # In case the incoming data is only images
        # such as in the case of comic book files,
        # we want a QGraphicsView widget doing all the heavy lifting
        # instead of a QTextBrowser
        if self.are_we_doing_images_only:  # Boolean
            self.contentView = PliantQGraphicsView(self.metadata['path'],
                                                   self.main_window, self)
            self.contentView.loadImage(chapter_content)
        else:
            self.contentView = PliantQTextBrowser(self.main_window, self)

            relative_path_root = os.path.join(self.main_window.temp_dir.path(),
                                              self.metadata['hash'])
            relative_paths = []
            for i in os.walk(relative_path_root):

                # TODO
                # Rename the .css files to something else here and keep
                # a record of them
                # Currently, I'm just removing them for the sake of simplicity
                for j in i[2]:
                    file_extension = os.path.splitext(j)[1]
                    if file_extension == '.css':
                        file_path = os.path.join(i[0], j)
                        os.remove(file_path)

                relative_paths.append(os.path.join(relative_path_root, i[0]))
            self.contentView.setSearchPaths(relative_paths)

            self.contentView.setOpenLinks(
                False)  # TODO Change this when HTML navigation works
            self.contentView.setHtml(chapter_content)
            self.contentView.setReadOnly(True)

            self.hiddenButton = QtWidgets.QToolButton(self)
            self.hiddenButton.setVisible(False)
            self.hiddenButton.clicked.connect(self.set_cursor_position)
            self.hiddenButton.animateClick(50)

        # Load annotations for current content
        self.contentView.common_functions.load_annotations(current_chapter)

        # The following are common to both the text browser and
        # the graphics view
        self.contentView.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.contentView.setObjectName('contentView')
        self.contentView.verticalScrollBar().setSingleStep(
            self.main_window.settings['scroll_speed'])

        if self.main_window.settings['hide_scrollbars']:
            self.contentView.setHorizontalScrollBarPolicy(
                QtCore.Qt.ScrollBarAlwaysOff)
            self.contentView.setVerticalScrollBarPolicy(
                QtCore.Qt.ScrollBarAlwaysOff)
        else:
            self.contentView.setHorizontalScrollBarPolicy(
                QtCore.Qt.ScrollBarAsNeeded)
            self.contentView.setVerticalScrollBarPolicy(
                QtCore.Qt.ScrollBarAsNeeded)

        # Create a common dock for annotations and bookmarks
        # And add a vertical layout to it for requisite widgets
        self.sideDock = PliantDockWidget(self.main_window, False,
                                         self.contentView)
        self.sideDock.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable)
        self.sideDock.setTitleBarWidget(QtWidgets.QWidget())
        self.sideDockTabWidget = QtWidgets.QTabWidget()
        self.sideDock.setWidget(self.sideDockTabWidget)

        # Annotation list view and model
        self.annotationListView = QtWidgets.QListView(self.sideDock)
        self.annotationListView.setResizeMode(QtWidgets.QListWidget.Adjust)
        self.annotationListView.setMaximumWidth(350)
        self.annotationListView.doubleClicked.connect(
            self.contentView.toggle_annotation_mode)
        self.annotationListView.setEditTriggers(
            QtWidgets.QListView.NoEditTriggers)
        annotations_string = self._translate('Tab', 'Annotations')
        self.sideDockTabWidget.addTab(self.annotationListView,
                                      annotations_string)

        self.annotationModel = QtGui.QStandardItemModel(self)
        self.generate_annotation_model()

        # Bookmark tree view and model
        self.bookmarkTreeView = QtWidgets.QTreeView(self.sideDock)
        self.bookmarkTreeView.setHeaderHidden(True)
        self.bookmarkTreeView.setMaximumWidth(350)
        self.bookmarkTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.bookmarkTreeView.customContextMenuRequested.connect(
            self.generate_bookmark_context_menu)
        self.bookmarkTreeView.clicked.connect(self.navigate_to_bookmark)
        bookmarks_string = self._translate('Tab', 'Bookmarks')
        self.sideDockTabWidget.addTab(self.bookmarkTreeView, bookmarks_string)

        self.bookmarkModel = QtGui.QStandardItemModel(self)
        self.bookmarkProxyModel = BookmarkProxyModel(self)
        self.generate_bookmark_model()

        # Create the annotation notes dock
        self.annotationNoteDock = PliantDockWidget(self.main_window, True,
                                                   self.contentView)
        self.annotationNoteDock.setWindowTitle(self._translate('Tab', 'Note'))
        self.annotationNoteDock.setFeatures(
            QtWidgets.QDockWidget.DockWidgetClosable)
        self.annotationNoteDock.hide()

        self.annotationNoteEdit = QtWidgets.QTextEdit(self.annotationNoteDock)
        self.annotationNoteEdit.setMaximumSize(QtCore.QSize(250, 250))
        self.annotationNoteEdit.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.annotationNoteDock.setWidget(self.annotationNoteEdit)

        self.generate_keyboard_shortcuts()

        self.masterLayout.addWidget(self.contentView)
        self.masterLayout.addWidget(self.sideDock)
        self.masterLayout.addWidget(self.annotationNoteDock)

        # The following has to be after the docks are added to the layout
        self.sideDock.setFloating(True)
        self.sideDock.setWindowOpacity(.95)
        self.annotationNoteDock.setFloating(True)
        self.annotationNoteDock.setWindowOpacity(.95)

        title = self.metadata['title']
        if self.main_window.settings['attenuate_titles'] and len(title) > 30:
            title = title[:30] + '...'
        self.main_window.tabWidget.addTab(self, title)

        this_tab_index = self.main_window.tabWidget.indexOf(self)
        cover_icon = QtGui.QPixmap()
        cover_icon.loadFromData(self.metadata['cover'])
        self.main_window.tabWidget.setTabIcon(this_tab_index,
                                              QtGui.QIcon(cover_icon))

        # Hide mouse cursor timer
        self.mouse_hide_timer = QtCore.QTimer()
        self.mouse_hide_timer.setSingleShot(True)
        self.mouse_hide_timer.timeout.connect(self.hide_mouse)

        # Hide the tab bar in case distraction free mode is active
        if not self.main_window.settings['show_bars']:
            self.main_window.tabWidget.tabBar().setVisible(False)

        self.contentView.setFocus()

    def toggle_side_dock(self, tab_required=1):
        if (self.sideDock.isVisible()
                and self.sideDockTabWidget.currentIndex() == tab_required):
            self.sideDock.hide()
        elif not self.sideDock.isVisible():
            self.sideDock.show()

        if tab_required == 0:
            self.sideDockTabWidget.setCurrentIndex(0)
        else:  # Takes care of the action menu as well
            self.sideDockTabWidget.setCurrentIndex(1)

    def update_last_accessed_time(self):
        self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime()

        start_index = self.main_window.lib_ref.libraryModel.index(0, 0)
        matching_item = self.main_window.lib_ref.libraryModel.match(
            start_index, QtCore.Qt.UserRole + 6, self.metadata['hash'], 1,
            QtCore.Qt.MatchExactly)

        try:
            self.main_window.lib_ref.libraryModel.setData(
                matching_item[0], self.metadata['last_accessed'],
                QtCore.Qt.UserRole + 12)
        except IndexError:  # The file has been deleted
            pass

    def set_cursor_position(self, cursor_position=None):
        try:
            required_position = self.metadata['position']['cursor_position']
        except KeyError:
            print('Database: Cursor position error. Recommend retry.')
            return

        if cursor_position:
            required_position = cursor_position

        # This is needed so that the line we want is
        # always at the top of the window
        self.contentView.verticalScrollBar().setValue(
            self.contentView.verticalScrollBar().maximum())

        # textCursor() RETURNS a copy of the textcursor
        cursor = self.contentView.textCursor()
        cursor.setPosition(required_position, QtGui.QTextCursor.MoveAnchor)
        self.contentView.setTextCursor(cursor)
        self.contentView.ensureCursorVisible()

    def generate_position(self, is_read=False):
        total_chapters = len(self.metadata['content'])

        current_chapter = 1
        if is_read:
            current_chapter = total_chapters

        # Generate block count @ time of first read
        # Blocks are indexed from 0 up
        blocks_per_chapter = []
        total_blocks = 0

        if not self.are_we_doing_images_only:
            for i in self.metadata['content']:
                chapter_html = i[1]

                textDocument = QtGui.QTextDocument(None)
                textDocument.setHtml(chapter_html)
                block_count = textDocument.blockCount()

                blocks_per_chapter.append(block_count)
                total_blocks += block_count

        self.metadata['position'] = {
            'current_chapter': current_chapter,
            'total_chapters': total_chapters,
            'blocks_per_chapter': blocks_per_chapter,
            'total_blocks': total_blocks,
            'is_read': is_read,
            'current_block': 0,
            'cursor_position': 0
        }

    def generate_keyboard_shortcuts(self):
        self.ksNextChapter = QtWidgets.QShortcut(QtGui.QKeySequence('Right'),
                                                 self.contentView)
        self.ksNextChapter.setObjectName('nextChapter')
        self.ksNextChapter.activated.connect(self.sneaky_change)

        self.ksPrevChapter = QtWidgets.QShortcut(QtGui.QKeySequence('Left'),
                                                 self.contentView)
        self.ksPrevChapter.setObjectName('prevChapter')
        self.ksPrevChapter.activated.connect(self.sneaky_change)

        self.ksGoFullscreen = QtWidgets.QShortcut(QtGui.QKeySequence('F11'),
                                                  self.contentView)
        self.ksGoFullscreen.activated.connect(self.go_fullscreen)

        self.ksExitFullscreen = QtWidgets.QShortcut(
            QtGui.QKeySequence('Escape'), self.contentView)
        self.ksExitFullscreen.setContext(QtCore.Qt.ApplicationShortcut)
        self.ksExitFullscreen.activated.connect(self.exit_fullscreen)

        self.ksToggleBookMarks = QtWidgets.QShortcut(
            QtGui.QKeySequence('Ctrl+B'), self.contentView)
        self.ksToggleBookMarks.activated.connect(self.toggle_side_dock)

    def go_fullscreen(self):
        # To allow toggles to function
        # properly after the fullscreening

        self.sideDock.hide()
        self.annotationNoteDock.hide()

        if self.contentView.windowState() == QtCore.Qt.WindowFullScreen:
            self.exit_fullscreen()
            return

        if not self.are_we_doing_images_only:
            self.contentView.record_position()

        self.contentView.setWindowFlags(QtCore.Qt.Window)
        self.contentView.setWindowState(QtCore.Qt.WindowFullScreen)
        self.contentView.show()
        self.main_window.hide()

        if not self.are_we_doing_images_only:
            self.hiddenButton.animateClick(100)

        self.is_fullscreen = True

    def exit_fullscreen(self):
        # Intercept escape presses
        for i in (self.annotationNoteDock, self.sideDock):
            if i.isVisible():
                i.setVisible(False)
                return

        # Prevents cursor position change on escape presses
        if self.main_window.isVisible():
            return

        if not self.are_we_doing_images_only:
            self.contentView.record_position()

        self.main_window.show()
        self.contentView.setWindowFlags(QtCore.Qt.Widget)
        self.contentView.setWindowState(QtCore.Qt.WindowNoState)
        self.contentView.show()
        self.is_fullscreen = False

        if not self.are_we_doing_images_only:
            self.hiddenButton.animateClick(100)

        # Hide the view modification buttons in case they're visible
        self.main_window.bookToolBar.customize_view_off()

        # Exit distraction free mode too
        if not self.main_window.settings['show_bars']:
            self.main_window.toggle_distraction_free()

        self.contentView.setFocus()

    def change_chapter_tocBox(self):
        chapter_number = self.main_window.bookToolBar.tocBox.currentIndex()
        required_content = self.metadata['content'][chapter_number][1]

        if self.are_we_doing_images_only:
            self.contentView.loadImage(required_content)
        else:
            self.contentView.clear()
            self.contentView.setHtml(required_content)

        self.contentView.common_functions.load_annotations(chapter_number + 1)

    def format_view(self, font, font_size, foreground, background, padding,
                    line_spacing, text_alignment):

        if self.are_we_doing_images_only:
            # Tab color does not need to be set separately in case
            # no padding is set for the viewport of a QGraphicsView
            # and image resizing in done in the pixmap
            my_qbrush = QtGui.QBrush(QtCore.Qt.SolidPattern)
            my_qbrush.setColor(background)
            self.contentView.setBackgroundBrush(my_qbrush)
            self.contentView.resizeEvent()

        else:
            self.contentView.setStyleSheet(
                "QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}"
                .format(font, font_size, foreground.name(), background.name()))

            # Line spacing
            # Set line spacing per a block format
            # This is proportional line spacing so assume a divisor of 100
            block_format = QtGui.QTextBlockFormat()
            block_format.setLineHeight(
                line_spacing, QtGui.QTextBlockFormat.ProportionalHeight)

            block_format.setTextIndent(50)

            # Give options for alignment
            alignment_dict = {
                'left': QtCore.Qt.AlignLeft,
                'right': QtCore.Qt.AlignRight,
                'center': QtCore.Qt.AlignCenter,
                'justify': QtCore.Qt.AlignJustify
            }

            current_index = self.main_window.bookToolBar.tocBox.currentIndex()
            if current_index == 0:
                block_format.setAlignment(QtCore.Qt.AlignVCenter
                                          | QtCore.Qt.AlignHCenter)
            else:
                block_format.setAlignment(alignment_dict[text_alignment])

            # Also for padding
            # Using setViewPortMargins for this disables scrolling in the margins
            block_format.setLeftMargin(padding)
            block_format.setRightMargin(padding)

            this_cursor = self.contentView.textCursor()
            this_cursor.movePosition(QtGui.QTextCursor.Start, 0, 1)

            # Iterate over the entire document block by block
            # The document ends when the cursor position can no longer be incremented
            while True:
                old_position = this_cursor.position()
                this_cursor.mergeBlockFormat(block_format)
                this_cursor.movePosition(QtGui.QTextCursor.NextBlock, 0, 1)
                new_position = this_cursor.position()
                if old_position == new_position:
                    break

    def generate_annotation_model(self):
        saved_annotations = self.main_window.settings['annotations']

        if not saved_annotations:
            return

        def add_to_model(annotation):
            item = QtGui.QStandardItem()
            item.setText(annotation['name'])
            item.setData(annotation, QtCore.Qt.UserRole)
            self.annotationModel.appendRow(item)

        # Prevent annotation mixup
        for i in saved_annotations:
            if self.are_we_doing_images_only and i['applicable_to'] == 'images':
                add_to_model(i)
            elif not self.are_we_doing_images_only and i[
                    'applicable_to'] == 'text':
                add_to_model(i)

        self.annotationListView.setModel(self.annotationModel)

    def add_bookmark(self):
        identifier = uuid.uuid4().hex[:10]
        description = self._translate('Tab', 'New bookmark')

        if self.are_we_doing_images_only:
            chapter = self.metadata['position']['current_chapter']
            cursor_position = 0
        else:
            chapter, cursor_position = self.contentView.record_position(True)

        self.metadata['bookmarks'][identifier] = {
            'chapter': chapter,
            'cursor_position': cursor_position,
            'description': description
        }

        self.sideDock.setVisible(True)
        self.add_bookmark_to_model(description, chapter, cursor_position,
                                   identifier, True)

    def add_bookmark_to_model(self,
                              description,
                              chapter,
                              cursor_position,
                              identifier,
                              new_bookmark=False):
        def edit_new_bookmark(parent_item):
            new_child = parent_item.child(parent_item.rowCount() - 1, 0)
            source_index = self.bookmarkModel.indexFromItem(new_child)
            edit_index = self.bookmarkTreeView.model().mapFromSource(
                source_index)
            self.sideDock.activateWindow()
            self.bookmarkTreeView.setFocus()
            self.bookmarkTreeView.setCurrentIndex(edit_index)
            self.bookmarkTreeView.edit(edit_index)

        bookmark = QtGui.QStandardItem()

        bookmark.setData(False, QtCore.Qt.UserRole + 10)  # Is Parent
        bookmark.setData(chapter, QtCore.Qt.UserRole)  # Chapter name
        bookmark.setData(cursor_position,
                         QtCore.Qt.UserRole + 1)  # Cursor Position
        bookmark.setData(identifier, QtCore.Qt.UserRole + 2)  # Identifier
        bookmark.setData(description, QtCore.Qt.DisplayRole)  # Description

        for i in range(self.bookmarkModel.rowCount()):
            parentIndex = self.bookmarkModel.index(i, 0)
            parent_chapter = parentIndex.data(QtCore.Qt.UserRole)
            if parent_chapter == chapter:
                bookmarkParent = self.bookmarkModel.itemFromIndex(parentIndex)
                bookmarkParent.appendRow(bookmark)
                if new_bookmark:
                    edit_new_bookmark(bookmarkParent)

                return

        # In case no parent item exists
        bookmarkParent = QtGui.QStandardItem()
        bookmarkParent.setData(True, QtCore.Qt.UserRole + 10)  # Is Parent
        bookmarkParent.setFlags(bookmarkParent.flags()
                                & ~QtCore.Qt.ItemIsEditable)  # Is Editable
        chapter_name = self.metadata['content'][chapter - 1][0]  # Chapter Name
        bookmarkParent.setData(chapter_name, QtCore.Qt.DisplayRole)
        bookmarkParent.setData(chapter, QtCore.Qt.UserRole)  # Chapter Number

        bookmarkParent.appendRow(bookmark)
        self.bookmarkModel.appendRow(bookmarkParent)
        if new_bookmark:
            edit_new_bookmark(bookmarkParent)

    def navigate_to_bookmark(self, index):
        if not index.isValid():
            return

        is_parent = self.bookmarkProxyModel.data(index,
                                                 QtCore.Qt.UserRole + 10)
        if is_parent:
            chapter_number = self.bookmarkProxyModel.data(
                index, QtCore.Qt.UserRole)
            self.main_window.bookToolBar.tocBox.setCurrentIndex(
                chapter_number - 1)
            return

        chapter = self.bookmarkProxyModel.data(index, QtCore.Qt.UserRole)
        cursor_position = self.bookmarkProxyModel.data(index,
                                                       QtCore.Qt.UserRole + 1)

        self.main_window.bookToolBar.tocBox.setCurrentIndex(chapter - 1)
        if not self.are_we_doing_images_only:
            self.set_cursor_position(cursor_position)

    def generate_bookmark_model(self):
        self.bookmarkModel = QtGui.QStandardItemModel(self)

        if self.main_window.settings['toc_with_bookmarks']:
            for chapter_number, i in enumerate(self.metadata['content']):
                chapterItem = QtGui.QStandardItem()
                chapterItem.setData(i[0],
                                    QtCore.Qt.DisplayRole)  # Display name
                chapterItem.setData(chapter_number + 1,
                                    QtCore.Qt.UserRole)  # Chapter Number
                chapterItem.setData(True, QtCore.Qt.UserRole + 10)  # Is Parent
                chapterItem.setFlags(
                    chapterItem.flags()
                    & ~QtCore.Qt.ItemIsEditable)  # Is Editable
                self.bookmarkModel.appendRow(chapterItem)

        for i in self.metadata['bookmarks'].items():
            description = i[1]['description']
            chapter = i[1]['chapter']
            cursor_position = i[1]['cursor_position']
            identifier = i[0]
            self.add_bookmark_to_model(description, chapter, cursor_position,
                                       identifier)

        self.generate_bookmark_proxy_model()

    def generate_bookmark_proxy_model(self):
        self.bookmarkProxyModel.setSourceModel(self.bookmarkModel)
        self.bookmarkProxyModel.setSortCaseSensitivity(False)
        self.bookmarkProxyModel.setSortRole(QtCore.Qt.UserRole)
        self.bookmarkProxyModel.sort(0)
        self.bookmarkTreeView.setModel(self.bookmarkProxyModel)

    def update_bookmark_proxy_model(self):
        # TODO
        # This isn't being called currently
        # See if there's any rationale for keeping it / removing it

        self.bookmarkProxyModel.invalidateFilter()
        self.bookmarkProxyModel.setFilterParams(
            self.main_window.bookToolBar.searchBar.text())
        self.bookmarkProxyModel.setFilterFixedString(
            self.main_window.bookToolBar.searchBar.text())

    def generate_bookmark_context_menu(self, position):
        index = self.bookmarkTreeView.indexAt(position)
        if not index.isValid():
            return

        is_parent = self.bookmarkProxyModel.data(index,
                                                 QtCore.Qt.UserRole + 10)
        if is_parent:
            return

        bookmarkMenu = QtWidgets.QMenu()
        editAction = bookmarkMenu.addAction(
            self.main_window.QImageFactory.get_image('edit-rename'),
            self._translate('Tab', 'Edit'))
        deleteAction = bookmarkMenu.addAction(
            self.main_window.QImageFactory.get_image('trash-empty'),
            self._translate('Tab', 'Delete'))

        action = bookmarkMenu.exec_(
            self.bookmarkTreeView.mapToGlobal(position))

        if action == editAction:
            self.bookmarkTreeView.edit(index)

        if action == deleteAction:
            child_index = self.bookmarkProxyModel.mapToSource(index)
            parent_index = child_index.parent()
            child_rows = self.bookmarkModel.itemFromIndex(
                parent_index).rowCount()
            delete_uuid = self.bookmarkModel.data(child_index,
                                                  QtCore.Qt.UserRole + 2)

            self.metadata['bookmarks'].pop(delete_uuid)

            self.bookmarkModel.removeRow(child_index.row(),
                                         child_index.parent())
            if child_rows == 1:
                self.bookmarkModel.removeRow(parent_index.row())

    def hide_mouse(self):
        self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor)

    def sneaky_change(self):
        direction = -1
        if self.sender().objectName() == 'nextChapter':
            direction = 1

        self.contentView.common_functions.change_chapter(direction, True)

    def sneaky_exit(self):
        self.contentView.hide()
        self.main_window.closeEvent()
Esempio n. 2
0
class Tab(QtWidgets.QWidget):
    def __init__(self, metadata, parent=None):
        super(Tab, self).__init__(parent)
        self._translate = QtCore.QCoreApplication.translate

        self.parent = parent
        self.metadata = metadata  # Save progress data into this dictionary
        self.are_we_doing_images_only = self.metadata['images_only']
        self.main_window = self.window()

        self.masterLayout = QtWidgets.QHBoxLayout(self)
        self.horzLayout = QtWidgets.QSplitter(self)
        self.horzLayout.setOrientation(QtCore.Qt.Horizontal)
        self.masterLayout.addWidget(self.horzLayout)

        self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime()

        if self.metadata['position']:
            if self.metadata['position']['is_read']:
                self.generate_position(True)
            current_chapter = self.metadata['position']['current_chapter']
        else:
            self.generate_position()
            current_chapter = 1

        chapter_content = self.metadata['content'][current_chapter - 1][1]

        # The content display widget is, by default a QTextBrowser.
        # In case the incoming data is only images
        # such as in the case of comic book files,
        # we want a QGraphicsView widget doing all the heavy lifting
        # instead of a QTextBrowser
        if self.are_we_doing_images_only:  # Boolean
            self.contentView = PliantQGraphicsView(self.metadata['path'],
                                                   self.main_window, self)
            self.contentView.loadImage(chapter_content)
        else:
            self.contentView = PliantQTextBrowser(self.main_window, self)

            relative_path_root = os.path.join(self.main_window.temp_dir.path(),
                                              self.metadata['hash'])
            relative_paths = []
            for i in os.walk(relative_path_root):

                # TODO
                # Rename the .css files to something else here and keep
                # a record of them
                # Currently, I'm just removing them for the sake of simplicity
                for j in i[2]:
                    file_extension = os.path.splitext(j)[1]
                    if file_extension == '.css':
                        file_path = os.path.join(i[0], j)
                        os.remove(file_path)

                relative_paths.append(os.path.join(relative_path_root, i[0]))
            self.contentView.setSearchPaths(relative_paths)

            self.contentView.setOpenLinks(
                False)  # TODO Change this when HTML navigation works
            self.contentView.setHtml(chapter_content)
            self.contentView.setReadOnly(True)

            tempHiddenButton = QtWidgets.QToolButton(self)
            tempHiddenButton.setVisible(False)
            tempHiddenButton.clicked.connect(self.set_scroll_value)
            tempHiddenButton.animateClick(100)

        # The following are common to both the text browser and
        # the graphics view
        self.contentView.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.contentView.setObjectName('contentView')
        self.contentView.verticalScrollBar().setSingleStep(7)
        self.contentView.setHorizontalScrollBarPolicy(
            QtCore.Qt.ScrollBarAsNeeded)

        # See bookmark availability
        if not self.metadata['bookmarks']:
            self.metadata['bookmarks'] = {}

        # Create the dock widget for context specific display
        self.dockWidget = PliantDockWidget(self)
        self.dockWidget.setWindowTitle(self._translate('Tab', 'Bookmarks'))
        self.dockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable)
        self.dockWidget.setFloating(False)
        self.dockWidget.hide()

        self.dockListView = QtWidgets.QListView(self.dockWidget)
        self.dockListView.setResizeMode(QtWidgets.QListWidget.Adjust)
        self.dockListView.setMaximumWidth(350)
        self.dockListView.setItemDelegate(BookmarkDelegate(self.dockListView))
        self.dockListView.setUniformItemSizes(True)
        self.dockListView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.dockListView.customContextMenuRequested.connect(
            self.generate_bookmark_context_menu)
        self.dockListView.clicked.connect(self.navigate_to_bookmark)
        self.dockWidget.setWidget(self.dockListView)

        self.bookmark_model = QtGui.QStandardItemModel(self)
        self.proxy_model = BookmarkProxyModel(self)
        self.generate_bookmark_model()

        self.generate_keyboard_shortcuts()

        self.horzLayout.addWidget(self.contentView)
        self.horzLayout.addWidget(self.dockWidget)
        title = self.metadata['title']
        self.parent.addTab(self, title)

        # Hide mouse cursor timer
        self.mouse_hide_timer = QtCore.QTimer()
        self.mouse_hide_timer.setSingleShot(True)
        self.mouse_hide_timer.timeout.connect(self.hide_mouse)

        self.contentView.setFocus()

    def update_last_accessed_time(self):
        self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime()

        start_index = self.main_window.lib_ref.view_model.index(0, 0)
        matching_item = self.main_window.lib_ref.view_model.match(
            start_index, QtCore.Qt.UserRole + 6, self.metadata['hash'], 1,
            QtCore.Qt.MatchExactly)

        try:
            self.main_window.lib_ref.view_model.setData(
                matching_item[0], self.metadata['last_accessed'],
                QtCore.Qt.UserRole + 12)
        except IndexError:  # The file has been deleted
            pass

    def set_scroll_value(self, switch_widgets=True, search_data=None):
        # TODO
        # Bookmark navigation does not work in case 2 entries in the same
        # chapter are clicked successively
        # It plain refuses to work other times

        if self.sender().objectName() == 'tabWidget':
            return

        if switch_widgets:
            previous_widget = self.main_window.tabWidget.currentWidget()
            self.main_window.tabWidget.setCurrentWidget(self)

        scroll_value = self.metadata['position']['scroll_value']
        if search_data:
            scroll_value = search_data[0]

        # Scroll a little ahead
        # This avoids confusion with potentially duplicate phrases
        # And the found result is at the top of the window
        scroll_position = scroll_value * self.contentView.verticalScrollBar(
        ).maximum()
        self.contentView.verticalScrollBar().setValue(scroll_position * 1.02)

        try:
            search_text = self.metadata['position']['last_visible_text']
            if search_data:
                search_text = search_data[1]

            if search_text:
                find_backward = False

                find_forward = self.contentView.find(search_text)
                if not find_forward:
                    find_backward = self.contentView.find(
                        search_text, QtGui.QTextDocument.FindBackward)

                if find_backward:
                    current_scroll_position = self.contentView.verticalScrollBar(
                    ).value()
                    new_scroll_position = current_scroll_position * .98
                    self.contentView.verticalScrollBar().setValue(
                        new_scroll_position)

                text_cursor = self.contentView.textCursor()
                text_cursor.clearSelection()
                self.contentView.setTextCursor(text_cursor)
        except KeyError:
            pass

        if switch_widgets:
            self.main_window.tabWidget.setCurrentWidget(previous_widget)

    def generate_position(self, is_read=False):
        total_chapters = len(self.metadata['content'])

        current_chapter = 1
        scroll_value = 0
        if is_read:
            current_chapter = total_chapters
            scroll_value = 1

        # TODO
        # Use this to generate position

        # Generate block count @ time of first read
        # Blocks are indexed from 0 up
        blocks_per_chapter = []
        total_blocks = 0

        if not self.are_we_doing_images_only:
            for i in self.metadata['content']:
                chapter_html = i[1]

                textDocument = QtGui.QTextDocument(None)
                textDocument.setHtml(chapter_html)
                block_count = textDocument.blockCount()

                blocks_per_chapter.append(block_count)
                total_blocks += block_count

        self.metadata['position'] = {
            'current_chapter': current_chapter,
            'total_chapters': total_chapters,
            'blocks_per_chapter': blocks_per_chapter,
            'total_blocks': total_blocks,
            'scroll_value': scroll_value,
            'last_visible_text': None,
            'is_read': is_read
        }

    def generate_keyboard_shortcuts(self):
        self.next_chapter = QtWidgets.QShortcut(QtGui.QKeySequence('Right'),
                                                self.contentView)
        self.next_chapter.setObjectName('nextChapter')
        self.next_chapter.activated.connect(self.sneaky_change)

        self.prev_chapter = QtWidgets.QShortcut(QtGui.QKeySequence('Left'),
                                                self.contentView)
        self.prev_chapter.setObjectName('prevChapter')
        self.prev_chapter.activated.connect(self.sneaky_change)

        self.go_fs = QtWidgets.QShortcut(QtGui.QKeySequence('F11'),
                                         self.contentView)
        self.go_fs.activated.connect(self.go_fullscreen)

        self.exit_fs = QtWidgets.QShortcut(QtGui.QKeySequence('Escape'),
                                           self.contentView)
        self.exit_fs.setContext(QtCore.Qt.ApplicationShortcut)
        self.exit_fs.activated.connect(self.exit_fullscreen)

    def go_fullscreen(self):
        if self.contentView.windowState() == QtCore.Qt.WindowFullScreen:
            self.exit_fullscreen()
            return

        self.contentView.setWindowFlags(QtCore.Qt.Window)
        self.contentView.setWindowState(QtCore.Qt.WindowFullScreen)
        self.contentView.show()
        self.main_window.hide()

    def exit_fullscreen(self):
        self.main_window.show()
        self.contentView.setWindowFlags(QtCore.Qt.Widget)
        self.contentView.setWindowState(QtCore.Qt.WindowNoState)
        self.contentView.show()

        # Hide the view modification buttons in case they're visible
        self.main_window.bookToolBar.customize_view_off()

    def change_chapter_tocBox(self):
        chapter_number = self.main_window.bookToolBar.tocBox.currentIndex()
        required_content = self.metadata['content'][chapter_number][1]

        if self.are_we_doing_images_only:
            self.contentView.loadImage(required_content)
        else:
            self.contentView.clear()
            self.contentView.setHtml(required_content)

    def format_view(self, font, font_size, foreground, background, padding,
                    line_spacing, text_alignment):

        if self.are_we_doing_images_only:
            # Tab color does not need to be set separately in case
            # no padding is set for the viewport of a QGraphicsView
            # and image resizing in done in the pixmap
            my_qbrush = QtGui.QBrush(QtCore.Qt.SolidPattern)
            my_qbrush.setColor(background)
            self.contentView.setBackgroundBrush(my_qbrush)
            self.contentView.resizeEvent()

        else:
            self.contentView.setStyleSheet(
                "QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}"
                .format(font, font_size, foreground.name(), background.name()))

            # Line spacing
            # Set line spacing per a block format
            # This is proportional line spacing so assume a divisor of 100
            block_format = QtGui.QTextBlockFormat()
            block_format.setLineHeight(
                line_spacing, QtGui.QTextBlockFormat.ProportionalHeight)

            block_format.setTextIndent(50)

            # Give options for alignment
            alignment_dict = {
                'left': QtCore.Qt.AlignLeft,
                'right': QtCore.Qt.AlignRight,
                'center': QtCore.Qt.AlignCenter,
                'justify': QtCore.Qt.AlignJustify
            }

            current_index = self.main_window.bookToolBar.tocBox.currentIndex()
            if current_index == 0:
                block_format.setAlignment(QtCore.Qt.AlignVCenter
                                          | QtCore.Qt.AlignHCenter)
            else:
                block_format.setAlignment(alignment_dict[text_alignment])

            # Also for padding
            # Using setViewPortMargins for this disables scrolling in the margins
            block_format.setLeftMargin(padding)
            block_format.setRightMargin(padding)

            this_cursor = self.contentView.textCursor()
            this_cursor.movePosition(QtGui.QTextCursor.Start, 0, 1)

            # Iterate over the entire document block by block
            # The document ends when the cursor position can no longer be incremented
            while True:
                old_position = this_cursor.position()
                this_cursor.mergeBlockFormat(block_format)
                this_cursor.movePosition(QtGui.QTextCursor.NextBlock, 0, 1)
                new_position = this_cursor.position()
                if old_position == new_position:
                    break

    def toggle_bookmarks(self):
        if self.dockWidget.isVisible():
            self.dockWidget.hide()
        else:
            self.dockWidget.show()

    def add_bookmark(self):
        # TODO
        # Start dockListView.edit(index) when something new is added

        identifier = uuid.uuid4().hex[:10]
        description = self._translate('Tab', 'New bookmark')

        if self.are_we_doing_images_only:
            chapter = self.metadata['position']['current_chapter']
            search_data = (0, None)
        else:
            chapter, scroll_position, visible_text = self.contentView.record_scroll_position(
                True)
            search_data = (scroll_position, visible_text)

        self.metadata['bookmarks'][identifier] = {
            'chapter': chapter,
            'search_data': search_data,
            'description': description
        }

        self.add_bookmark_to_model(description, chapter, search_data,
                                   identifier)
        self.dockWidget.setVisible(True)

    def add_bookmark_to_model(self, description, chapter, search_data,
                              identifier):
        bookmark = QtGui.QStandardItem()
        bookmark.setData(description, QtCore.Qt.DisplayRole)

        bookmark.setData(chapter, QtCore.Qt.UserRole)
        bookmark.setData(search_data, QtCore.Qt.UserRole + 1)
        bookmark.setData(identifier, QtCore.Qt.UserRole + 2)

        self.bookmark_model.appendRow(bookmark)
        self.update_bookmark_proxy_model()

    def navigate_to_bookmark(self, index):
        if not index.isValid():
            return

        chapter = self.proxy_model.data(index, QtCore.Qt.UserRole)
        search_data = self.proxy_model.data(index, QtCore.Qt.UserRole + 1)

        self.main_window.bookToolBar.tocBox.setCurrentIndex(chapter - 1)
        if not self.are_we_doing_images_only:
            self.set_scroll_value(False, search_data)

    def generate_bookmark_model(self):
        # TODO
        # Sorting is not working correctly

        for i in self.metadata['bookmarks'].items():
            self.add_bookmark_to_model(i[1]['description'], i[1]['chapter'],
                                       i[1]['search_data'], i[0])

        self.generate_bookmark_proxy_model()

    def generate_bookmark_proxy_model(self):
        self.proxy_model.setSourceModel(self.bookmark_model)
        self.proxy_model.setSortCaseSensitivity(False)
        self.proxy_model.setSortRole(QtCore.Qt.UserRole)
        self.dockListView.setModel(self.proxy_model)

    def update_bookmark_proxy_model(self):
        self.proxy_model.invalidateFilter()
        self.proxy_model.setFilterParams(
            self.main_window.bookToolBar.searchBar.text())
        self.proxy_model.setFilterFixedString(
            self.main_window.bookToolBar.searchBar.text())

    def generate_bookmark_context_menu(self, position):
        index = self.dockListView.indexAt(position)
        if not index.isValid():
            return

        bookmark_menu = QtWidgets.QMenu()
        editAction = bookmark_menu.addAction(
            self.main_window.QImageFactory.get_image('edit-rename'),
            self._translate('Tab', 'Edit'))
        deleteAction = bookmark_menu.addAction(
            self.main_window.QImageFactory.get_image('trash-empty'),
            self._translate('Tab', 'Delete'))

        action = bookmark_menu.exec_(self.dockListView.mapToGlobal(position))

        if action == editAction:
            self.dockListView.edit(index)

        if action == deleteAction:
            row = index.row()
            delete_uuid = self.bookmark_model.item(row).data(
                QtCore.Qt.UserRole + 2)

            self.metadata['bookmarks'].pop(delete_uuid)
            self.bookmark_model.removeRow(index.row())

    def hide_mouse(self):
        self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor)

    def sneaky_change(self):
        direction = -1
        if self.sender().objectName() == 'nextChapter':
            direction = 1

        self.contentView.common_functions.change_chapter(direction, True)

    def sneaky_exit(self):
        self.contentView.hide()
        self.main_window.closeEvent()