From 75a851e62b0ff7c0b6c252aa810ac8ab7c4dcebb Mon Sep 17 00:00:00 2001 From: BasioMeusPuga Date: Mon, 27 Nov 2017 02:18:09 +0530 Subject: [PATCH] Improve epub parsing, Table View for main library --- __main__.py | 36 +++++++++--- library.py | 18 +++++- parsers/epub.py | 30 +++++----- resources/mainwindow.py | 27 ++++++--- resources/raw/main.ui | 118 +++++++++++++++++++++++++--------------- sorter.py | 9 ++- widgets.py | 48 +++++++++++++++- 7 files changed, 203 insertions(+), 83 deletions(-) diff --git a/__main__.py b/__main__.py index abfd669..d460cba 100755 --- a/__main__.py +++ b/__main__.py @@ -89,6 +89,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): # Initialize settings dialog self.settings_dialog = SettingsUI() + # Hide or show the main widget of the library + self.tableView.setVisible(False) + # Empty variables that will be infested soon self.last_open_books = None self.last_open_tab = None @@ -176,22 +179,31 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): # TODO # Associate this with the library switcher - library_subclass = QtWidgets.QToolButton() - library_subclass.setIcon(QtGui.QIcon.fromTheme('view-readermode')) - library_subclass.setAutoRaise(True) - library_subclass.setPopupMode(QtWidgets.QToolButton.InstantPopup) + self.library_view_switch = QtWidgets.QToolButton() + self.library_view_switch.setIcon(QtGui.QIcon.fromTheme('view-readermode')) + self.library_view_switch.setAutoRaise(True) + self.library_view_switch.setPopupMode(QtWidgets.QToolButton.InstantPopup) + self.library_view_switch.triggered.connect(self.switch_library_view) - self.tabWidget.tabBar().setTabButton(0, QtWidgets.QTabBar.RightSide, library_subclass) + self.tabWidget.tabBar().setTabButton( + 0, QtWidgets.QTabBar.RightSide, self.library_view_switch) + self.library_view_switch.clicked.connect(self.switch_library_view) self.tabWidget.tabCloseRequested.connect(self.tab_close) + # Init display models + self.lib_ref.generate_model('build') + self.lib_ref.create_tablemodel() # TODO - Make this accompany other proxy model generations + self.lib_ref.create_proxymodel() + # ListView self.listView.setGridSize(QtCore.QSize(175, 240)) self.listView.setMouseTracking(True) self.listView.verticalScrollBar().setSingleStep(7) self.listView.doubleClicked.connect(self.list_doubleclick) self.listView.setItemDelegate(LibraryDelegate(self.temp_dir.path())) - self.lib_ref.generate_model('build') - self.lib_ref.create_proxymodel() + + # TableView + self.tableView.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) # Keyboard shortcuts self.ks_close_tab = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+W'), self) @@ -290,6 +302,16 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): msg_box.show() msg_box.exec_() + def switch_library_view(self): + if self.listView.isVisible(): + self.listView.setVisible(False) + self.tableView.setVisible(True) + self.libraryToolBar.sortingBoxAction.setVisible(False) + else: + self.listView.setVisible(True) + self.tableView.setVisible(False) + self.libraryToolBar.sortingBoxAction.setVisible(True) + def tab_switch(self): if self.tabWidget.currentIndex() == 0: diff --git a/library.py b/library.py index e0193fd..cafee02 100644 --- a/library.py +++ b/library.py @@ -5,7 +5,7 @@ import pickle import database from PyQt5 import QtWidgets, QtGui, QtCore -from widgets import MyAbsModel +from widgets import LibraryItemModel, LibraryTableModel class Library: @@ -13,6 +13,8 @@ class Library: self.parent_window = parent self.view_model = None self.proxy_model = None + self.table_model = None + self.table_rows = [] def generate_model(self, mode, parsed_books=None): # The QlistView widget needs to be populated @@ -20,7 +22,7 @@ class Library: # because I kinda sorta NEED the match() method if mode == 'build': - self.view_model = MyAbsModel() + self.view_model = LibraryItemModel() books = database.DatabaseFunctions( self.parent_window.database_path).fetch_data( @@ -65,7 +67,7 @@ class Library: author = i[2] year = i[3] path = i[4] - tags = i[6] + tags = i[7] cover = i[9] position = i[5] @@ -120,6 +122,16 @@ class Library: item.setIcon(QtGui.QIcon(img_pixmap)) self.view_model.appendRow(item) + # Path is just being sent. It is not being displayed + self.table_rows.append( + (title, author, year, tags, path)) + + def create_tablemodel(self): + table_header = ['Title', 'Author', 'Year', 'Tags'] + self.table_rows.sort(key=lambda x: x[0]) + self.table_model = LibraryTableModel(table_header, self.table_rows) + self.parent_window.tableView.setModel(self.table_model) + def create_proxymodel(self): self.proxy_model = QtCore.QSortFilterProxyModel() self.proxy_model.setSourceModel(self.view_model) diff --git a/parsers/epub.py b/parsers/epub.py index 28c3e38..7529d0f 100644 --- a/parsers/epub.py +++ b/parsers/epub.py @@ -12,6 +12,7 @@ import os import re import zipfile import collections +from urllib.parse import unquote import ebooklib.epub @@ -101,37 +102,36 @@ class ParseEPUB: return None def get_contents(self): - # Extract all contents to a temporary directory - # for relative path lookup voodoo extract_path = os.path.join(self.temp_dir, self.file_md5) zipfile.ZipFile(self.filename).extractall(extract_path) contents = collections.OrderedDict() - def flatten_chapter(toc_element): + def flatten_section(toc_element): output_list = [] for i in toc_element: if isinstance(i, (tuple, list)): - output_list.extend(flatten_chapter(i)) + output_list.extend(flatten_section(i)) else: output_list.append(i) return output_list for i in self.book.toc: if isinstance(i, (tuple, list)): - title = i[0].title - contents[title] = 'Composite Chapter' - # composite_chapter = flatten_chapter(i) - # composite_chapter_content = [] - # for j in composite_chapter: - # href = j.href - # composite_chapter_content.append( - # self.book.get_item_with_href(href).get_content()) + flattened = flatten_section(i) + + for j in flattened: + title = j.title + href = unquote(j.href) + try: + content = self.book.get_item_with_href(href).get_content() + contents[title] = content.decode() + except AttributeError: + pass - # contents[title] = composite_chapter_content else: title = i.title - href = i.href + href = unquote(i.href) try: content = self.book.get_item_with_href(href).get_content() if content: @@ -139,7 +139,7 @@ class ParseEPUB: else: raise AttributeError except AttributeError: - contents[title] = '' + contents[title] = 'Parse Error' # Special settings that have to be returned with the file # Referenced in sorter.py diff --git a/resources/mainwindow.py b/resources/mainwindow.py index 500d2bf..dadf8a5 100644 --- a/resources/mainwindow.py +++ b/resources/mainwindow.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'resources/main.ui' +# Form implementation generated from reading ui file 'raw/main.ui' # -# Created by: PyQt5 UI code generator 5.9.1 +# Created by: PyQt5 UI code generator 5.9.2 # # WARNING! All changes made in this file will be lost! @@ -23,10 +23,8 @@ class Ui_MainWindow(object): self.tabWidget.setObjectName("tabWidget") self.tab = QtWidgets.QWidget() self.tab.setObjectName("tab") - self.gridLayout_2 = QtWidgets.QGridLayout(self.tab) - self.gridLayout_2.setObjectName("gridLayout_2") - self.verticalLayout = QtWidgets.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.tab) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.listView = QtWidgets.QListView(self.tab) self.listView.setFrameShape(QtWidgets.QFrame.NoFrame) self.listView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) @@ -40,8 +38,20 @@ class Ui_MainWindow(object): self.listView.setUniformItemSizes(True) self.listView.setWordWrap(True) self.listView.setObjectName("listView") - self.verticalLayout.addWidget(self.listView) - self.gridLayout_2.addLayout(self.verticalLayout, 0, 0, 1, 1) + self.horizontalLayout_2.addWidget(self.listView) + self.tableView = QtWidgets.QTableView(self.tab) + self.tableView.setFrameShape(QtWidgets.QFrame.NoFrame) + self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed) + self.tableView.setAlternatingRowColors(True) + self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableView.setGridStyle(QtCore.Qt.NoPen) + self.tableView.setSortingEnabled(True) + self.tableView.setWordWrap(False) + self.tableView.setObjectName("tableView") + self.tableView.horizontalHeader().setVisible(True) + self.tableView.verticalHeader().setVisible(False) + self.horizontalLayout_2.addWidget(self.tableView) self.tabWidget.addTab(self.tab, "") self.horizontalLayout.addWidget(self.tabWidget) self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) @@ -64,4 +74,3 @@ class Ui_MainWindow(object): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Lector")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Library")) - diff --git a/resources/raw/main.ui b/resources/raw/main.ui index 8cbc473..f1eac72 100644 --- a/resources/raw/main.ui +++ b/resources/raw/main.ui @@ -29,50 +29,80 @@ Library - - - - - - - QFrame::NoFrame - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::ExtendedSelection - - - QListView::Static - - - true - - - QListView::Fixed - - - QListView::SinglePass - - - 0 - - - QListView::IconMode - - - true - - - true - - - - + + + + + QFrame::NoFrame + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::ExtendedSelection + + + QListView::Static + + + true + + + QListView::Fixed + + + QListView::SinglePass + + + 0 + + + QListView::IconMode + + + true + + + true + + + + + + + QFrame::NoFrame + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed + + + true + + + QAbstractItemView::SelectRows + + + Qt::NoPen + + + true + + + false + + + true + + + false + + diff --git a/sorter.py b/sorter.py index 6531707..4dea22b 100644 --- a/sorter.py +++ b/sorter.py @@ -132,13 +132,18 @@ class BookSorter: # None values are accounted for here book_ref.read_book() if book_ref.book: + title = book_ref.get_title().title() + author = book_ref.get_author() if not author: author = 'Unknown' - year = book_ref.get_year() - if not year: + + try: + year = int(book_ref.get_year()) + except (TypeError, ValueError): year = 9999 + isbn = book_ref.get_isbn() # Different modes require different values diff --git a/widgets.py b/widgets.py index 3dd9180..feb3d9b 100644 --- a/widgets.py +++ b/widgets.py @@ -297,7 +297,7 @@ class LibraryToolBar(QtWidgets.QToolBar): # Add widgets self.addWidget(spacer) - self.addWidget(self.sortingBox) + self.sortingBoxAction = self.addWidget(self.sortingBox) self.addWidget(self.searchBar) @@ -709,7 +709,49 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate): painter.drawPixmap(x_draw, y_draw, read_icon) -class MyAbsModel(QtGui.QStandardItemModel, QtCore.QAbstractItemModel): +class LibraryItemModel(QtGui.QStandardItemModel, QtCore.QAbstractItemModel): def __init__(self, parent=None): # We're using this to be able to access the match() method - super(MyAbsModel, self).__init__(parent) + super(LibraryItemModel, self).__init__(parent) + +class LibraryTableModel(QtCore.QAbstractTableModel): + # TODO + # Speed up sorting + # Associate with a proxy model to enable searching + # Double clicking + # Auto resize with emphasis on Name + # Hide path but send it anyway + + def __init__(self, header_data, display_data, parent=None): + super(LibraryTableModel, self).__init__(parent) + self.header_data = header_data + self.display_data = display_data + + def rowCount(self, parent): + return len(self.display_data) + + def columnCount(self, parent): + return len(self.header_data) + + def data(self, index, role): + if not index.isValid(): + return None + + if role == QtCore.Qt.DisplayRole: + value = self.display_data[index.row()][index.column()] + return value + else: + return QtCore.QVariant() + + def headerData(self, col, orientation, role): + if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: + return self.header_data[col] + return None + + def sort(self, col, order): + # self.emit(SIGNAL("layoutAboutToBeChanged()")) + self.display_data.sort(key=lambda x: x[col]) + if order == QtCore.Qt.DescendingOrder: + self.display_data.sort(key=lambda x: x[col], reverse=True) + + # self.emit(SIGNAL("layoutChanged()"))