diff --git a/__main__.py b/__main__.py index 38fd42a..9682294 100755 --- a/__main__.py +++ b/__main__.py @@ -10,6 +10,7 @@ ✓ Shift focus to the tab that has the book open ✓ Search bar in toolbar ✓ Drop down for TOC (book view) + ✓ Image reflow mobi support txt, doc support @@ -37,6 +38,7 @@ import database import book_parser from widgets import LibraryToolBar, BookToolBar, Tab +from main_subclasses import Settings, Library class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): @@ -87,11 +89,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): # ListView # self.listView.setSpacing(0) - self.listView.setGridSize(QtCore.QSize(175, 250)) + self.listView.setGridSize(QtCore.QSize(175, 240)) self.listView.verticalScrollBar().setSingleStep(7) self.listView.doubleClicked.connect(self.list_doubleclick) self.reload_listview() - self.resizeEvent() # Keyboard shortcuts self.exit_all = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+Q'), self) @@ -127,7 +128,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): # Maybe expand this to traverse directories recursively self.statusMessage.setText('Adding books...') my_file = QtWidgets.QFileDialog.getOpenFileNames( - self, 'Open file', self.last_open_path, "eBooks (*.epub *.mobi *.txt)") + self, 'Open file', self.last_open_path, + "eBooks (*.epub *.mobi *.aws *.txt *.pdf *.fb2 *.djvu)") if my_file[0]: self.listView.setEnabled(False) self.last_open_path = os.path.dirname(my_file[0][0]) @@ -232,138 +234,13 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): QtWidgets.qApp.exit() -class Library: - def __init__(self, parent): - self.parent_window = parent - self.proxy_model = None - - def generate_model(self): - # TODO - # Use QItemdelegates to show book read progress - - # The QlistView widget needs to be populated - # with a model that inherits from QStandardItemModel - self.parent_window.viewModel = QtGui.QStandardItemModel() - books = database.DatabaseFunctions( - self.parent_window.database_path).fetch_data( - ('*',), - 'books', - {'Title': ''}, - 'LIKE') - - if not books: - print('Database returned nothing') - return - - for i in books: - # The database query returns a tuple with the following indices - # Index 0 is the key ID is ignored - book_title = i[1] - book_author = i[2] - book_year = i[3] - book_cover = i[8] - book_tags = i[6] - all_metadata = { - 'book_title': i[1], - 'book_author': i[2], - 'book_year': i[3], - 'book_path': i[4], - 'book_isbn': i[5], - 'book_tags': i[6], - 'book_hash': i[7]} - - tooltip_string = book_title + '\nAuthor: ' + book_author + '\nYear: ' + str(book_year) - if book_tags: - tooltip_string += ('\nTags: ' + book_tags) - - # This remarkably ugly hack is because the QSortFilterProxyModel - # doesn't easily allow searching through multiple item roles - search_workaround = book_title + ' ' + book_author - if book_tags: - search_workaround += book_tags - - # Generate image pixmap and then pass it to the widget - # as a QIcon - # Additional data can be set using an incrementing - # QtCore.Qt.UserRole - # QtCore.Qt.DisplayRole is the same as item.setText() - # The model is a single row and has no columns - img_pixmap = QtGui.QPixmap() - img_pixmap.loadFromData(book_cover) - img_pixmap = img_pixmap.scaled(420, 600, QtCore.Qt.IgnoreAspectRatio) - item = QtGui.QStandardItem() - item.setToolTip(tooltip_string) - # The following order is needed to keep sorting working - item.setData(book_title, QtCore.Qt.UserRole) - item.setData(book_author, QtCore.Qt.UserRole + 1) - item.setData(book_year, QtCore.Qt.UserRole + 2) - item.setData(all_metadata, QtCore.Qt.UserRole + 3) - item.setData(search_workaround, QtCore.Qt.UserRole + 4) - item.setIcon(QtGui.QIcon(img_pixmap)) - self.parent_window.viewModel.appendRow(item) - - - def update_listView(self): - self.proxy_model = QtCore.QSortFilterProxyModel() - self.proxy_model.setSourceModel(self.parent_window.viewModel) - self.proxy_model.setFilterRole(QtCore.Qt.UserRole + 4) - self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - self.proxy_model.setFilterWildcard(self.parent_window.libraryToolBar.filterEdit.text()) - - self.parent_window.statusMessage.setText( - str(self.proxy_model.rowCount()) + ' books') - - # Sorting according to roles and the drop down in the library - self.proxy_model.setSortRole( - QtCore.Qt.UserRole + self.parent_window.libraryToolBar.sortingBox.currentIndex()) - self.proxy_model.sort(0) - - s = QtCore.QSize(160, 250) # Set icon sizing here - self.parent_window.listView.setIconSize(s) - self.parent_window.listView.setModel(self.proxy_model) - - -class Settings: - def __init__(self, parent): - self.parent_window = parent - self.settings = QtCore.QSettings('Lector', 'Lector') - - def read_settings(self): - self.settings.beginGroup('mainWindow') - self.parent_window.resize(self.settings.value( - 'windowSize', - QtCore.QSize(1299, 748))) - self.parent_window.move(self.settings.value( - 'windowPosition', - QtCore.QPoint(286, 141))) - self.settings.endGroup() - - self.settings.beginGroup('runtimeVariables') - self.parent_window.last_open_path = self.settings.value( - 'lastOpenPath', os.path.expanduser('~')) - self.parent_window.database_path = self.settings.value( - 'databasePath', - QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppDataLocation)) - self.settings.endGroup() - - def save_settings(self): - self.settings.beginGroup('mainWindow') - self.settings.setValue('windowSize', self.parent_window.size()) - self.settings.setValue('windowPosition', self.parent_window.pos()) - self.settings.endGroup() - - self.settings.beginGroup('runtimeVariables') - self.settings.setValue('lastOpenPath', self.parent_window.last_open_path) - self.settings.setValue('databasePath', self.parent_window.database_path) - self.settings.endGroup() - - def main(): app = QtWidgets.QApplication(sys.argv) app.setApplicationName('Lector') # This is needed for QStandardPaths # and my own hubris form = MainUI() form.show() + form.resizeEvent() app.exec_() diff --git a/main_subclasses.py b/main_subclasses.py new file mode 100644 index 0000000..5f29b5d --- /dev/null +++ b/main_subclasses.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import os +import database +from PyQt5 import QtWidgets, QtGui, QtCore + + +class Library: + def __init__(self, parent): + self.parent_window = parent + self.proxy_model = None + + def generate_model(self): + # TODO + # Use QItemdelegates to show book read progress + + # The QlistView widget needs to be populated + # with a model that inherits from QStandardItemModel + self.parent_window.viewModel = QtGui.QStandardItemModel() + books = database.DatabaseFunctions( + self.parent_window.database_path).fetch_data( + ('*',), + 'books', + {'Title': ''}, + 'LIKE') + + if not books: + print('Database returned nothing') + return + + for i in books: + # The database query returns a tuple with the following indices + # Index 0 is the key ID is ignored + book_title = i[1] + book_author = i[2] + book_year = i[3] + book_cover = i[8] + book_tags = i[6] + all_metadata = { + 'book_title': i[1], + 'book_author': i[2], + 'book_year': i[3], + 'book_path': i[4], + 'book_isbn': i[5], + 'book_tags': i[6], + 'book_hash': i[7]} + + tooltip_string = book_title + '\nAuthor: ' + book_author + '\nYear: ' + str(book_year) + if book_tags: + tooltip_string += ('\nTags: ' + book_tags) + + # This remarkably ugly hack is because the QSortFilterProxyModel + # doesn't easily allow searching through multiple item roles + search_workaround = book_title + ' ' + book_author + if book_tags: + search_workaround += book_tags + + # Generate image pixmap and then pass it to the widget + # as a QIcon + # Additional data can be set using an incrementing + # QtCore.Qt.UserRole + # QtCore.Qt.DisplayRole is the same as item.setText() + # The model is a single row and has no columns + img_pixmap = QtGui.QPixmap() + img_pixmap.loadFromData(book_cover) + img_pixmap = img_pixmap.scaled(420, 600, QtCore.Qt.IgnoreAspectRatio) + item = QtGui.QStandardItem() + item.setToolTip(tooltip_string) + # The following order is needed to keep sorting working + item.setData(book_title, QtCore.Qt.UserRole) + item.setData(book_author, QtCore.Qt.UserRole + 1) + item.setData(book_year, QtCore.Qt.UserRole + 2) + item.setData(all_metadata, QtCore.Qt.UserRole + 3) + item.setData(search_workaround, QtCore.Qt.UserRole + 4) + item.setIcon(QtGui.QIcon(img_pixmap)) + self.parent_window.viewModel.appendRow(item) + + + def update_listView(self): + self.proxy_model = QtCore.QSortFilterProxyModel() + self.proxy_model.setSourceModel(self.parent_window.viewModel) + self.proxy_model.setFilterRole(QtCore.Qt.UserRole + 4) + self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + self.proxy_model.setFilterWildcard(self.parent_window.libraryToolBar.filterEdit.text()) + + self.parent_window.statusMessage.setText( + str(self.proxy_model.rowCount()) + ' books') + + # Sorting according to roles and the drop down in the library + self.proxy_model.setSortRole( + QtCore.Qt.UserRole + self.parent_window.libraryToolBar.sortingBox.currentIndex()) + self.proxy_model.sort(0) + + s = QtCore.QSize(160, 250) # Set icon sizing here + self.parent_window.listView.setIconSize(s) + self.parent_window.listView.setModel(self.proxy_model) + + +class Settings: + def __init__(self, parent): + self.parent_window = parent + self.settings = QtCore.QSettings('Lector', 'Lector') + + def read_settings(self): + self.settings.beginGroup('mainWindow') + self.parent_window.resize(self.settings.value( + 'windowSize', + QtCore.QSize(1299, 748))) + self.parent_window.move(self.settings.value( + 'windowPosition', + QtCore.QPoint(286, 141))) + self.settings.endGroup() + + self.settings.beginGroup('runtimeVariables') + self.parent_window.last_open_path = self.settings.value( + 'lastOpenPath', os.path.expanduser('~')) + self.parent_window.database_path = self.settings.value( + 'databasePath', + QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppDataLocation)) + self.settings.endGroup() + + def save_settings(self): + self.settings.beginGroup('mainWindow') + self.settings.setValue('windowSize', self.parent_window.size()) + self.settings.setValue('windowPosition', self.parent_window.pos()) + self.settings.endGroup() + + self.settings.beginGroup('runtimeVariables') + self.settings.setValue('lastOpenPath', self.parent_window.last_open_path) + self.settings.setValue('databasePath', self.parent_window.database_path) + self.settings.endGroup() \ No newline at end of file