Files
Lector/__main__.py
2017-11-11 06:08:51 +05:30

266 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
""" TODO
✓ sqlite3 for cover images cache
✓ sqlite3 for storing metadata
✓ Drop down for SortBy (library view)
✓ Define every widget in code because you're going to need to create separate tabs
✓ Override the keypress event of the textedit
✓ Search bar in toolbar
✓ Shift focus to the tab that has the book open
✓ Search bar in toolbar
✓ Drop down for TOC (book view)
✓ Image reflow
✓ Implement book view settings with a(nother) toolbar
✓ Use format* icons for toolbar buttons
✓ Image delegates
Options:
Ignore a and the for sorting purposes
Check files (hashes) upon restart
Recursive file addition
Show what on startup
Maybe include icons for emblems
mobi, azw support
txt, doc, djvu support
pdf support?
Goodreads API: Ratings, Read, Recommendations
Get ISBN using python-isbnlib
All ebooks should first be added to the database and then returned as HTML
Theming
Pagination
Information dialog widget
Library context menu: Cache, Read, Edit database, delete
Set context menu for definitions and the like
"""
import os
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import mainwindow
import database
import sorter
from widgets import LibraryToolBar, BookToolBar, Tab, LibraryDelegate
from subclasses import Settings, Library
class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
# Initialize application
Settings(self).read_settings() # This should populate all variables that need
# to be remembered across sessions
# Create the database in case it doesn't exist
database.DatabaseInit(self.database_path)
# Create and right align the statusbar label widget
self.statusMessage = QtWidgets.QLabel()
self.statusMessage.setObjectName('statusMessage')
self.statusBar.addPermanentWidget(self.statusMessage)
# Init the QListView
self.viewModel = None
self.lib_ref = Library(self)
# Create toolbars
self.libraryToolBar = LibraryToolBar(self)
self.libraryToolBar.addButton.triggered.connect(self.add_books)
self.libraryToolBar.deleteButton.triggered.connect(self.delete_books)
self.libraryToolBar.filterEdit.textChanged.connect(self.only_update_listview)
self.libraryToolBar.sortingBox.activated.connect(self.only_update_listview)
self.addToolBar(self.libraryToolBar)
self.bookToolBar = BookToolBar(self)
self.bookToolBar.fullscreenButton.triggered.connect(self.set_fullscreen)
self.addToolBar(self.bookToolBar)
# Make the correct toolbar visible
self.tab_switch()
self.tabWidget.currentChanged.connect(self.tab_switch)
# New tabs and their contents
self.current_tab = None
self.current_textEdit = None
# Tab closing
self.tabWidget.setTabsClosable(True)
self.tabWidget.tabBar().setTabButton(0, QtWidgets.QTabBar.RightSide, None)
self.tabWidget.tabCloseRequested.connect(self.close_tab)
# ListView
# self.listView.setSpacing(0)
self.listView.setGridSize(QtCore.QSize(175, 240))
self.listView.verticalScrollBar().setSingleStep(7)
self.listView.doubleClicked.connect(self.list_doubleclick)
self.listView.setItemDelegate(LibraryDelegate())
self.reload_listview()
# Keyboard shortcuts
self.exit_all = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+Q'), self)
self.exit_all.activated.connect(self.closeEvent)
def resizeEvent(self, event=None):
if event:
# This implies a vertical resize event only
# We ain't about that lifestyle
if event.oldSize().width() == event.size().width():
return
# The hackiness of this hack is just...
default_size = 175 # This is size of the QIcon (160 by default) +
# minimum margin is needed between thumbnails
# for n icons, the n + 1th icon will appear at > n +1.11875
# First, calculate the number of images per row
i = self.listView.viewport().width() / default_size
rem = i - int(i)
if rem >= .11875 and rem <= .9999:
num_images = int(i)
else:
num_images = int(i) - 1
# The rest is illustrated using informative variable names
space_occupied = num_images * default_size
space_left = (
self.listView.viewport().width() - space_occupied - 19) # 19 is the scrollbar width
try:
layout_extra_space_per_image = space_left // num_images
self.listView.setGridSize(
QtCore.QSize(default_size + layout_extra_space_per_image, 240))
except ZeroDivisionError: # Initial resize is ignored
return
def add_books(self):
# TODO
# 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 *.aws *.txt *.pdf *.fb2 *.djvu)")
if my_file[0]:
self.listView.setEnabled(False)
self.last_open_path = os.path.dirname(my_file[0][0])
books = sorter.BookSorter(my_file[0], self.database_path)
parsed_books = books.initiate_threads()
database.DatabaseFunctions(self.database_path).add_to_database(parsed_books)
self.listView.setEnabled(True)
self.viewModel = None
self.reload_listview()
def delete_books(self):
selected_books = self.listView.selectedIndexes()
if selected_books:
def ifcontinue(box_button):
if box_button.text() == '&Yes':
selected_hashes = []
for i in selected_books:
book_data = i.data(QtCore.Qt.UserRole + 3)
selected_hashes.append(book_data['book_hash'])
database.DatabaseFunctions(
self.database_path).delete_from_database(selected_hashes)
self.viewModel = None
self.reload_listview()
selected_number = len(selected_books)
msg_box = QtWidgets.QMessageBox()
msg_box.setText('Delete %d book(s)?' % selected_number)
msg_box.setIcon(QtWidgets.QMessageBox.Question)
msg_box.setWindowTitle('Confirm deletion')
msg_box.setStandardButtons(
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
msg_box.buttonClicked.connect(ifcontinue)
msg_box.show()
msg_box.exec_()
def only_update_listview(self):
self.lib_ref.update_proxyModel()
def reload_listview(self):
if not self.viewModel:
self.lib_ref.generate_model()
self.lib_ref.create_proxyModel()
self.lib_ref.update_proxyModel()
def tab_switch(self):
if self.tabWidget.currentIndex() == 0:
self.bookToolBar.hide()
self.libraryToolBar.show()
if self.lib_ref.proxy_model:
# Making the proxy model available doesn't affect
# memory utilization at all. Bleh.
self.statusMessage.setText(
str(self.lib_ref.proxy_model.rowCount()) + ' Books')
else:
self.bookToolBar.show()
self.libraryToolBar.hide()
current_metadata = self.tabWidget.widget(
self.tabWidget.currentIndex()).book_metadata
current_title = current_metadata['book_title']
current_author = current_metadata['book_author']
self.statusMessage.setText(
current_author + ' - ' + current_title)
def set_fullscreen(self):
self.current_tab = self.tabWidget.currentIndex()
self.current_textEdit = self.tabWidget.widget(self.current_tab)
self.exit_shortcut = QtWidgets.QShortcut(
QtGui.QKeySequence('Escape'), self.current_textEdit)
self.exit_shortcut.activated.connect(self.set_normalsize)
self.current_textEdit.setWindowFlags(QtCore.Qt.Window)
self.current_textEdit.setWindowState(QtCore.Qt.WindowFullScreen)
self.hide()
self.current_textEdit.show()
def set_normalsize(self):
self.current_textEdit.setWindowState(QtCore.Qt.WindowNoState)
self.current_textEdit.setWindowFlags(QtCore.Qt.Widget)
self.show()
self.current_textEdit.show()
def list_doubleclick(self, myindex):
# TODO
# Load the book.
index = self.listView.model().index(myindex.row(), 0)
book_metadata = self.listView.model().data(index, QtCore.Qt.UserRole + 3)
# Shift focus to the tab that has the book open (if there is one)
for i in range(1, self.tabWidget.count()):
tab_book_metadata = self.tabWidget.widget(i).book_metadata
if tab_book_metadata['book_hash'] == book_metadata['book_hash']:
self.tabWidget.setCurrentIndex(i)
return
tab_ref = Tab(book_metadata, self.tabWidget)
self.tabWidget.setCurrentWidget(tab_ref)
print(tab_ref.book_metadata) # Metadata upon tab creation
def close_tab(self, tab_index):
print(self.tabWidget.widget(tab_index).book_metadata) # Metadata upon tab deletion
self.tabWidget.removeTab(tab_index)
def closeEvent(self, event=None):
Settings(self).save_settings()
QtWidgets.qApp.exit()
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_()
if __name__ == '__main__':
main()