Fairly substantial rewrite.
This commit is contained in:
13
TODO
13
TODO
@@ -1,13 +1,12 @@
|
|||||||
TODO
|
TODO
|
||||||
Options:
|
Options:
|
||||||
Automatic library management
|
Automatic library management
|
||||||
|
✓ Recursive file addition
|
||||||
Auto deletion
|
Auto deletion
|
||||||
Recursive file addition
|
|
||||||
Add only one file type if multiple are present
|
Add only one file type if multiple are present
|
||||||
Remember files
|
✓ Remember files
|
||||||
Check files (hashes) upon restart
|
✓ Check files (hashes) upon restart
|
||||||
Show what on startup
|
✓ Draw shadows
|
||||||
Draw shadows
|
|
||||||
Library:
|
Library:
|
||||||
✓ sqlite3 for cover images cache
|
✓ sqlite3 for cover images cache
|
||||||
✓ sqlite3 for storing metadata
|
✓ sqlite3 for storing metadata
|
||||||
@@ -19,6 +18,7 @@ TODO
|
|||||||
✓ Tie file deletion and tab closing to model updates
|
✓ Tie file deletion and tab closing to model updates
|
||||||
✓ Create separate thread for parser - Show progress in main window
|
✓ Create separate thread for parser - Show progress in main window
|
||||||
? Create emblem per filetype
|
? Create emblem per filetype
|
||||||
|
Memory management
|
||||||
Table view
|
Table view
|
||||||
Ignore a / the / numbers for sorting purposes
|
Ignore a / the / numbers for sorting purposes
|
||||||
Put the path in the scope of the search
|
Put the path in the scope of the search
|
||||||
@@ -27,6 +27,7 @@ TODO
|
|||||||
Information dialog widget
|
Information dialog widget
|
||||||
Context menu: Cache, Read, Edit database, delete, Mark read/unread
|
Context menu: Cache, Read, Edit database, delete, Mark read/unread
|
||||||
Set focus to newly added file
|
Set focus to newly added file
|
||||||
|
Add capability to sort by new
|
||||||
Reading:
|
Reading:
|
||||||
✓ Drop down for TOC
|
✓ Drop down for TOC
|
||||||
✓ Override the keypress event of the textedit
|
✓ Override the keypress event of the textedit
|
||||||
@@ -45,6 +46,7 @@ TODO
|
|||||||
Record progress
|
Record progress
|
||||||
Pagination
|
Pagination
|
||||||
Set context menu for definitions and the like
|
Set context menu for definitions and the like
|
||||||
|
Hide progressbar
|
||||||
Filetypes:
|
Filetypes:
|
||||||
✓ cbz, cbr support
|
✓ cbz, cbr support
|
||||||
✓ Keep font settings enabled but only for background color
|
✓ Keep font settings enabled but only for background color
|
||||||
@@ -60,3 +62,4 @@ TODO
|
|||||||
Other:
|
Other:
|
||||||
✓ Define every widget in code
|
✓ Define every widget in code
|
||||||
✓ Include icons for emblems
|
✓ Include icons for emblems
|
||||||
|
Shift to logging instead of print statements
|
297
__main__.py
297
__main__.py
@@ -1,5 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Consider using sender().text() instead of sender().objectName()
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -8,9 +27,9 @@ from PyQt5 import QtWidgets, QtGui, QtCore
|
|||||||
import sorter
|
import sorter
|
||||||
import database
|
import database
|
||||||
|
|
||||||
from resources import mainwindow
|
from resources import mainwindow, resources
|
||||||
from widgets import LibraryToolBar, BookToolBar, Tab, LibraryDelegate
|
from widgets import LibraryToolBar, BookToolBar, Tab, LibraryDelegate
|
||||||
from threaded import BackGroundTabUpdate, BackGroundBookAddition
|
from threaded import BackGroundTabUpdate, BackGroundBookAddition, BackGroundBookDeletion
|
||||||
from library import Library
|
from library import Library
|
||||||
from settings import Settings
|
from settings import Settings
|
||||||
|
|
||||||
@@ -23,8 +42,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
# Empty variables that will be infested soon
|
# Empty variables that will be infested soon
|
||||||
self.current_view = None
|
self.settings = {}
|
||||||
self.last_open_books = None
|
|
||||||
self.last_open_tab = None
|
self.last_open_tab = None
|
||||||
self.last_open_path = None
|
self.last_open_path = None
|
||||||
self.thread = None # Background Thread
|
self.thread = None # Background Thread
|
||||||
@@ -32,8 +50,17 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.display_profiles = None
|
self.display_profiles = None
|
||||||
self.current_profile_index = None
|
self.current_profile_index = None
|
||||||
self.database_path = None
|
self.database_path = None
|
||||||
self.table_header_sizes = None
|
self.library_filter_menu = None
|
||||||
self.settings_dialog_settings = None
|
|
||||||
|
# Initialize toolbars
|
||||||
|
self.libraryToolBar = LibraryToolBar(self)
|
||||||
|
self.bookToolBar = BookToolBar(self)
|
||||||
|
|
||||||
|
# Widget declarations
|
||||||
|
self.library_filter_menu = QtWidgets.QMenu()
|
||||||
|
self.statusMessage = QtWidgets.QLabel()
|
||||||
|
self.toolbarToggle = QtWidgets.QToolButton()
|
||||||
|
self.reloadLibrary = QtWidgets.QToolButton()
|
||||||
|
|
||||||
# Initialize application
|
# Initialize application
|
||||||
Settings(self).read_settings() # This should populate all variables that need
|
Settings(self).read_settings() # This should populate all variables that need
|
||||||
@@ -45,25 +72,44 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
# Initialize settings dialog
|
# Initialize settings dialog
|
||||||
self.settings_dialog = SettingsUI(self)
|
self.settings_dialog = SettingsUI(self)
|
||||||
|
|
||||||
# Create and right align the statusbar label widget
|
# Statusbar widgets
|
||||||
self.statusMessage = QtWidgets.QLabel()
|
|
||||||
self.statusMessage.setObjectName('statusMessage')
|
self.statusMessage.setObjectName('statusMessage')
|
||||||
self.statusBar.addPermanentWidget(self.statusMessage)
|
self.statusBar.addPermanentWidget(self.statusMessage)
|
||||||
self.sorterProgress = QtWidgets.QProgressBar()
|
self.sorterProgress = QtWidgets.QProgressBar()
|
||||||
|
self.sorterProgress.setMaximumWidth(300)
|
||||||
self.sorterProgress.setObjectName('sorterProgress')
|
self.sorterProgress.setObjectName('sorterProgress')
|
||||||
sorter.progressbar = self.sorterProgress # This is so that updates can be
|
sorter.progressbar = self.sorterProgress # This is so that updates can be
|
||||||
# connected to setValue
|
# connected to setValue
|
||||||
self.statusBar.addWidget(self.sorterProgress)
|
self.statusBar.addWidget(self.sorterProgress)
|
||||||
self.sorterProgress.setVisible(False)
|
self.sorterProgress.setVisible(False)
|
||||||
|
|
||||||
|
self.toolbarToggle.setIcon(QtGui.QIcon.fromTheme('visibility'))
|
||||||
|
self.toolbarToggle.setObjectName('toolbarToggle')
|
||||||
|
self.toolbarToggle.setToolTip('Toggle toolbar')
|
||||||
|
self.toolbarToggle.setAutoRaise(True)
|
||||||
|
self.toolbarToggle.clicked.connect(self.toggle_toolbars)
|
||||||
|
self.statusBar.addPermanentWidget(self.toolbarToggle)
|
||||||
|
|
||||||
|
# THIS IS TEMPORARY
|
||||||
|
self.guiTest = QtWidgets.QToolButton()
|
||||||
|
self.guiTest.setIcon(QtGui.QIcon.fromTheme('mail-thread-watch'))
|
||||||
|
self.guiTest.setObjectName('guiTest')
|
||||||
|
self.guiTest.setToolTip('Test Function')
|
||||||
|
self.guiTest.setAutoRaise(True)
|
||||||
|
self.guiTest.clicked.connect(self.test_function)
|
||||||
|
self.statusBar.addPermanentWidget(self.guiTest)
|
||||||
|
|
||||||
# Application wide temporary directory
|
# Application wide temporary directory
|
||||||
self.temp_dir = QtCore.QTemporaryDir()
|
self.temp_dir = QtCore.QTemporaryDir()
|
||||||
|
|
||||||
# Init the Library
|
# Init the Library
|
||||||
self.lib_ref = Library(self)
|
self.lib_ref = Library(self)
|
||||||
|
|
||||||
|
# Toolbar display
|
||||||
|
# Maybe make this a persistent option
|
||||||
|
self.settings['show_toolbars'] = True
|
||||||
|
|
||||||
# Library toolbar
|
# Library toolbar
|
||||||
self.libraryToolBar = LibraryToolBar(self)
|
|
||||||
self.libraryToolBar.addButton.triggered.connect(self.add_books)
|
self.libraryToolBar.addButton.triggered.connect(self.add_books)
|
||||||
self.libraryToolBar.deleteButton.triggered.connect(self.delete_books)
|
self.libraryToolBar.deleteButton.triggered.connect(self.delete_books)
|
||||||
self.libraryToolBar.coverViewButton.triggered.connect(self.switch_library_view)
|
self.libraryToolBar.coverViewButton.triggered.connect(self.switch_library_view)
|
||||||
@@ -72,16 +118,16 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.libraryToolBar.searchBar.textChanged.connect(self.lib_ref.update_proxymodel)
|
self.libraryToolBar.searchBar.textChanged.connect(self.lib_ref.update_proxymodel)
|
||||||
self.libraryToolBar.searchBar.textChanged.connect(self.lib_ref.update_table_proxy_model)
|
self.libraryToolBar.searchBar.textChanged.connect(self.lib_ref.update_table_proxy_model)
|
||||||
self.libraryToolBar.sortingBox.activated.connect(self.lib_ref.update_proxymodel)
|
self.libraryToolBar.sortingBox.activated.connect(self.lib_ref.update_proxymodel)
|
||||||
|
self.libraryToolBar.libraryFilterButton.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
||||||
if self.current_view == 0:
|
|
||||||
self.libraryToolBar.coverViewButton.trigger()
|
|
||||||
elif self.current_view == 1:
|
|
||||||
self.libraryToolBar.tableViewButton.trigger()
|
|
||||||
|
|
||||||
self.addToolBar(self.libraryToolBar)
|
self.addToolBar(self.libraryToolBar)
|
||||||
|
|
||||||
|
if self.settings['current_view'] == 0:
|
||||||
|
self.libraryToolBar.coverViewButton.trigger()
|
||||||
|
else:
|
||||||
|
self.libraryToolBar.tableViewButton.trigger()
|
||||||
|
|
||||||
# Book toolbar
|
# Book toolbar
|
||||||
self.bookToolBar = BookToolBar(self)
|
self.bookToolBar.bookmarkButton.triggered.connect(self.toggle_dock_widget)
|
||||||
self.bookToolBar.fullscreenButton.triggered.connect(self.set_fullscreen)
|
self.bookToolBar.fullscreenButton.triggered.connect(self.set_fullscreen)
|
||||||
|
|
||||||
for count, i in enumerate(self.display_profiles):
|
for count, i in enumerate(self.display_profiles):
|
||||||
@@ -120,11 +166,12 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.available_parsers = '*.' + ' *.'.join(sorter.available_parsers)
|
self.available_parsers = '*.' + ' *.'.join(sorter.available_parsers)
|
||||||
print('Available parsers: ' + self.available_parsers)
|
print('Available parsers: ' + self.available_parsers)
|
||||||
|
|
||||||
self.reloadLibrary = QtWidgets.QToolButton()
|
# The library refresh button on the Library tab
|
||||||
self.reloadLibrary.setIcon(QtGui.QIcon.fromTheme('reload'))
|
self.reloadLibrary.setIcon(QtGui.QIcon.fromTheme('reload'))
|
||||||
|
self.reloadLibrary.setObjectName('reloadLibrary')
|
||||||
self.reloadLibrary.setAutoRaise(True)
|
self.reloadLibrary.setAutoRaise(True)
|
||||||
self.reloadLibrary.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
self.reloadLibrary.clicked.connect(self.settings_dialog.start_library_scan)
|
||||||
self.reloadLibrary.triggered.connect(self.switch_library_view)
|
# self.reloadLibrary.clicked.connect(self.cull_covers) # TODO
|
||||||
|
|
||||||
self.tabWidget.tabBar().setTabButton(
|
self.tabWidget.tabBar().setTabButton(
|
||||||
0, QtWidgets.QTabBar.RightSide, self.reloadLibrary)
|
0, QtWidgets.QTabBar.RightSide, self.reloadLibrary)
|
||||||
@@ -140,7 +187,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.listView.setMouseTracking(True)
|
self.listView.setMouseTracking(True)
|
||||||
self.listView.verticalScrollBar().setSingleStep(9)
|
self.listView.verticalScrollBar().setSingleStep(9)
|
||||||
self.listView.doubleClicked.connect(self.library_doubleclick)
|
self.listView.doubleClicked.connect(self.library_doubleclick)
|
||||||
self.listView.setItemDelegate(LibraryDelegate(self.temp_dir.path()))
|
self.listView.setItemDelegate(LibraryDelegate(self.temp_dir.path(), self))
|
||||||
|
|
||||||
# TableView
|
# TableView
|
||||||
self.tableView.doubleClicked.connect(self.library_doubleclick)
|
self.tableView.doubleClicked.connect(self.library_doubleclick)
|
||||||
@@ -148,11 +195,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
QtWidgets.QHeaderView.Interactive)
|
QtWidgets.QHeaderView.Interactive)
|
||||||
self.tableView.horizontalHeader().setSortIndicator(0, QtCore.Qt.AscendingOrder)
|
self.tableView.horizontalHeader().setSortIndicator(0, QtCore.Qt.AscendingOrder)
|
||||||
self.tableView.horizontalHeader().setHighlightSections(False)
|
self.tableView.horizontalHeader().setHighlightSections(False)
|
||||||
if self.table_header_sizes:
|
if self.settings['main_window_headers']:
|
||||||
for count, i in enumerate(self.table_header_sizes):
|
for count, i in enumerate(self.settings['main_window_headers']):
|
||||||
self.tableView.horizontalHeader().resizeSection(count, int(i))
|
self.tableView.horizontalHeader().resizeSection(count, int(i))
|
||||||
self.tableView.horizontalHeader().setStretchLastSection(True)
|
self.tableView.horizontalHeader().setStretchLastSection(True)
|
||||||
self.tableView.horizontalHeader().setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
|
|
||||||
|
|
||||||
# Keyboard shortcuts
|
# Keyboard shortcuts
|
||||||
self.ks_close_tab = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+W'), self)
|
self.ks_close_tab = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+W'), self)
|
||||||
@@ -167,8 +213,34 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
# Open last... open books.
|
# Open last... open books.
|
||||||
# Then set the value to None for the next run
|
# Then set the value to None for the next run
|
||||||
self.open_files(self.last_open_books)
|
self.open_files(self.settings['last_open_books'])
|
||||||
self.last_open_books = None
|
|
||||||
|
# Scan the library @ startup
|
||||||
|
if self.settings['scan_library']:
|
||||||
|
self.settings_dialog.start_library_scan()
|
||||||
|
|
||||||
|
def test_function(self, event=None):
|
||||||
|
top_index = self.listView.indexAt(QtCore.QPoint(20, 20))
|
||||||
|
model_index = self.lib_ref.proxy_model.mapToSource(top_index)
|
||||||
|
top_item = self.lib_ref.view_model.item(model_index.row())
|
||||||
|
|
||||||
|
if top_item:
|
||||||
|
img_pixmap = QtGui.QPixmap()
|
||||||
|
img_pixmap.load(':/images/blank.png')
|
||||||
|
top_item.setIcon(QtGui.QIcon(img_pixmap))
|
||||||
|
else:
|
||||||
|
print('Invalid index')
|
||||||
|
|
||||||
|
def cull_covers(self):
|
||||||
|
# TODO
|
||||||
|
# Use this to reduce memory utilization
|
||||||
|
|
||||||
|
img_pixmap = QtGui.QPixmap()
|
||||||
|
img_pixmap.load(':/images/blank.png')
|
||||||
|
|
||||||
|
for i in range(self.lib_ref.view_model.rowCount()):
|
||||||
|
item = self.lib_ref.view_model.item(i)
|
||||||
|
item.setIcon(QtGui.QIcon(img_pixmap))
|
||||||
|
|
||||||
def resizeEvent(self, event=None):
|
def resizeEvent(self, event=None):
|
||||||
if event:
|
if event:
|
||||||
@@ -206,59 +278,96 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
def add_books(self):
|
def add_books(self):
|
||||||
# TODO
|
# TODO
|
||||||
# Maybe expand this to traverse directories recursively
|
# Remember file addition modality
|
||||||
|
# If a file is added from here, it should not be removed
|
||||||
|
# from the libary in case of a database refresh
|
||||||
|
|
||||||
opened_files = QtWidgets.QFileDialog.getOpenFileNames(
|
opened_files = QtWidgets.QFileDialog.getOpenFileNames(
|
||||||
self, 'Open file', self.last_open_path,
|
self, 'Open file', self.last_open_path,
|
||||||
f'eBooks ({self.available_parsers})')
|
f'eBooks ({self.available_parsers})')
|
||||||
|
|
||||||
if opened_files[0]:
|
if not opened_files[0]:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.settings_dialog.okButton.setEnabled(False)
|
||||||
|
self.reloadLibrary.setEnabled(False)
|
||||||
|
|
||||||
self.last_open_path = os.path.dirname(opened_files[0][0])
|
self.last_open_path = os.path.dirname(opened_files[0][0])
|
||||||
self.sorterProgress.setVisible(True)
|
self.sorterProgress.setVisible(True)
|
||||||
self.statusMessage.setText('Adding books...')
|
self.statusMessage.setText('Adding books...')
|
||||||
self.thread = BackGroundBookAddition(self, opened_files[0], self.database_path)
|
self.thread = BackGroundBookAddition(
|
||||||
|
opened_files[0], self.database_path, False, self)
|
||||||
self.thread.finished.connect(self.move_on)
|
self.thread.finished.connect(self.move_on)
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
def move_on(self):
|
|
||||||
self.sorterProgress.setVisible(False)
|
|
||||||
self.lib_ref.create_table_model()
|
|
||||||
self.lib_ref.create_proxymodel()
|
|
||||||
|
|
||||||
def delete_books(self):
|
def delete_books(self):
|
||||||
# TODO
|
# TODO
|
||||||
# Use maptosource() here to get the view_model
|
|
||||||
# indices selected in the listView
|
|
||||||
# Implement this for the tableview
|
# Implement this for the tableview
|
||||||
# The same process can be used to mirror selection
|
# The same process can be used to mirror selection
|
||||||
|
# Ask if library files are to be excluded from further scans
|
||||||
|
# Make a checkbox for this
|
||||||
|
|
||||||
selected_books = self.listView.selectedIndexes()
|
# Get a list of QItemSelection objects
|
||||||
if selected_books:
|
# What we're interested in is the indexes()[0] in each of them
|
||||||
|
# That gives a list of indexes from the view model
|
||||||
|
selected_books = self.lib_ref.proxy_model.mapSelectionToSource(
|
||||||
|
self.listView.selectionModel().selection())
|
||||||
|
|
||||||
|
if not selected_books:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Deal with message box selection
|
||||||
def ifcontinue(box_button):
|
def ifcontinue(box_button):
|
||||||
if box_button.text() == '&Yes':
|
if box_button.text() != '&Yes':
|
||||||
selected_hashes = []
|
return
|
||||||
for i in selected_books:
|
|
||||||
data = i.data(QtCore.Qt.UserRole + 3)
|
|
||||||
selected_hashes.append(data['hash'])
|
|
||||||
|
|
||||||
database.DatabaseFunctions(
|
# Generate list of selected indexes and deletable hashes
|
||||||
self.database_path).delete_from_database(selected_hashes)
|
selected_indexes = [i.indexes() for i in selected_books]
|
||||||
|
delete_hashes = [
|
||||||
|
self.lib_ref.view_model.data(
|
||||||
|
i[0], QtCore.Qt.UserRole + 6) for i in selected_indexes]
|
||||||
|
|
||||||
|
# Delete the entries from the table model by way of filtering by hash
|
||||||
|
self.lib_ref.table_rows = [
|
||||||
|
i for i in self.lib_ref.table_rows if i[6] not in delete_hashes]
|
||||||
|
|
||||||
|
# Persistent model indexes are required beause deletion mutates the model
|
||||||
|
# Gnerate and delete by persistent index
|
||||||
|
persistent_indexes = [
|
||||||
|
QtCore.QPersistentModelIndex(i[0]) for i in selected_indexes]
|
||||||
|
for i in persistent_indexes:
|
||||||
|
self.lib_ref.view_model.removeRow(i.row())
|
||||||
|
|
||||||
|
# Update the database in the background
|
||||||
|
self.thread = BackGroundBookDeletion(
|
||||||
|
delete_hashes, self.database_path, self)
|
||||||
|
self.thread.finished.connect(self.move_on)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
# Generate a message box to confirm deletion
|
||||||
|
selected_number = len(selected_books)
|
||||||
|
confirm_deletion = QtWidgets.QMessageBox()
|
||||||
|
confirm_deletion.setText('Delete %d book(s)?' % selected_number)
|
||||||
|
confirm_deletion.setIcon(QtWidgets.QMessageBox.Question)
|
||||||
|
confirm_deletion.setWindowTitle('Confirm deletion')
|
||||||
|
confirm_deletion.setStandardButtons(
|
||||||
|
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
|
||||||
|
confirm_deletion.buttonClicked.connect(ifcontinue)
|
||||||
|
confirm_deletion.show()
|
||||||
|
confirm_deletion.exec_()
|
||||||
|
|
||||||
|
def move_on(self):
|
||||||
|
self.settings_dialog.okButton.setEnabled(True)
|
||||||
|
self.settings_dialog.okButton.setToolTip(
|
||||||
|
'Save changes and start library scan')
|
||||||
|
self.reloadLibrary.setEnabled(True)
|
||||||
|
|
||||||
|
self.sorterProgress.setVisible(False)
|
||||||
|
self.sorterProgress.setValue(0)
|
||||||
|
|
||||||
self.lib_ref.generate_model('build')
|
|
||||||
self.lib_ref.create_table_model()
|
self.lib_ref.create_table_model()
|
||||||
self.lib_ref.create_proxymodel()
|
self.lib_ref.create_proxymodel()
|
||||||
|
|
||||||
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 switch_library_view(self):
|
def switch_library_view(self):
|
||||||
if self.libraryToolBar.coverViewButton.isChecked():
|
if self.libraryToolBar.coverViewButton.isChecked():
|
||||||
self.stackedWidget.setCurrentIndex(0)
|
self.stackedWidget.setCurrentIndex(0)
|
||||||
@@ -273,6 +382,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
if self.tabWidget.currentIndex() == 0:
|
if self.tabWidget.currentIndex() == 0:
|
||||||
|
|
||||||
self.resizeEvent()
|
self.resizeEvent()
|
||||||
|
if self.settings['show_toolbars']:
|
||||||
self.bookToolBar.hide()
|
self.bookToolBar.hide()
|
||||||
self.libraryToolBar.show()
|
self.libraryToolBar.show()
|
||||||
|
|
||||||
@@ -282,6 +392,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.statusMessage.setText(
|
self.statusMessage.setText(
|
||||||
str(self.lib_ref.proxy_model.rowCount()) + ' Books')
|
str(self.lib_ref.proxy_model.rowCount()) + ' Books')
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
if self.settings['show_toolbars']:
|
||||||
self.bookToolBar.show()
|
self.bookToolBar.show()
|
||||||
self.libraryToolBar.hide()
|
self.libraryToolBar.hide()
|
||||||
|
|
||||||
@@ -375,14 +487,24 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
current_tab_widget = self.tabWidget.widget(current_tab)
|
current_tab_widget = self.tabWidget.widget(current_tab)
|
||||||
current_tab_widget.go_fullscreen()
|
current_tab_widget.go_fullscreen()
|
||||||
|
|
||||||
def library_doubleclick(self, myindex):
|
def toggle_dock_widget(self):
|
||||||
|
sender = self.sender().objectName()
|
||||||
|
current_tab = self.tabWidget.currentIndex()
|
||||||
|
current_tab_widget = self.tabWidget.widget(current_tab)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Extend this to other context related functions
|
||||||
|
# Make this fullscreenable
|
||||||
|
|
||||||
|
if sender == 'bookmarkButton':
|
||||||
|
current_tab_widget.toggle_bookmarks()
|
||||||
|
|
||||||
|
def library_doubleclick(self, index):
|
||||||
sender = self.sender().objectName()
|
sender = self.sender().objectName()
|
||||||
|
|
||||||
if sender == 'listView':
|
if sender == 'listView':
|
||||||
index = self.lib_ref.proxy_model.index(myindex.row(), 0)
|
|
||||||
metadata = self.lib_ref.proxy_model.data(index, QtCore.Qt.UserRole + 3)
|
metadata = self.lib_ref.proxy_model.data(index, QtCore.Qt.UserRole + 3)
|
||||||
elif sender == 'tableView':
|
elif sender == 'tableView':
|
||||||
index = self.lib_ref.table_proxy_model.index(myindex.row(), 0)
|
|
||||||
metadata = self.lib_ref.table_proxy_model.data(index, QtCore.Qt.UserRole)
|
metadata = self.lib_ref.table_proxy_model.data(index, QtCore.Qt.UserRole)
|
||||||
|
|
||||||
# Shift focus to the tab that has the book open (if there is one)
|
# Shift focus to the tab that has the book open (if there is one)
|
||||||
@@ -409,6 +531,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
file_paths,
|
file_paths,
|
||||||
'reading',
|
'reading',
|
||||||
self.database_path,
|
self.database_path,
|
||||||
|
True,
|
||||||
self.temp_dir.path()).initiate_threads()
|
self.temp_dir.path()).initiate_threads()
|
||||||
|
|
||||||
found_a_focusable_tab = False
|
found_a_focusable_tab = False
|
||||||
@@ -609,19 +732,69 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
else:
|
else:
|
||||||
self.settings_dialog.hide()
|
self.settings_dialog.hide()
|
||||||
|
|
||||||
|
def generate_library_filter_menu(self, directory_list=None):
|
||||||
|
# TODO
|
||||||
|
# Connect this to filtering @ the level of the library
|
||||||
|
# Remember state of the checkboxes on library update and application restart
|
||||||
|
# Behavior for clicking on All
|
||||||
|
# Don't show anything for less than 2 library folders
|
||||||
|
|
||||||
|
self.library_filter_menu.clear()
|
||||||
|
|
||||||
|
def generate_name(path_data):
|
||||||
|
this_filter = path_data[1]
|
||||||
|
if not this_filter:
|
||||||
|
this_filter = os.path.basename(
|
||||||
|
path_data[0]).title()
|
||||||
|
return this_filter
|
||||||
|
|
||||||
|
filter_actions = []
|
||||||
|
filter_list = []
|
||||||
|
if directory_list:
|
||||||
|
checked = [i for i in directory_list if i[3] == QtCore.Qt.Checked]
|
||||||
|
filter_list = list(map(generate_name, checked))
|
||||||
|
filter_list.sort()
|
||||||
|
filter_actions = [QtWidgets.QAction(i, self.library_filter_menu) for i in filter_list]
|
||||||
|
|
||||||
|
filter_all = QtWidgets.QAction('All', self.library_filter_menu)
|
||||||
|
filter_actions.append(filter_all)
|
||||||
|
for i in filter_actions:
|
||||||
|
i.setCheckable(True)
|
||||||
|
i.setChecked(True)
|
||||||
|
i.triggered.connect(self.set_library_filter)
|
||||||
|
|
||||||
|
self.library_filter_menu.addActions(filter_actions)
|
||||||
|
self.library_filter_menu.insertSeparator(filter_all)
|
||||||
|
self.libraryToolBar.libraryFilterButton.setMenu(self.library_filter_menu)
|
||||||
|
|
||||||
|
def set_library_filter(self, event=None):
|
||||||
|
print(event)
|
||||||
|
print(self.sender().text())
|
||||||
|
|
||||||
|
def toggle_toolbars(self):
|
||||||
|
self.settings['show_toolbars'] = not self.settings['show_toolbars']
|
||||||
|
|
||||||
|
current_tab = self.tabWidget.currentIndex()
|
||||||
|
if current_tab == 0:
|
||||||
|
self.libraryToolBar.setVisible(
|
||||||
|
not self.libraryToolBar.isVisible())
|
||||||
|
else:
|
||||||
|
self.bookToolBar.setVisible(
|
||||||
|
not self.bookToolBar.isVisible())
|
||||||
|
|
||||||
def closeEvent(self, event=None):
|
def closeEvent(self, event=None):
|
||||||
# All tabs must be iterated upon here
|
# All tabs must be iterated upon here
|
||||||
self.hide()
|
self.hide()
|
||||||
self.settings_dialog.hide()
|
self.settings_dialog.hide()
|
||||||
self.temp_dir.remove()
|
self.temp_dir.remove()
|
||||||
|
|
||||||
self.last_open_books = []
|
self.settings['last_open_books'] = []
|
||||||
if self.tabWidget.count() > 1:
|
if self.tabWidget.count() > 1 and self.settings['remember_files']:
|
||||||
|
|
||||||
all_metadata = []
|
all_metadata = []
|
||||||
for i in range(1, self.tabWidget.count()):
|
for i in range(1, self.tabWidget.count()):
|
||||||
tab_metadata = self.tabWidget.widget(i).metadata
|
tab_metadata = self.tabWidget.widget(i).metadata
|
||||||
self.last_open_books.append(tab_metadata['path'])
|
self.settings['last_open_books'].append(tab_metadata['path'])
|
||||||
all_metadata.append(tab_metadata)
|
all_metadata.append(tab_metadata)
|
||||||
|
|
||||||
Settings(self).save_settings()
|
Settings(self).save_settings()
|
||||||
|
76
database.py
76
database.py
@@ -1,5 +1,21 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import sqlite3
|
import sqlite3
|
||||||
@@ -16,13 +32,20 @@ class DatabaseInit:
|
|||||||
|
|
||||||
def create_database(self):
|
def create_database(self):
|
||||||
# TODO
|
# TODO
|
||||||
# Add a separate column for directory tags
|
# Add separate columns for:
|
||||||
|
# directory tags
|
||||||
|
# bookmarks
|
||||||
|
# date added
|
||||||
|
# addition mode
|
||||||
self.database.execute(
|
self.database.execute(
|
||||||
"CREATE TABLE books \
|
"CREATE TABLE books \
|
||||||
(id INTEGER PRIMARY KEY, Title TEXT, Author TEXT, Year INTEGER, \
|
(id INTEGER PRIMARY KEY, Title TEXT, Author TEXT, Year INTEGER, \
|
||||||
Path TEXT, Position BLOB, ISBN TEXT, Tags TEXT, Hash TEXT, CoverImage BLOB)")
|
Path TEXT, Position BLOB, ISBN TEXT, Tags TEXT, Hash TEXT, CoverImage BLOB)")
|
||||||
|
|
||||||
|
# CheckState is the standard QtCore.Qt.Checked / Unchecked
|
||||||
self.database.execute(
|
self.database.execute(
|
||||||
"CREATE TABLE directories (id INTEGER PRIMARY KEY, Path TEXT, Name TEXT, Tags TEXT)")
|
"CREATE TABLE directories (id INTEGER PRIMARY KEY, Path TEXT, \
|
||||||
|
Name TEXT, Tags TEXT, CheckState INTEGER)")
|
||||||
self.database.commit()
|
self.database.commit()
|
||||||
self.database.close()
|
self.database.close()
|
||||||
|
|
||||||
@@ -33,15 +56,22 @@ class DatabaseFunctions:
|
|||||||
self.database = sqlite3.connect(database_path)
|
self.database = sqlite3.connect(database_path)
|
||||||
|
|
||||||
def set_library_paths(self, data_iterable):
|
def set_library_paths(self, data_iterable):
|
||||||
|
# TODO
|
||||||
|
# INSERT OR REPLACE is not working
|
||||||
|
# So this is the old fashion kitchen sink approach
|
||||||
|
|
||||||
|
self.database.execute("DELETE FROM directories")
|
||||||
|
|
||||||
for i in data_iterable:
|
for i in data_iterable:
|
||||||
path = i[0]
|
path = i[0]
|
||||||
name = i[1]
|
name = i[1]
|
||||||
tags = i[2]
|
tags = i[2]
|
||||||
|
is_checked = i[3]
|
||||||
|
|
||||||
sql_command = (
|
sql_command = (
|
||||||
"INSERT OR REPLACE INTO directories (ID, Path, Name, Tags)\
|
"INSERT OR REPLACE INTO directories (ID, Path, Name, Tags, CheckState)\
|
||||||
VALUES ((SELECT ID FROM directories WHERE Path = ?), ?, ?, ?)")
|
VALUES ((SELECT ID FROM directories WHERE Path = ?), ?, ?, ?, ?)")
|
||||||
self.database.execute(sql_command, [path, path, name, tags])
|
self.database.execute(sql_command, [path, path, name, tags, is_checked])
|
||||||
|
|
||||||
self.database.commit()
|
self.database.commit()
|
||||||
self.close_database()
|
self.close_database()
|
||||||
@@ -61,9 +91,15 @@ class DatabaseFunctions:
|
|||||||
path = i[1]['path']
|
path = i[1]['path']
|
||||||
cover = i[1]['cover_image']
|
cover = i[1]['cover_image']
|
||||||
isbn = i[1]['isbn']
|
isbn = i[1]['isbn']
|
||||||
|
tags = i[1]['tags']
|
||||||
|
if tags:
|
||||||
|
# Is a tuple. Needs to be a string
|
||||||
|
tags = ', '.join([j for j in tags if j])
|
||||||
|
|
||||||
sql_command_add = (
|
sql_command_add = (
|
||||||
"INSERT INTO books (Title,Author,Year,Path,ISBN,Hash,CoverImage) VALUES(?, ?, ?, ?, ?, ?, ?)")
|
"INSERT INTO \
|
||||||
|
books (Title, Author, Year, Path, ISBN, Tags, Hash, CoverImage) \
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
||||||
|
|
||||||
cover_insert = None
|
cover_insert = None
|
||||||
if cover:
|
if cover:
|
||||||
@@ -72,7 +108,7 @@ class DatabaseFunctions:
|
|||||||
self.database.execute(
|
self.database.execute(
|
||||||
sql_command_add,
|
sql_command_add,
|
||||||
[title, author, year,
|
[title, author, year,
|
||||||
path, isbn, book_hash, cover_insert])
|
path, isbn, tags, book_hash, cover_insert])
|
||||||
|
|
||||||
self.database.commit()
|
self.database.commit()
|
||||||
self.close_database()
|
self.close_database()
|
||||||
@@ -121,8 +157,7 @@ class DatabaseFunctions:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# except sqlite3.OperationalError:
|
except (KeyError, sqlite3.OperationalError):
|
||||||
except KeyError:
|
|
||||||
print('SQLite is in rebellion, Commander')
|
print('SQLite is in rebellion, Commander')
|
||||||
|
|
||||||
self.close_database()
|
self.close_database()
|
||||||
@@ -136,7 +171,9 @@ class DatabaseFunctions:
|
|||||||
|
|
||||||
sql_command = "UPDATE books SET Position = ? WHERE Hash = ?"
|
sql_command = "UPDATE books SET Position = ? WHERE Hash = ?"
|
||||||
try:
|
try:
|
||||||
self.database.execute(sql_command, [sqlite3.Binary(pickled_position), file_hash])
|
self.database.execute(
|
||||||
|
sql_command,
|
||||||
|
[sqlite3.Binary(pickled_position), file_hash])
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
print('SQLite is in rebellion, Commander')
|
print('SQLite is in rebellion, Commander')
|
||||||
return
|
return
|
||||||
@@ -144,18 +181,15 @@ class DatabaseFunctions:
|
|||||||
self.database.commit()
|
self.database.commit()
|
||||||
self.close_database()
|
self.close_database()
|
||||||
|
|
||||||
def delete_from_database(self, file_hashes):
|
def delete_from_database(self, column_name, target_data):
|
||||||
# file_hashes is expected as a list that will be iterated upon
|
# target_data is an iterable
|
||||||
# This should enable multiple deletion
|
|
||||||
|
|
||||||
first = file_hashes[0]
|
if column_name == '*':
|
||||||
sql_command = f"DELETE FROM books WHERE Hash = '{first}'"
|
self.database.execute('DELETE FROM books')
|
||||||
|
else:
|
||||||
if len(file_hashes) > 1:
|
sql_command = f'DELETE FROM books WHERE {column_name} = ?'
|
||||||
for i in file_hashes[1:]:
|
for i in target_data:
|
||||||
sql_command += f" OR Hash = '{i}'"
|
self.database.execute(sql_command, (i,))
|
||||||
|
|
||||||
self.database.execute(sql_command)
|
|
||||||
|
|
||||||
self.database.commit()
|
self.database.commit()
|
||||||
self.close_database()
|
self.close_database()
|
||||||
|
112
library.py
112
library.py
@@ -1,16 +1,36 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Implement filterAcceptsRow for the view_model
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import database
|
import database
|
||||||
|
|
||||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
from PyQt5 import QtGui, QtCore
|
||||||
from models import LibraryItemModel, MostExcellentTableModel, TableProxyModel
|
from models import MostExcellentTableModel, TableProxyModel
|
||||||
|
|
||||||
|
|
||||||
class Library:
|
class Library:
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
self.parent_window = parent
|
self.parent = parent
|
||||||
self.view_model = None
|
self.view_model = None
|
||||||
self.proxy_model = None
|
self.proxy_model = None
|
||||||
self.table_model = None
|
self.table_model = None
|
||||||
@@ -18,16 +38,12 @@ class Library:
|
|||||||
self.table_rows = []
|
self.table_rows = []
|
||||||
|
|
||||||
def generate_model(self, mode, parsed_books=None):
|
def generate_model(self, mode, parsed_books=None):
|
||||||
# The QlistView widget needs to be populated
|
|
||||||
# with a model that inherits from QAbstractItemModel
|
|
||||||
# because I kinda sorta NEED the match() method
|
|
||||||
|
|
||||||
if mode == 'build':
|
if mode == 'build':
|
||||||
self.table_rows = []
|
self.table_rows = []
|
||||||
self.view_model = LibraryItemModel()
|
self.view_model = QtGui.QStandardItemModel()
|
||||||
|
|
||||||
books = database.DatabaseFunctions(
|
books = database.DatabaseFunctions(
|
||||||
self.parent_window.database_path).fetch_data(
|
self.parent.database_path).fetch_data(
|
||||||
('*',),
|
('*',),
|
||||||
'books',
|
'books',
|
||||||
{'Title': ''},
|
{'Title': ''},
|
||||||
@@ -43,20 +59,19 @@ class Library:
|
|||||||
# database using background threads
|
# database using background threads
|
||||||
|
|
||||||
books = []
|
books = []
|
||||||
for i in parsed_books:
|
for i in parsed_books.items():
|
||||||
parsed_title = parsed_books[i]['title']
|
# Scheme
|
||||||
parsed_author = parsed_books[i]['author']
|
# 1: Title, 2: Author, 3: Year, 4: Path
|
||||||
parsed_year = parsed_books[i]['year']
|
# 5: Position, 6: isbn, 7: Tags, 8: Hash
|
||||||
parsed_path = parsed_books[i]['path']
|
# 9: CoverImage
|
||||||
parsed_position = None
|
|
||||||
parsed_isbn = parsed_books[i]['isbn']
|
_tags = i[1]['tags']
|
||||||
parsed_tags = None
|
if _tags:
|
||||||
parsed_hash = i
|
_tags = ', '.join([j for j in _tags if j])
|
||||||
parsed_cover = parsed_books[i]['cover_image']
|
|
||||||
|
|
||||||
books.append([
|
books.append([
|
||||||
None, parsed_title, parsed_author, parsed_year, parsed_path,
|
None, i[1]['title'], i[1]['author'], i[1]['year'], i[1]['path'],
|
||||||
parsed_position, parsed_isbn, parsed_tags, parsed_hash, parsed_cover])
|
None, i[1]['isbn'], _tags, i[0], i[1]['cover_image']])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
@@ -134,7 +149,7 @@ class Library:
|
|||||||
def create_table_model(self):
|
def create_table_model(self):
|
||||||
table_header = ['Title', 'Author', 'Status', 'Year', 'Tags']
|
table_header = ['Title', 'Author', 'Status', 'Year', 'Tags']
|
||||||
self.table_model = MostExcellentTableModel(
|
self.table_model = MostExcellentTableModel(
|
||||||
table_header, self.table_rows, self.parent_window.temp_dir.path())
|
table_header, self.table_rows, self.parent.temp_dir.path())
|
||||||
self.create_table_proxy_model()
|
self.create_table_proxy_model()
|
||||||
|
|
||||||
def create_table_proxy_model(self):
|
def create_table_proxy_model(self):
|
||||||
@@ -142,38 +157,73 @@ class Library:
|
|||||||
self.table_proxy_model.setSourceModel(self.table_model)
|
self.table_proxy_model.setSourceModel(self.table_model)
|
||||||
self.table_proxy_model.setSortCaseSensitivity(False)
|
self.table_proxy_model.setSortCaseSensitivity(False)
|
||||||
self.table_proxy_model.sort(0, QtCore.Qt.AscendingOrder)
|
self.table_proxy_model.sort(0, QtCore.Qt.AscendingOrder)
|
||||||
self.parent_window.tableView.setModel(self.table_proxy_model)
|
self.parent.tableView.setModel(self.table_proxy_model)
|
||||||
self.parent_window.tableView.horizontalHeader().setSortIndicator(
|
self.parent.tableView.horizontalHeader().setSortIndicator(
|
||||||
0, QtCore.Qt.AscendingOrder)
|
0, QtCore.Qt.AscendingOrder)
|
||||||
|
self.update_table_proxy_model()
|
||||||
|
|
||||||
def update_table_proxy_model(self):
|
def update_table_proxy_model(self):
|
||||||
self.table_proxy_model.invalidateFilter()
|
self.table_proxy_model.invalidateFilter()
|
||||||
self.table_proxy_model.setFilterParams(
|
self.table_proxy_model.setFilterParams(
|
||||||
self.parent_window.libraryToolBar.searchBar.text(), [0, 1, 4])
|
self.parent.libraryToolBar.searchBar.text(), [0, 1, 4])
|
||||||
# This isn't needed, but it forces a model update every time the
|
# This isn't needed, but it forces a model update every time the
|
||||||
# text in the line edit changes. So I guess it is needed.
|
# text in the line edit changes. So I guess it is needed.
|
||||||
self.table_proxy_model.setFilterFixedString(
|
self.table_proxy_model.setFilterFixedString(
|
||||||
self.parent_window.libraryToolBar.searchBar.text())
|
self.parent.libraryToolBar.searchBar.text())
|
||||||
|
|
||||||
def create_proxymodel(self):
|
def create_proxymodel(self):
|
||||||
self.proxy_model = QtCore.QSortFilterProxyModel()
|
self.proxy_model = QtCore.QSortFilterProxyModel()
|
||||||
self.proxy_model.setSourceModel(self.view_model)
|
self.proxy_model.setSourceModel(self.view_model)
|
||||||
self.proxy_model.setSortCaseSensitivity(False)
|
self.proxy_model.setSortCaseSensitivity(False)
|
||||||
s = QtCore.QSize(160, 250) # Set icon sizing here
|
s = QtCore.QSize(160, 250) # Set icon sizing here
|
||||||
self.parent_window.listView.setIconSize(s)
|
self.parent.listView.setIconSize(s)
|
||||||
self.parent_window.listView.setModel(self.proxy_model)
|
self.parent.listView.setModel(self.proxy_model)
|
||||||
self.update_proxymodel()
|
self.update_proxymodel()
|
||||||
|
|
||||||
def update_proxymodel(self):
|
def update_proxymodel(self):
|
||||||
self.proxy_model.setFilterRole(QtCore.Qt.UserRole + 4)
|
self.proxy_model.setFilterRole(QtCore.Qt.UserRole + 4)
|
||||||
self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||||
self.proxy_model.setFilterWildcard(
|
self.proxy_model.setFilterWildcard(
|
||||||
self.parent_window.libraryToolBar.searchBar.text())
|
self.parent.libraryToolBar.searchBar.text())
|
||||||
|
|
||||||
self.parent_window.statusMessage.setText(
|
self.parent.statusMessage.setText(
|
||||||
str(self.proxy_model.rowCount()) + ' books')
|
str(self.proxy_model.rowCount()) + ' books')
|
||||||
|
|
||||||
# Sorting according to roles and the drop down in the library
|
# Sorting according to roles and the drop down in the library
|
||||||
self.proxy_model.setSortRole(
|
self.proxy_model.setSortRole(
|
||||||
QtCore.Qt.UserRole + self.parent_window.libraryToolBar.sortingBox.currentIndex())
|
QtCore.Qt.UserRole + self.parent.libraryToolBar.sortingBox.currentIndex())
|
||||||
self.proxy_model.sort(0)
|
self.proxy_model.sort(0)
|
||||||
|
|
||||||
|
def prune_models(self, valid_paths):
|
||||||
|
# To be executed when the library is updated by folder
|
||||||
|
# All files in unselected directories will have to be removed
|
||||||
|
# from both of the models
|
||||||
|
# They will also have to be deleted from the library
|
||||||
|
valid_paths = set(valid_paths)
|
||||||
|
|
||||||
|
# Get all paths in the dictionary from either of the models
|
||||||
|
# self.table_rows has all file metadata in position 5
|
||||||
|
all_paths = [i[5]['path'] for i in self.table_rows]
|
||||||
|
all_paths = set(all_paths)
|
||||||
|
|
||||||
|
invalid_paths = all_paths - valid_paths
|
||||||
|
|
||||||
|
# Remove invalid paths from both of the models
|
||||||
|
self.table_rows = [
|
||||||
|
i for i in self.table_rows if i[5]['path'] not in invalid_paths]
|
||||||
|
|
||||||
|
deletable_persistent_indexes = []
|
||||||
|
for i in range(self.view_model.rowCount()):
|
||||||
|
item = self.view_model.item(i)
|
||||||
|
path = item.data(QtCore.Qt.UserRole + 3)['path']
|
||||||
|
if path in invalid_paths:
|
||||||
|
deletable_persistent_indexes.append(
|
||||||
|
QtCore.QPersistentModelIndex(item.index()))
|
||||||
|
|
||||||
|
if deletable_persistent_indexes:
|
||||||
|
for i in deletable_persistent_indexes:
|
||||||
|
self.view_model.removeRow(i.row())
|
||||||
|
|
||||||
|
# Remove invalid paths from the database as well
|
||||||
|
database.DatabaseFunctions(
|
||||||
|
self.parent.database_path).delete_from_database('Path', invalid_paths)
|
||||||
|
180
models.py
180
models.py
@@ -1,15 +1,33 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from resources import pie_chart
|
from resources import pie_chart
|
||||||
|
|
||||||
|
|
||||||
class LibraryItemModel(QtGui.QStandardItemModel, QtCore.QAbstractItemModel):
|
class ItemProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
|
# TODO
|
||||||
|
# Implement filterAcceptsRow
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
# We're using this to be able to access the match() method
|
super(ItemProxyModel, self).__init__(parent)
|
||||||
super(LibraryItemModel, self).__init__(parent)
|
|
||||||
|
|
||||||
|
|
||||||
class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
||||||
@@ -39,7 +57,7 @@ class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
|||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# This block specializaes this function for the library
|
# This block specializes this function for the library
|
||||||
# Not having a self.temp_dir allows for its reuse elsewhere
|
# Not having a self.temp_dir allows for its reuse elsewhere
|
||||||
if self.temp_dir:
|
if self.temp_dir:
|
||||||
if role == QtCore.Qt.DecorationRole and index.column() == 2:
|
if role == QtCore.Qt.DecorationRole and index.column() == 2:
|
||||||
@@ -71,7 +89,8 @@ class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
#_________________________________
|
#_________________________________
|
||||||
if role == QtCore.Qt.DisplayRole:
|
# The EditRole is so that editing a cell doesn't clear its contents
|
||||||
|
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
|
||||||
value = self.display_data[index.row()][index.column()]
|
value = self.display_data[index.row()][index.column()]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -84,8 +103,8 @@ class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def flags(self, index):
|
def flags(self, index):
|
||||||
# In case of the settings model, model column index 1+ are editable
|
# This means only the Tags column is editable
|
||||||
if not self.temp_dir and index.column() != 0:
|
if self.temp_dir and index.column() == 4:
|
||||||
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
|
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
|
||||||
else:
|
else:
|
||||||
# These are standard select but don't edit values
|
# These are standard select but don't edit values
|
||||||
@@ -124,10 +143,151 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
|||||||
model = self.sourceModel()
|
model = self.sourceModel()
|
||||||
|
|
||||||
valid_indices = [model.index(row_num, i) for i in self.filter_columns]
|
valid_indices = [model.index(row_num, i) for i in self.filter_columns]
|
||||||
valid_data = [model.data(i, QtCore.Qt.DisplayRole).lower() for i in valid_indices if model.data(i, QtCore.Qt.DisplayRole) is not None]
|
valid_data = [
|
||||||
|
model.data(i, QtCore.Qt.DisplayRole).lower() for i in valid_indices if model.data(
|
||||||
|
i, QtCore.Qt.DisplayRole) is not None]
|
||||||
|
|
||||||
for i in valid_data:
|
for i in valid_data:
|
||||||
if self.filter_string in i:
|
if self.filter_string in i:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class MostExcellentFileSystemModel(QtWidgets.QFileSystemModel):
|
||||||
|
# Directories are tracked on the basis of their paths
|
||||||
|
# Poll the tag_data dictionary to get User selection
|
||||||
|
def __init__(self, tag_data, parent=None):
|
||||||
|
super(MostExcellentFileSystemModel, self).__init__(parent)
|
||||||
|
self.tag_data = tag_data
|
||||||
|
self.field_dict = {
|
||||||
|
0: 'check_state',
|
||||||
|
4: 'name',
|
||||||
|
5: 'tags'}
|
||||||
|
|
||||||
|
def columnCount(self, parent):
|
||||||
|
# The QFileSystemModel returns 4 columns by default
|
||||||
|
# Columns 1, 2, 3 will be present but hidden
|
||||||
|
return 6
|
||||||
|
|
||||||
|
def headerData(self, col, orientation, role):
|
||||||
|
# Columns not mentioned here will be hidden
|
||||||
|
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
||||||
|
column_dict = {
|
||||||
|
0: 'Path',
|
||||||
|
4: 'Name',
|
||||||
|
5: 'Tags'}
|
||||||
|
return column_dict[col]
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
if (index.column() in (4, 5)
|
||||||
|
and (role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole)):
|
||||||
|
|
||||||
|
read_field = self.field_dict[index.column()]
|
||||||
|
try:
|
||||||
|
return self.tag_data[self.filePath(index)][read_field]
|
||||||
|
except KeyError:
|
||||||
|
return QtCore.QVariant()
|
||||||
|
|
||||||
|
if role == QtCore.Qt.CheckStateRole and index.column() == 0:
|
||||||
|
return self.checkState(index)
|
||||||
|
|
||||||
|
return QtWidgets.QFileSystemModel.data(self, index, role)
|
||||||
|
|
||||||
|
def flags(self, index):
|
||||||
|
if index.column() in (4, 5):
|
||||||
|
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
|
||||||
|
else:
|
||||||
|
return QtWidgets.QFileSystemModel.flags(self, index) | QtCore.Qt.ItemIsUserCheckable
|
||||||
|
|
||||||
|
def checkState(self, index):
|
||||||
|
while index.isValid():
|
||||||
|
index_path = self.filePath(index)
|
||||||
|
if index_path in self.tag_data:
|
||||||
|
return self.tag_data[index_path]['check_state']
|
||||||
|
index = index.parent()
|
||||||
|
return QtCore.Qt.Unchecked
|
||||||
|
|
||||||
|
def setData(self, index, value, role):
|
||||||
|
if (role == QtCore.Qt.EditRole or role == QtCore.Qt.CheckStateRole) and index.isValid():
|
||||||
|
write_field = self.field_dict[index.column()]
|
||||||
|
self.layoutAboutToBeChanged.emit()
|
||||||
|
|
||||||
|
this_path = self.filePath(index)
|
||||||
|
if this_path not in self.tag_data:
|
||||||
|
self.populate_dictionary(this_path)
|
||||||
|
self.tag_data[this_path][write_field] = value
|
||||||
|
|
||||||
|
self.depopulate_dictionary()
|
||||||
|
|
||||||
|
self.layoutChanged.emit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def populate_dictionary(self, path):
|
||||||
|
self.tag_data[path] = {}
|
||||||
|
self.tag_data[path]['name'] = None
|
||||||
|
self.tag_data[path]['tags'] = None
|
||||||
|
self.tag_data[path]['check_state'] = QtCore.Qt.Checked
|
||||||
|
|
||||||
|
def depopulate_dictionary(self):
|
||||||
|
# This keeps the tag_data dictionary manageable as well as preventing
|
||||||
|
# weird ass behaviour when something is deselected and its tags are cleared
|
||||||
|
deletable = set()
|
||||||
|
for i in self.tag_data.items():
|
||||||
|
all_data = [j[1] for j in i[1].items()]
|
||||||
|
filtered_down = list(filter(lambda x: x is not None and x != 0, all_data))
|
||||||
|
if not filtered_down:
|
||||||
|
deletable.add(i[0])
|
||||||
|
|
||||||
|
# Get untagged subdirectories too
|
||||||
|
all_dirs = [i for i in self.tag_data]
|
||||||
|
all_dirs.sort()
|
||||||
|
|
||||||
|
def is_child(this_dir):
|
||||||
|
this_path = pathlib.Path(this_dir)
|
||||||
|
for i in all_dirs:
|
||||||
|
if pathlib.Path(i) in this_path.parents:
|
||||||
|
# If a parent folder has tags, we only want the deletion
|
||||||
|
# to kick in in case the parent is also checked
|
||||||
|
if self.tag_data[i]['check_state'] == QtCore.Qt.Checked:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
for i in all_dirs:
|
||||||
|
if is_child(i):
|
||||||
|
dir_tags = (self.tag_data[i]['name'], self.tag_data[i]['tags'])
|
||||||
|
filtered_down = list(filter(lambda x: x is not None and x != '', dir_tags))
|
||||||
|
if not filtered_down:
|
||||||
|
deletable.add(i)
|
||||||
|
|
||||||
|
for i in deletable:
|
||||||
|
del self.tag_data[i]
|
||||||
|
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Unbork this
|
||||||
|
class FileSystemProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(FileSystemProxyModel, self).__init__(parent)
|
||||||
|
|
||||||
|
def filterAcceptsRow(self, row_num, parent):
|
||||||
|
model = self.sourceModel()
|
||||||
|
filter_out = [
|
||||||
|
'boot', 'dev', 'etc', 'lost+found', 'opt', 'pdb',
|
||||||
|
'proc', 'root', 'run', 'srv', 'sys', 'tmp', 'twonky',
|
||||||
|
'usr', 'var', 'bin', 'kdeinit5__0', 'lib', 'lib64', 'sbin']
|
||||||
|
|
||||||
|
name_index = model.index(row_num, 0)
|
||||||
|
valid_data = model.data(name_index)
|
||||||
|
|
||||||
|
print(valid_data)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
if valid_data in filter_out:
|
||||||
|
return False
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return True
|
||||||
|
@@ -1,5 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Account for files with passwords
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import collections
|
import collections
|
||||||
@@ -49,7 +68,10 @@ class ParseCBR:
|
|||||||
return cover_image
|
return cover_image
|
||||||
|
|
||||||
def get_isbn(self):
|
def get_isbn(self):
|
||||||
return None
|
return
|
||||||
|
|
||||||
|
def get_tags(self):
|
||||||
|
return
|
||||||
|
|
||||||
def get_contents(self):
|
def get_contents(self):
|
||||||
file_settings = {
|
file_settings = {
|
||||||
|
@@ -1,5 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Account for files with passwords
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import zipfile
|
import zipfile
|
||||||
@@ -52,7 +71,10 @@ class ParseCBZ:
|
|||||||
return cover_image
|
return cover_image
|
||||||
|
|
||||||
def get_isbn(self):
|
def get_isbn(self):
|
||||||
return None
|
return
|
||||||
|
|
||||||
|
def get_tags(self):
|
||||||
|
return
|
||||||
|
|
||||||
def get_contents(self):
|
def get_contents(self):
|
||||||
file_settings = {
|
file_settings = {
|
||||||
|
@@ -1,12 +1,20 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Every parser is supposed to have the following methods, even if they return None:
|
|
||||||
# read_book()
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
# get_title()
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
# get_author()
|
|
||||||
# get_year()
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# get_cover_image()
|
# it under the terms of the GNU General Public License as published by
|
||||||
# get_isbn()
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# get_contents() - Should return a tuple with 0: TOC 1: Deletable temp_directory
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -40,13 +48,13 @@ class ParseEPUB:
|
|||||||
try:
|
try:
|
||||||
return self.book.metadata['http://purl.org/dc/elements/1.1/']['creator'][0][0]
|
return self.book.metadata['http://purl.org/dc/elements/1.1/']['creator'][0][0]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return
|
||||||
|
|
||||||
def get_year(self):
|
def get_year(self):
|
||||||
try:
|
try:
|
||||||
return self.book.metadata['http://purl.org/dc/elements/1.1/']['date'][0][0][:4]
|
return self.book.metadata['http://purl.org/dc/elements/1.1/']['date'][0][0][:4]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return
|
||||||
|
|
||||||
def get_cover_image(self):
|
def get_cover_image(self):
|
||||||
# Get cover image
|
# Get cover image
|
||||||
@@ -89,7 +97,6 @@ class ParseEPUB:
|
|||||||
if i.media_type == 'image/jpeg' or i.media_type == 'image/png':
|
if i.media_type == 'image/jpeg' or i.media_type == 'image/png':
|
||||||
return i.get_content()
|
return i.get_content()
|
||||||
|
|
||||||
|
|
||||||
def get_isbn(self):
|
def get_isbn(self):
|
||||||
try:
|
try:
|
||||||
identifier = self.book.metadata['http://purl.org/dc/elements/1.1/']['identifier']
|
identifier = self.book.metadata['http://purl.org/dc/elements/1.1/']['identifier']
|
||||||
@@ -99,7 +106,15 @@ class ParseEPUB:
|
|||||||
isbn = i[0]
|
isbn = i[0]
|
||||||
return isbn
|
return isbn
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return
|
||||||
|
|
||||||
|
def get_tags(self):
|
||||||
|
try:
|
||||||
|
subject = self.book.metadata['http://purl.org/dc/elements/1.1/']['subject']
|
||||||
|
tags = [i[0] for i in subject]
|
||||||
|
return tags
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
|
||||||
def get_contents(self):
|
def get_contents(self):
|
||||||
extract_path = os.path.join(self.temp_dir, self.file_md5)
|
extract_path = os.path.join(self.temp_dir, self.file_md5)
|
||||||
|
14
resources/about.html
Normal file
14
resources/about.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<meta content="HTML is not a programming language">
|
||||||
|
<style></style>
|
||||||
|
</head>
|
||||||
|
<body><h1 style="text-align: center;">Lector</h1>
|
||||||
|
<h2 style="text-align: center;">A Qt Based ebook reader</h2>
|
||||||
|
<p> </p>
|
||||||
|
<p>Author: BasioMeusPuga <a href="mailto:disgruntled.mob@gmail.com">disgruntled.mob@gmail.com</a></p>
|
||||||
|
<p>Page: <a href="https://github.com/BasioMeusPuga/Lector">https://github.com/BasioMeusPuga/Lector</a></p>
|
||||||
|
<p>License: GPLv3 <a href="https://www.gnu.org/licenses/gpl-3.0.en.html">https://www.gnu.org/licenses/gpl-3.0.en.html</a></p>
|
||||||
|
<p> </p></body>
|
||||||
|
</html>
|
@@ -57,9 +57,10 @@ class Ui_MainWindow(object):
|
|||||||
self.gridLayout_3.setSpacing(0)
|
self.gridLayout_3.setSpacing(0)
|
||||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||||
self.tableView = QtWidgets.QTableView(self.tablePage)
|
self.tableView = QtWidgets.QTableView(self.tablePage)
|
||||||
self.tableView.setFrameShape(QtWidgets.QFrame.NoFrame)
|
self.tableView.setFrameShape(QtWidgets.QFrame.Box)
|
||||||
|
self.tableView.setFrameShadow(QtWidgets.QFrame.Plain)
|
||||||
self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
|
self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
|
||||||
self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed)
|
self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed|QtWidgets.QAbstractItemView.SelectedClicked)
|
||||||
self.tableView.setAlternatingRowColors(True)
|
self.tableView.setAlternatingRowColors(True)
|
||||||
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||||
self.tableView.setGridStyle(QtCore.Qt.NoPen)
|
self.tableView.setGridStyle(QtCore.Qt.NoPen)
|
||||||
|
@@ -110,10 +110,11 @@ def pixmapper(current_chapter, total_chapters, temp_dir, size):
|
|||||||
# TODO
|
# TODO
|
||||||
# See if saving the svg to disk can be avoided
|
# See if saving the svg to disk can be avoided
|
||||||
# Shift to lines to track progress
|
# Shift to lines to track progress
|
||||||
|
# Maybe make the alignment a little more uniform across emblems
|
||||||
|
|
||||||
progress_percent = int(current_chapter * 100 / total_chapters)
|
progress_percent = int(current_chapter * 100 / total_chapters)
|
||||||
generate_pie(progress_percent, temp_dir)
|
generate_pie(progress_percent, temp_dir)
|
||||||
svg_path = os.path.join(temp_dir, 'lector_progress.svg')
|
svg_path = os.path.join(temp_dir, 'lector_progress.svg')
|
||||||
return_pixmap = QtGui.QIcon(svg_path).pixmap(size)
|
return_pixmap = QtGui.QIcon(svg_path).pixmap(size - 4) ## The -4 looks more proportional
|
||||||
|
|
||||||
return return_pixmap
|
return return_pixmap
|
||||||
|
BIN
resources/raw/blank.png
Normal file
BIN
resources/raw/blank.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 663 B |
@@ -117,13 +117,16 @@
|
|||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QTableView" name="tableView">
|
<widget class="QTableView" name="tableView">
|
||||||
<property name="frameShape">
|
<property name="frameShape">
|
||||||
<enum>QFrame::NoFrame</enum>
|
<enum>QFrame::Box</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Plain</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeAdjustPolicy">
|
<property name="sizeAdjustPolicy">
|
||||||
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
|
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="editTriggers">
|
<property name="editTriggers">
|
||||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
|
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||||
</property>
|
</property>
|
||||||
<property name="alternatingRowColors">
|
<property name="alternatingRowColors">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -141,7 +144,7 @@
|
|||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="horizontalHeaderVisible">
|
<attribute name="horizontalHeaderVisible">
|
||||||
<bool>true</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
<attribute name="verticalHeaderVisible">
|
<attribute name="verticalHeaderVisible">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="images">
|
<qresource prefix="images">
|
||||||
|
<file>blank.png</file>
|
||||||
<file>gray-shadow.png</file>
|
<file>gray-shadow.png</file>
|
||||||
<file>NotFound.png</file>
|
<file>NotFound.png</file>
|
||||||
<file>checkmark.svg</file>
|
<file>checkmark.svg</file>
|
||||||
|
@@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>879</width>
|
<width>929</width>
|
||||||
<height>673</height>
|
<height>638</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -23,82 +23,35 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QTableView" name="tableView">
|
<widget class="QTreeView" name="treeView"/>
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::NoFrame</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeAdjustPolicy">
|
|
||||||
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
|
|
||||||
</property>
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
|
|
||||||
</property>
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="selectionMode">
|
|
||||||
<enum>QAbstractItemView::SingleSelection</enum>
|
|
||||||
</property>
|
|
||||||
<property name="selectionBehavior">
|
|
||||||
<enum>QAbstractItemView::SelectItems</enum>
|
|
||||||
</property>
|
|
||||||
<property name="gridStyle">
|
|
||||||
<enum>Qt::NoPen</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sortingEnabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<attribute name="horizontalHeaderVisible">
|
|
||||||
<bool>true</bool>
|
|
||||||
</attribute>
|
|
||||||
<attribute name="verticalHeaderVisible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</attribute>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<widget class="QTextBrowser" name="aboutBox">
|
||||||
<item>
|
<property name="openExternalLinks">
|
||||||
<widget class="QLineEdit" name="tableFilterEdit">
|
<bool>true</bool>
|
||||||
<property name="placeholderText">
|
</property>
|
||||||
<string>Search for Paths, Names, Tags...</string>
|
<property name="openLinks">
|
||||||
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="addButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Add</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="removeButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Remove</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Startup</string>
|
<string>Switches</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox">
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="refreshLibrary">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Auto add files</string>
|
<string>Startup: Refresh library</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -110,21 +63,28 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox_2">
|
<widget class="QCheckBox" name="coverShadows">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Show Library</string>
|
<string>Cover shadows</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox_3">
|
<widget class="QCheckBox" name="autoTags">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cover Shadows</string>
|
<string>Generate tags from files</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
@@ -152,11 +112,6 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Resource object code
|
# Resource object code
|
||||||
#
|
#
|
||||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.9.2)
|
# Created by: The Resource Compiler for PyQt5 (Qt v5.10.0)
|
||||||
#
|
#
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
@@ -1117,6 +1117,50 @@ qt_resource_data = b"\
|
|||||||
\x00\x00\xd2\x04\x2f\x00\x00\x69\x82\x17\x00\x80\xb4\x2f\xb6\xe1\
|
\x00\x00\xd2\x04\x2f\x00\x00\x69\x82\x17\x00\x80\xb4\x2f\xb6\xe1\
|
||||||
\xd6\x4d\xdd\x20\x9a\xae\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\
|
\xd6\x4d\xdd\x20\x9a\xae\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\
|
||||||
\x60\x82\
|
\x60\x82\
|
||||||
|
\x00\x00\x01\xb6\
|
||||||
|
\x3c\
|
||||||
|
\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
|
||||||
|
\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
|
||||||
|
\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
|
||||||
|
\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x65\
|
||||||
|
\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x76\x69\x65\x77\
|
||||||
|
\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x31\x36\x20\x31\x36\x22\x3e\
|
||||||
|
\x0a\x20\x3c\x67\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\
|
||||||
|
\x74\x72\x61\x6e\x73\x6c\x61\x74\x65\x28\x30\x20\x2d\x31\x30\x33\
|
||||||
|
\x36\x2e\x34\x29\x22\x3e\x0a\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\
|
||||||
|
\x20\x66\x69\x6c\x6c\x3d\x22\x23\x66\x34\x34\x33\x33\x36\x22\x20\
|
||||||
|
\x63\x78\x3d\x22\x38\x22\x20\x63\x79\x3d\x22\x31\x30\x34\x34\x2e\
|
||||||
|
\x34\x22\x20\x72\x3d\x22\x37\x22\x2f\x3e\x0a\x20\x20\x3c\x67\x20\
|
||||||
|
\x66\x69\x6c\x6c\x3d\x22\x23\x66\x66\x66\x22\x20\x74\x72\x61\x6e\
|
||||||
|
\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x2e\x37\
|
||||||
|
\x30\x37\x31\x31\x20\x2e\x37\x30\x37\x31\x31\x20\x2d\x2e\x37\x30\
|
||||||
|
\x37\x31\x31\x20\x2e\x37\x30\x37\x31\x31\x20\x37\x34\x30\x2e\x38\
|
||||||
|
\x32\x20\x33\x30\x30\x2e\x32\x33\x29\x22\x3e\x0a\x20\x20\x20\x3c\
|
||||||
|
\x72\x65\x63\x74\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x22\x20\x68\
|
||||||
|
\x65\x69\x67\x68\x74\x3d\x22\x31\x30\x22\x20\x78\x3d\x22\x31\x30\
|
||||||
|
\x34\x33\x2e\x34\x22\x20\x79\x3d\x22\x2d\x31\x33\x22\x20\x74\x72\
|
||||||
|
\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x72\x6f\x74\x61\x74\x65\x28\
|
||||||
|
\x39\x30\x29\x22\x2f\x3e\x0a\x20\x20\x20\x3c\x72\x65\x63\x74\x20\
|
||||||
|
\x77\x69\x64\x74\x68\x3d\x22\x32\x22\x20\x68\x65\x69\x67\x68\x74\
|
||||||
|
\x3d\x22\x31\x30\x22\x20\x78\x3d\x22\x2d\x39\x22\x20\x79\x3d\x22\
|
||||||
|
\x2d\x31\x30\x34\x39\x2e\x34\x22\x20\x74\x72\x61\x6e\x73\x66\x6f\
|
||||||
|
\x72\x6d\x3d\x22\x73\x63\x61\x6c\x65\x28\x2d\x31\x29\x22\x2f\x3e\
|
||||||
|
\x0a\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\
|
||||||
|
\x73\x76\x67\x3e\x0a\
|
||||||
|
\x00\x00\x00\xb1\
|
||||||
|
\x00\
|
||||||
|
\x00\x02\x97\x78\x9c\xeb\x0c\xf0\x73\xe7\xe5\x92\xe2\x62\x60\x60\
|
||||||
|
\xe0\xf5\xf4\x70\x09\x62\x60\x60\x5c\xc2\xc0\xc0\x14\xc1\xc1\x02\
|
||||||
|
\x14\x69\xff\x1b\xe5\x08\xa4\x98\x92\xbc\xdd\x5d\x18\xfe\xb7\xf7\
|
||||||
|
\x9f\xd9\x0f\xe4\x71\x16\x78\x44\x16\x03\x69\x0d\x10\x66\x8c\x39\
|
||||||
|
\x6e\x1f\x0a\x64\xb0\x97\x78\xfa\xba\xb2\x3f\xe4\xe1\xe7\x93\xd2\
|
||||||
|
\x9f\x6b\x5b\x1a\x01\x14\x92\xcd\x0c\x89\x28\x71\xce\xcf\xcd\x4d\
|
||||||
|
\xcd\x2b\x61\x00\x01\xe7\xa2\xd4\xc4\x92\xd4\x14\x85\xf2\xcc\x92\
|
||||||
|
\x0c\x05\x77\x4f\xdf\x80\x14\xbd\x54\x76\xa0\x7d\xff\x3d\x5d\x1c\
|
||||||
|
\x43\x2a\x6e\xbd\x3d\xc8\xc8\x0b\x54\x75\x68\xc1\x77\xff\x5c\x7e\
|
||||||
|
\x76\x11\x86\x51\x30\x22\xc0\x87\xb4\xcd\x8d\x0c\x8c\x9e\x1e\xcf\
|
||||||
|
\xbe\x83\x78\x9e\xae\x7e\x2e\xeb\x9c\x12\x9a\x00\x8a\x79\x2e\x80\
|
||||||
|
\
|
||||||
\x00\x00\x00\xa3\
|
\x00\x00\x00\xa3\
|
||||||
\x00\
|
\x00\
|
||||||
\x00\x09\x38\x78\x9c\xeb\x0c\xf0\x73\xe7\xe5\x92\xe2\x62\x60\x60\
|
\x00\x09\x38\x78\x9c\xeb\x0c\xf0\x73\xe7\xe5\x92\xe2\x62\x60\x60\
|
||||||
@@ -1154,36 +1198,6 @@ qt_resource_data = b"\
|
|||||||
\x36\x35\x36\x33\x2d\x31\x2e\x34\x31\x34\x31\x2d\x31\x2e\x34\x31\
|
\x36\x35\x36\x33\x2d\x31\x2e\x34\x31\x34\x31\x2d\x31\x2e\x34\x31\
|
||||||
\x34\x31\x7a\x22\x2f\x3e\x0a\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\
|
\x34\x31\x7a\x22\x2f\x3e\x0a\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\
|
||||||
\x76\x67\x3e\x0a\
|
\x76\x67\x3e\x0a\
|
||||||
\x00\x00\x01\xb6\
|
|
||||||
\x3c\
|
|
||||||
\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
|
|
||||||
\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
|
|
||||||
\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
|
|
||||||
\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x65\
|
|
||||||
\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x76\x69\x65\x77\
|
|
||||||
\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x31\x36\x20\x31\x36\x22\x3e\
|
|
||||||
\x0a\x20\x3c\x67\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\
|
|
||||||
\x74\x72\x61\x6e\x73\x6c\x61\x74\x65\x28\x30\x20\x2d\x31\x30\x33\
|
|
||||||
\x36\x2e\x34\x29\x22\x3e\x0a\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\
|
|
||||||
\x20\x66\x69\x6c\x6c\x3d\x22\x23\x66\x34\x34\x33\x33\x36\x22\x20\
|
|
||||||
\x63\x78\x3d\x22\x38\x22\x20\x63\x79\x3d\x22\x31\x30\x34\x34\x2e\
|
|
||||||
\x34\x22\x20\x72\x3d\x22\x37\x22\x2f\x3e\x0a\x20\x20\x3c\x67\x20\
|
|
||||||
\x66\x69\x6c\x6c\x3d\x22\x23\x66\x66\x66\x22\x20\x74\x72\x61\x6e\
|
|
||||||
\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x2e\x37\
|
|
||||||
\x30\x37\x31\x31\x20\x2e\x37\x30\x37\x31\x31\x20\x2d\x2e\x37\x30\
|
|
||||||
\x37\x31\x31\x20\x2e\x37\x30\x37\x31\x31\x20\x37\x34\x30\x2e\x38\
|
|
||||||
\x32\x20\x33\x30\x30\x2e\x32\x33\x29\x22\x3e\x0a\x20\x20\x20\x3c\
|
|
||||||
\x72\x65\x63\x74\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x22\x20\x68\
|
|
||||||
\x65\x69\x67\x68\x74\x3d\x22\x31\x30\x22\x20\x78\x3d\x22\x31\x30\
|
|
||||||
\x34\x33\x2e\x34\x22\x20\x79\x3d\x22\x2d\x31\x33\x22\x20\x74\x72\
|
|
||||||
\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x72\x6f\x74\x61\x74\x65\x28\
|
|
||||||
\x39\x30\x29\x22\x2f\x3e\x0a\x20\x20\x20\x3c\x72\x65\x63\x74\x20\
|
|
||||||
\x77\x69\x64\x74\x68\x3d\x22\x32\x22\x20\x68\x65\x69\x67\x68\x74\
|
|
||||||
\x3d\x22\x31\x30\x22\x20\x78\x3d\x22\x2d\x39\x22\x20\x79\x3d\x22\
|
|
||||||
\x2d\x31\x30\x34\x39\x2e\x34\x22\x20\x74\x72\x61\x6e\x73\x66\x6f\
|
|
||||||
\x72\x6d\x3d\x22\x73\x63\x61\x6c\x65\x28\x2d\x31\x29\x22\x2f\x3e\
|
|
||||||
\x0a\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\
|
|
||||||
\x73\x76\x67\x3e\x0a\
|
|
||||||
"
|
"
|
||||||
|
|
||||||
qt_resource_name = b"\
|
qt_resource_name = b"\
|
||||||
@@ -1195,6 +1209,14 @@ qt_resource_name = b"\
|
|||||||
\x02\xea\x4d\x87\
|
\x02\xea\x4d\x87\
|
||||||
\x00\x4e\
|
\x00\x4e\
|
||||||
\x00\x6f\x00\x74\x00\x46\x00\x6f\x00\x75\x00\x6e\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
|
\x00\x6f\x00\x74\x00\x46\x00\x6f\x00\x75\x00\x6e\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
|
||||||
|
\x00\x09\
|
||||||
|
\x09\x65\x83\xe7\
|
||||||
|
\x00\x65\
|
||||||
|
\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
|
||||||
|
\x00\x09\
|
||||||
|
\x08\x4e\x85\x07\
|
||||||
|
\x00\x62\
|
||||||
|
\x00\x6c\x00\x61\x00\x6e\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\
|
||||||
\x00\x0f\
|
\x00\x0f\
|
||||||
\x0a\xe2\xd1\x87\
|
\x0a\xe2\xd1\x87\
|
||||||
\x00\x67\
|
\x00\x67\
|
||||||
@@ -1203,33 +1225,32 @@ qt_resource_name = b"\
|
|||||||
\x0b\x5d\x1f\x07\
|
\x0b\x5d\x1f\x07\
|
||||||
\x00\x63\
|
\x00\x63\
|
||||||
\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x6d\x00\x61\x00\x72\x00\x6b\x00\x2e\x00\x73\x00\x76\x00\x67\
|
\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x6d\x00\x61\x00\x72\x00\x6b\x00\x2e\x00\x73\x00\x76\x00\x67\
|
||||||
\x00\x09\
|
|
||||||
\x09\x65\x83\xe7\
|
|
||||||
\x00\x65\
|
|
||||||
\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
|
|
||||||
"
|
"
|
||||||
|
|
||||||
qt_resource_struct_v1 = b"\
|
qt_resource_struct_v1 = b"\
|
||||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x02\
|
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\
|
||||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||||
\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x47\x17\
|
\x00\x00\x00\x48\x00\x01\x00\x00\x00\x01\x00\x00\x46\xd1\
|
||||||
\x00\x00\x00\x30\x00\x01\x00\x00\x00\x01\x00\x00\x45\x17\
|
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x45\x17\
|
||||||
\x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x45\xbe\
|
\x00\x00\x00\x60\x00\x01\x00\x00\x00\x01\x00\x00\x47\x86\
|
||||||
|
\x00\x00\x00\x84\x00\x00\x00\x00\x00\x01\x00\x00\x48\x2d\
|
||||||
"
|
"
|
||||||
|
|
||||||
qt_resource_struct_v2 = b"\
|
qt_resource_struct_v2 = b"\
|
||||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x02\
|
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||||
\x00\x00\x01\x5f\xb9\x9f\xcd\x26\
|
\x00\x00\x01\x5f\xb9\x9f\xca\xd0\
|
||||||
\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x47\x17\
|
\x00\x00\x00\x48\x00\x01\x00\x00\x00\x01\x00\x00\x46\xd1\
|
||||||
|
\x00\x00\x01\x60\x5a\x92\x05\xe5\
|
||||||
|
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x45\x17\
|
||||||
\x00\x00\x01\x5f\x7e\xcc\x7f\x20\
|
\x00\x00\x01\x5f\x7e\xcc\x7f\x20\
|
||||||
\x00\x00\x00\x30\x00\x01\x00\x00\x00\x01\x00\x00\x45\x17\
|
\x00\x00\x00\x60\x00\x01\x00\x00\x00\x01\x00\x00\x47\x86\
|
||||||
\x00\x00\x01\x5f\xf0\x1d\xc5\xb3\
|
\x00\x00\x01\x5f\xf0\x1d\xc2\x38\
|
||||||
\x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x45\xbe\
|
\x00\x00\x00\x84\x00\x00\x00\x00\x00\x01\x00\x00\x48\x2d\
|
||||||
\x00\x00\x01\x5f\x7e\xcc\x7f\x20\
|
\x00\x00\x01\x5f\x7e\xcc\x7f\x20\
|
||||||
"
|
"
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||||||
class Ui_Dialog(object):
|
class Ui_Dialog(object):
|
||||||
def setupUi(self, Dialog):
|
def setupUi(self, Dialog):
|
||||||
Dialog.setObjectName("Dialog")
|
Dialog.setObjectName("Dialog")
|
||||||
Dialog.resize(879, 673)
|
Dialog.resize(929, 638)
|
||||||
self.gridLayout_3 = QtWidgets.QGridLayout(Dialog)
|
self.gridLayout_3 = QtWidgets.QGridLayout(Dialog)
|
||||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
|
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
|
||||||
@@ -20,66 +20,51 @@ class Ui_Dialog(object):
|
|||||||
self.groupBox_2.setObjectName("groupBox_2")
|
self.groupBox_2.setObjectName("groupBox_2")
|
||||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
|
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
|
||||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||||
self.tableView = QtWidgets.QTableView(self.groupBox_2)
|
self.treeView = QtWidgets.QTreeView(self.groupBox_2)
|
||||||
self.tableView.setFrameShape(QtWidgets.QFrame.NoFrame)
|
self.treeView.setObjectName("treeView")
|
||||||
self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
|
self.gridLayout_2.addWidget(self.treeView, 0, 0, 1, 1)
|
||||||
self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed)
|
self.aboutBox = QtWidgets.QTextBrowser(self.groupBox_2)
|
||||||
self.tableView.setAlternatingRowColors(True)
|
self.aboutBox.setOpenExternalLinks(True)
|
||||||
self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
self.aboutBox.setOpenLinks(False)
|
||||||
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
|
self.aboutBox.setObjectName("aboutBox")
|
||||||
self.tableView.setGridStyle(QtCore.Qt.NoPen)
|
self.gridLayout_2.addWidget(self.aboutBox, 1, 0, 1, 1)
|
||||||
self.tableView.setSortingEnabled(True)
|
|
||||||
self.tableView.setWordWrap(False)
|
|
||||||
self.tableView.setObjectName("tableView")
|
|
||||||
self.tableView.horizontalHeader().setVisible(True)
|
|
||||||
self.tableView.verticalHeader().setVisible(False)
|
|
||||||
self.gridLayout_2.addWidget(self.tableView, 0, 0, 1, 1)
|
|
||||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
|
||||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
|
||||||
self.tableFilterEdit = QtWidgets.QLineEdit(self.groupBox_2)
|
|
||||||
self.tableFilterEdit.setObjectName("tableFilterEdit")
|
|
||||||
self.horizontalLayout.addWidget(self.tableFilterEdit)
|
|
||||||
self.addButton = QtWidgets.QPushButton(self.groupBox_2)
|
|
||||||
self.addButton.setObjectName("addButton")
|
|
||||||
self.horizontalLayout.addWidget(self.addButton)
|
|
||||||
self.removeButton = QtWidgets.QPushButton(self.groupBox_2)
|
|
||||||
self.removeButton.setObjectName("removeButton")
|
|
||||||
self.horizontalLayout.addWidget(self.removeButton)
|
|
||||||
self.gridLayout_2.addLayout(self.horizontalLayout, 1, 0, 1, 1)
|
|
||||||
self.verticalLayout_2.addWidget(self.groupBox_2)
|
self.verticalLayout_2.addWidget(self.groupBox_2)
|
||||||
self.groupBox = QtWidgets.QGroupBox(Dialog)
|
self.groupBox = QtWidgets.QGroupBox(Dialog)
|
||||||
self.groupBox.setObjectName("groupBox")
|
self.groupBox.setObjectName("groupBox")
|
||||||
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
|
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
|
||||||
self.gridLayout.setObjectName("gridLayout")
|
self.gridLayout.setObjectName("gridLayout")
|
||||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
self.verticalLayout.setObjectName("verticalLayout")
|
||||||
self.checkBox = QtWidgets.QCheckBox(self.groupBox)
|
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
||||||
self.checkBox.setObjectName("checkBox")
|
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||||
self.horizontalLayout_3.addWidget(self.checkBox)
|
self.refreshLibrary = QtWidgets.QCheckBox(self.groupBox)
|
||||||
|
self.refreshLibrary.setObjectName("refreshLibrary")
|
||||||
|
self.horizontalLayout_4.addWidget(self.refreshLibrary)
|
||||||
self.fileRemember = QtWidgets.QCheckBox(self.groupBox)
|
self.fileRemember = QtWidgets.QCheckBox(self.groupBox)
|
||||||
self.fileRemember.setObjectName("fileRemember")
|
self.fileRemember.setObjectName("fileRemember")
|
||||||
self.horizontalLayout_3.addWidget(self.fileRemember)
|
self.horizontalLayout_4.addWidget(self.fileRemember)
|
||||||
self.checkBox_2 = QtWidgets.QCheckBox(self.groupBox)
|
self.coverShadows = QtWidgets.QCheckBox(self.groupBox)
|
||||||
self.checkBox_2.setObjectName("checkBox_2")
|
self.coverShadows.setObjectName("coverShadows")
|
||||||
self.horizontalLayout_3.addWidget(self.checkBox_2)
|
self.horizontalLayout_4.addWidget(self.coverShadows)
|
||||||
self.checkBox_3 = QtWidgets.QCheckBox(self.groupBox)
|
self.autoTags = QtWidgets.QCheckBox(self.groupBox)
|
||||||
self.checkBox_3.setObjectName("checkBox_3")
|
self.autoTags.setObjectName("autoTags")
|
||||||
self.horizontalLayout_3.addWidget(self.checkBox_3)
|
self.horizontalLayout_4.addWidget(self.autoTags)
|
||||||
self.gridLayout.addLayout(self.horizontalLayout_3, 0, 0, 1, 1)
|
self.verticalLayout.addLayout(self.horizontalLayout_4)
|
||||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
|
||||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
|
||||||
self.okButton = QtWidgets.QPushButton(self.groupBox)
|
|
||||||
self.okButton.setObjectName("okButton")
|
|
||||||
self.horizontalLayout_2.addWidget(self.okButton)
|
|
||||||
self.cancelButton = QtWidgets.QPushButton(self.groupBox)
|
|
||||||
self.cancelButton.setObjectName("cancelButton")
|
|
||||||
self.horizontalLayout_2.addWidget(self.cancelButton)
|
|
||||||
self.aboutButton = QtWidgets.QPushButton(self.groupBox)
|
|
||||||
self.aboutButton.setObjectName("aboutButton")
|
|
||||||
self.horizontalLayout_2.addWidget(self.aboutButton)
|
|
||||||
self.gridLayout.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
|
|
||||||
self.verticalLayout_2.addWidget(self.groupBox)
|
self.verticalLayout_2.addWidget(self.groupBox)
|
||||||
self.gridLayout_3.addLayout(self.verticalLayout_2, 0, 0, 1, 1)
|
self.gridLayout_3.addLayout(self.verticalLayout_2, 0, 0, 1, 1)
|
||||||
|
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||||
|
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||||
|
self.okButton = QtWidgets.QPushButton(Dialog)
|
||||||
|
self.okButton.setObjectName("okButton")
|
||||||
|
self.horizontalLayout_2.addWidget(self.okButton)
|
||||||
|
self.cancelButton = QtWidgets.QPushButton(Dialog)
|
||||||
|
self.cancelButton.setObjectName("cancelButton")
|
||||||
|
self.horizontalLayout_2.addWidget(self.cancelButton)
|
||||||
|
self.aboutButton = QtWidgets.QPushButton(Dialog)
|
||||||
|
self.aboutButton.setObjectName("aboutButton")
|
||||||
|
self.horizontalLayout_2.addWidget(self.aboutButton)
|
||||||
|
self.gridLayout_3.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
|
||||||
|
|
||||||
self.retranslateUi(Dialog)
|
self.retranslateUi(Dialog)
|
||||||
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
||||||
@@ -88,14 +73,11 @@ class Ui_Dialog(object):
|
|||||||
_translate = QtCore.QCoreApplication.translate
|
_translate = QtCore.QCoreApplication.translate
|
||||||
Dialog.setWindowTitle(_translate("Dialog", "Settings"))
|
Dialog.setWindowTitle(_translate("Dialog", "Settings"))
|
||||||
self.groupBox_2.setTitle(_translate("Dialog", "Library"))
|
self.groupBox_2.setTitle(_translate("Dialog", "Library"))
|
||||||
self.tableFilterEdit.setPlaceholderText(_translate("Dialog", "Search for Paths, Names, Tags..."))
|
self.groupBox.setTitle(_translate("Dialog", "Switches"))
|
||||||
self.addButton.setText(_translate("Dialog", "Add"))
|
self.refreshLibrary.setText(_translate("Dialog", "Startup: Refresh library"))
|
||||||
self.removeButton.setText(_translate("Dialog", "Remove"))
|
|
||||||
self.groupBox.setTitle(_translate("Dialog", "Startup"))
|
|
||||||
self.checkBox.setText(_translate("Dialog", "Auto add files"))
|
|
||||||
self.fileRemember.setText(_translate("Dialog", "Remember open files"))
|
self.fileRemember.setText(_translate("Dialog", "Remember open files"))
|
||||||
self.checkBox_2.setText(_translate("Dialog", "Show Library"))
|
self.coverShadows.setText(_translate("Dialog", "Cover shadows"))
|
||||||
self.checkBox_3.setText(_translate("Dialog", "Cover Shadows"))
|
self.autoTags.setText(_translate("Dialog", "Generate tags from files"))
|
||||||
self.okButton.setText(_translate("Dialog", "OK"))
|
self.okButton.setText(_translate("Dialog", "OK"))
|
||||||
self.cancelButton.setText(_translate("Dialog", "Cancel"))
|
self.cancelButton.setText(_translate("Dialog", "Cancel"))
|
||||||
self.aboutButton.setText(_translate("Dialog", "About"))
|
self.aboutButton.setText(_translate("Dialog", "About"))
|
||||||
|
90
settings.py
90
settings.py
@@ -1,13 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Keep in mind that all integer settings are returned as strings
|
# Keep in mind that all integer / boolean settings are returned as strings
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from ast import literal_eval
|
||||||
from PyQt5 import QtCore, QtGui
|
from PyQt5 import QtCore, QtGui
|
||||||
|
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
self.parent_window = parent
|
self.parent = parent
|
||||||
self.settings = QtCore.QSettings('Lector', 'Lector')
|
self.settings = QtCore.QSettings('Lector', 'Lector')
|
||||||
|
|
||||||
default_profile1 = {
|
default_profile1 = {
|
||||||
@@ -44,87 +45,106 @@ class Settings:
|
|||||||
|
|
||||||
def read_settings(self):
|
def read_settings(self):
|
||||||
self.settings.beginGroup('mainWindow')
|
self.settings.beginGroup('mainWindow')
|
||||||
self.parent_window.resize(self.settings.value('windowSize', QtCore.QSize(1299, 748)))
|
self.parent.resize(self.settings.value('windowSize', QtCore.QSize(1299, 748)))
|
||||||
self.parent_window.move(self.settings.value('windowPosition', QtCore.QPoint(0, 0)))
|
self.parent.move(self.settings.value('windowPosition', QtCore.QPoint(0, 0)))
|
||||||
self.parent_window.current_view = int(self.settings.value('currentView', 0))
|
self.parent.settings['current_view'] = int(self.settings.value('currentView', 0))
|
||||||
self.parent_window.table_header_sizes = self.settings.value('tableHeaders', None)
|
self.parent.settings['main_window_headers'] = self.settings.value('tableHeaders', None)
|
||||||
self.settings.endGroup()
|
self.settings.endGroup()
|
||||||
|
|
||||||
self.settings.beginGroup('runtimeVariables')
|
self.settings.beginGroup('runtimeVariables')
|
||||||
self.parent_window.last_open_path = self.settings.value(
|
self.parent.last_open_path = self.settings.value(
|
||||||
'lastOpenPath', os.path.expanduser('~'))
|
'lastOpenPath', os.path.expanduser('~'))
|
||||||
self.parent_window.database_path = self.settings.value(
|
self.parent.database_path = self.settings.value(
|
||||||
'databasePath',
|
'databasePath',
|
||||||
QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppDataLocation))
|
QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppDataLocation))
|
||||||
self.parent_window.display_profiles = self.settings.value(
|
self.parent.display_profiles = self.settings.value(
|
||||||
'displayProfiles', self.default_profiles)
|
'displayProfiles', self.default_profiles)
|
||||||
self.parent_window.current_profile_index = int(self.settings.value(
|
self.parent.current_profile_index = int(self.settings.value(
|
||||||
'currentProfileIndex', 0))
|
'currentProfileIndex', 0))
|
||||||
self.parent_window.comic_profile = self.settings.value(
|
self.parent.comic_profile = self.settings.value(
|
||||||
'comicProfile', self.default_comic_profile)
|
'comicProfile', self.default_comic_profile)
|
||||||
self.settings.endGroup()
|
self.settings.endGroup()
|
||||||
|
|
||||||
self.settings.beginGroup('lastOpen')
|
self.settings.beginGroup('lastOpen')
|
||||||
self.parent_window.last_open_books = self.settings.value('lastOpenFiles', [])
|
self.parent.settings['last_open_books'] = self.settings.value('lastOpenBooks', [])
|
||||||
self.parent_window.last_open_tab = self.settings.value('lastOpenTab', 'library')
|
self.parent.last_open_tab = self.settings.value('lastOpenTab', 'library')
|
||||||
self.settings.endGroup()
|
self.settings.endGroup()
|
||||||
|
|
||||||
self.settings.beginGroup('settingsWindow')
|
self.settings.beginGroup('settingsWindow')
|
||||||
self.parent_window.settings_dialog_settings = {}
|
self.parent.settings['settings_dialog_size'] = self.settings.value(
|
||||||
self.parent_window.settings_dialog_settings['size'] = self.settings.value(
|
|
||||||
'windowSize', QtCore.QSize(700, 500))
|
'windowSize', QtCore.QSize(700, 500))
|
||||||
self.parent_window.settings_dialog_settings['position'] = self.settings.value(
|
self.parent.settings['settings_dialog_position'] = self.settings.value(
|
||||||
'windowPosition', QtCore.QPoint(0, 0))
|
'windowPosition', QtCore.QPoint(0, 0))
|
||||||
self.parent_window.settings_dialog_settings['headers'] = self.settings.value(
|
self.parent.settings['settings_dialog_headers'] = self.settings.value(
|
||||||
'tableHeaders', [200, 150])
|
'tableHeaders', [200, 150])
|
||||||
self.settings.endGroup()
|
self.settings.endGroup()
|
||||||
|
|
||||||
|
self.settings.beginGroup('settingsSwitches')
|
||||||
|
# The default is string true because literal eval will convert it anyway
|
||||||
|
self.parent.settings['cover_shadows'] = literal_eval(self.settings.value(
|
||||||
|
'coverShadows', 'True').capitalize())
|
||||||
|
self.parent.settings['auto_tags'] = literal_eval(self.settings.value(
|
||||||
|
'autoTags', 'True').capitalize())
|
||||||
|
self.parent.settings['scan_library'] = literal_eval(self.settings.value(
|
||||||
|
'scanLibraryAtStart', 'False').capitalize())
|
||||||
|
self.parent.settings['remember_files'] = literal_eval(self.settings.value(
|
||||||
|
'rememberFiles', 'False').capitalize())
|
||||||
|
self.settings.endGroup()
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
print('Saving settings...')
|
print('Saving settings...')
|
||||||
|
current_settings = self.parent.settings
|
||||||
|
|
||||||
self.settings.beginGroup('mainWindow')
|
self.settings.beginGroup('mainWindow')
|
||||||
self.settings.setValue('windowSize', self.parent_window.size())
|
self.settings.setValue('windowSize', self.parent.size())
|
||||||
self.settings.setValue('windowPosition', self.parent_window.pos())
|
self.settings.setValue('windowPosition', self.parent.pos())
|
||||||
self.settings.setValue('currentView', self.parent_window.stackedWidget.currentIndex())
|
self.settings.setValue('currentView', self.parent.stackedWidget.currentIndex())
|
||||||
|
|
||||||
table_headers = []
|
table_headers = []
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
table_headers.append(self.parent_window.tableView.horizontalHeader().sectionSize(i))
|
table_headers.append(self.parent.tableView.horizontalHeader().sectionSize(i))
|
||||||
self.settings.setValue('tableHeaders', table_headers)
|
self.settings.setValue('tableHeaders', table_headers)
|
||||||
self.settings.endGroup()
|
self.settings.endGroup()
|
||||||
|
|
||||||
self.settings.beginGroup('runtimeVariables')
|
self.settings.beginGroup('runtimeVariables')
|
||||||
self.settings.setValue('lastOpenPath', self.parent_window.last_open_path)
|
self.settings.setValue('lastOpenPath', self.parent.last_open_path)
|
||||||
self.settings.setValue('databasePath', self.parent_window.database_path)
|
self.settings.setValue('databasePath', self.parent.database_path)
|
||||||
|
|
||||||
current_profile1 = self.parent_window.bookToolBar.profileBox.itemData(
|
current_profile1 = self.parent.bookToolBar.profileBox.itemData(
|
||||||
0, QtCore.Qt.UserRole)
|
0, QtCore.Qt.UserRole)
|
||||||
current_profile2 = self.parent_window.bookToolBar.profileBox.itemData(
|
current_profile2 = self.parent.bookToolBar.profileBox.itemData(
|
||||||
1, QtCore.Qt.UserRole)
|
1, QtCore.Qt.UserRole)
|
||||||
current_profile3 = self.parent_window.bookToolBar.profileBox.itemData(
|
current_profile3 = self.parent.bookToolBar.profileBox.itemData(
|
||||||
2, QtCore.Qt.UserRole)
|
2, QtCore.Qt.UserRole)
|
||||||
current_profile_index = self.parent_window.bookToolBar.profileBox.currentIndex()
|
current_profile_index = self.parent.bookToolBar.profileBox.currentIndex()
|
||||||
self.settings.setValue('displayProfiles', [
|
self.settings.setValue('displayProfiles', [
|
||||||
current_profile1,
|
current_profile1,
|
||||||
current_profile2,
|
current_profile2,
|
||||||
current_profile3])
|
current_profile3])
|
||||||
self.settings.setValue('currentProfileIndex', current_profile_index)
|
self.settings.setValue('currentProfileIndex', current_profile_index)
|
||||||
self.settings.setValue('comicProfile', self.parent_window.comic_profile)
|
self.settings.setValue('comicProfile', self.parent.comic_profile)
|
||||||
self.settings.endGroup()
|
self.settings.endGroup()
|
||||||
|
|
||||||
current_tab_index = self.parent_window.tabWidget.currentIndex()
|
current_tab_index = self.parent.tabWidget.currentIndex()
|
||||||
if current_tab_index == 0:
|
if current_tab_index == 0:
|
||||||
last_open_tab = 'library'
|
last_open_tab = 'library'
|
||||||
else:
|
else:
|
||||||
last_open_tab = self.parent_window.tabWidget.widget(current_tab_index).metadata['path']
|
last_open_tab = self.parent.tabWidget.widget(current_tab_index).metadata['path']
|
||||||
|
|
||||||
self.settings.beginGroup('lastOpen')
|
self.settings.beginGroup('lastOpen')
|
||||||
self.settings.setValue('lastOpenFiles', self.parent_window.last_open_books)
|
self.settings.setValue('lastOpenBooks', current_settings['last_open_books'])
|
||||||
self.settings.setValue('lastOpenTab', last_open_tab)
|
self.settings.setValue('lastOpenTab', last_open_tab)
|
||||||
self.settings.endGroup()
|
self.settings.endGroup()
|
||||||
|
|
||||||
self.settings.beginGroup('settingsWindow')
|
self.settings.beginGroup('settingsWindow')
|
||||||
these_settings = self.parent_window.settings_dialog_settings
|
self.settings.setValue('windowSize', current_settings['settings_dialog_size'])
|
||||||
self.settings.setValue('windowSize', these_settings['size'])
|
self.settings.setValue('windowPosition', current_settings['settings_dialog_position'])
|
||||||
self.settings.setValue('windowPosition', these_settings['position'])
|
self.settings.setValue('tableHeaders', current_settings['settings_dialog_headers'])
|
||||||
self.settings.setValue('tableHeaders', these_settings['headers'])
|
self.settings.endGroup()
|
||||||
|
|
||||||
|
self.settings.beginGroup('settingsSwitches')
|
||||||
|
self.settings.setValue('rememberFiles', current_settings['remember_files'])
|
||||||
|
self.settings.setValue('coverShadows', current_settings['cover_shadows'])
|
||||||
|
self.settings.setValue('autoTags', current_settings['auto_tags'])
|
||||||
|
self.settings.setValue('scanLibraryAtStart', current_settings['scan_library'])
|
||||||
|
self.settings.endGroup()
|
||||||
|
@@ -1,156 +1,247 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
import collections
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Get Cancel working with the file system model
|
||||||
|
|
||||||
|
import os
|
||||||
|
import copy
|
||||||
|
from PyQt5 import QtWidgets, QtCore
|
||||||
|
|
||||||
import database
|
import database
|
||||||
from resources import settingswindow
|
from resources import settingswindow
|
||||||
from models import MostExcellentTableModel, TableProxyModel
|
from models import MostExcellentFileSystemModel, FileSystemProxyModel
|
||||||
from threaded import BackGroundBookSearch, BackGroundBookAddition
|
from threaded import BackGroundBookSearch, BackGroundBookAddition
|
||||||
|
|
||||||
|
|
||||||
class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
|
class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
|
||||||
# TODO
|
def __init__(self, parent):
|
||||||
# Deletion from table
|
|
||||||
# Cancel behavior
|
|
||||||
# Update database on table model update
|
|
||||||
|
|
||||||
def __init__(self, parent_window):
|
|
||||||
super(SettingsUI, self).__init__()
|
super(SettingsUI, self).__init__()
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
self.last_open_directory = None
|
self.parent = parent
|
||||||
self.parent_window = parent_window
|
self.database_path = self.parent.database_path
|
||||||
self.database_path = self.parent_window.database_path
|
|
||||||
|
|
||||||
self.resize(self.parent_window.settings_dialog_settings['size'])
|
self.resize(self.parent.settings['settings_dialog_size'])
|
||||||
self.move(self.parent_window.settings_dialog_settings['position'])
|
self.move(self.parent.settings['settings_dialog_position'])
|
||||||
|
|
||||||
|
self.aboutBox.setVisible(False)
|
||||||
|
with open('resources/about.html') as about_html:
|
||||||
|
self.aboutBox.setHtml(about_html.read())
|
||||||
|
|
||||||
self.table_model = None
|
|
||||||
self.old_table_model = None
|
|
||||||
self.table_proxy_model = None
|
|
||||||
self.paths = None
|
self.paths = None
|
||||||
|
|
||||||
self.thread = None
|
self.thread = None
|
||||||
|
self.filesystem_model = None
|
||||||
|
self.tag_data_copy = None
|
||||||
|
|
||||||
self.tableFilterEdit.textChanged.connect(self.update_table_proxy_model)
|
self.okButton.setToolTip('Save changes and start library scan')
|
||||||
self.addButton.clicked.connect(self.add_directories)
|
self.okButton.clicked.connect(self.start_library_scan)
|
||||||
self.cancelButton.clicked.connect(self.cancel_pressed)
|
self.cancelButton.clicked.connect(self.cancel_pressed)
|
||||||
self.okButton.clicked.connect(self.ok_pressed)
|
self.aboutButton.clicked.connect(self.about_pressed)
|
||||||
|
|
||||||
self.generate_table()
|
# Check boxes
|
||||||
header_sizes = self.parent_window.settings_dialog_settings['headers']
|
self.autoTags.setChecked(self.parent.settings['auto_tags'])
|
||||||
if header_sizes:
|
self.coverShadows.setChecked(self.parent.settings['cover_shadows'])
|
||||||
for count, i in enumerate(header_sizes):
|
self.refreshLibrary.setChecked(self.parent.settings['scan_library'])
|
||||||
self.tableView.horizontalHeader().resizeSection(count, int(i))
|
self.fileRemember.setChecked(self.parent.settings['remember_files'])
|
||||||
|
|
||||||
self.tableView.horizontalHeader().setSectionResizeMode(
|
self.autoTags.clicked.connect(self.manage_checkboxes)
|
||||||
QtWidgets.QHeaderView.Interactive)
|
self.coverShadows.clicked.connect(self.manage_checkboxes)
|
||||||
self.tableView.horizontalHeader().setHighlightSections(False)
|
self.refreshLibrary.clicked.connect(self.manage_checkboxes)
|
||||||
self.tableView.horizontalHeader().setStretchLastSection(True)
|
self.fileRemember.clicked.connect(self.manage_checkboxes)
|
||||||
# self.tableView.horizontalHeader().setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
|
|
||||||
|
|
||||||
def generate_table(self):
|
# Generate the filesystem treeView
|
||||||
|
self.generate_tree()
|
||||||
|
|
||||||
|
def generate_tree(self):
|
||||||
# Fetch all directories in the database
|
# Fetch all directories in the database
|
||||||
self.paths = database.DatabaseFunctions(
|
paths = database.DatabaseFunctions(
|
||||||
self.database_path).fetch_data(
|
self.database_path).fetch_data(
|
||||||
('Path', 'Name', 'Tags'),
|
('Path', 'Name', 'Tags', 'CheckState'),
|
||||||
'directories',
|
'directories',
|
||||||
{'Path': ''},
|
{'Path': ''},
|
||||||
'LIKE')
|
'LIKE')
|
||||||
|
|
||||||
if not self.paths:
|
self.parent.generate_library_filter_menu(paths)
|
||||||
|
directory_data = {}
|
||||||
|
if not paths:
|
||||||
print('Database returned no paths for settings...')
|
print('Database returned no paths for settings...')
|
||||||
else:
|
else:
|
||||||
# Convert to a list because tuples, well, they're tuples
|
# Convert to the dictionary format that is
|
||||||
self.paths = [list(i) for i in self.paths]
|
# to be fed into the QFileSystemModel
|
||||||
|
for i in paths:
|
||||||
|
directory_data[i[0]] = {
|
||||||
|
'name': i[1],
|
||||||
|
'tags': i[2],
|
||||||
|
'check_state': i[3]}
|
||||||
|
|
||||||
table_header = ['Path', 'Name', 'Tags']
|
self.filesystem_model = MostExcellentFileSystemModel(directory_data)
|
||||||
self.table_model = MostExcellentTableModel(
|
self.filesystem_model.setFilter(QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Dirs)
|
||||||
table_header, self.paths, None)
|
self.treeView.setModel(self.filesystem_model)
|
||||||
|
|
||||||
self.create_table_proxy_model()
|
|
||||||
|
|
||||||
def create_table_proxy_model(self):
|
|
||||||
self.table_proxy_model = TableProxyModel()
|
|
||||||
self.table_proxy_model.setSourceModel(self.table_model)
|
|
||||||
self.table_proxy_model.setSortCaseSensitivity(False)
|
|
||||||
self.table_proxy_model.sort(1, QtCore.Qt.AscendingOrder)
|
|
||||||
self.tableView.setModel(self.table_proxy_model)
|
|
||||||
self.tableView.horizontalHeader().setSortIndicator(
|
|
||||||
1, QtCore.Qt.AscendingOrder)
|
|
||||||
|
|
||||||
def update_table_proxy_model(self):
|
|
||||||
self.table_proxy_model.invalidateFilter()
|
|
||||||
self.table_proxy_model.setFilterParams(
|
|
||||||
self.tableFilterEdit.text(), [0, 1, 2])
|
|
||||||
self.table_proxy_model.setFilterFixedString(
|
|
||||||
self.tableFilterEdit.text())
|
|
||||||
|
|
||||||
def add_directories(self):
|
|
||||||
# Directories will be added recursively
|
|
||||||
# Sub directory addition is not allowed
|
|
||||||
# In case it is to be allowed eventually, files will not
|
|
||||||
# be duplicated. However, any additional tags will get
|
|
||||||
# added to file tags
|
|
||||||
|
|
||||||
add_directory = QtWidgets.QFileDialog.getExistingDirectory(
|
|
||||||
self, 'Select Directory', self.last_open_directory,
|
|
||||||
QtWidgets.QFileDialog.ShowDirsOnly)
|
|
||||||
add_directory = os.path.realpath(add_directory)
|
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# Account for a parent folder getting added after a subfolder
|
# This here might break on them pestilent non unixy OSes
|
||||||
# Currently this does the inverse only
|
# Check and see
|
||||||
|
|
||||||
for i in self.paths:
|
root_directory = QtCore.QDir().rootPath()
|
||||||
already_present = os.path.realpath(i[0])
|
self.treeView.setRootIndex(self.filesystem_model.setRootPath(root_directory))
|
||||||
if already_present == add_directory or already_present in add_directory:
|
|
||||||
QtWidgets.QMessageBox.critical(
|
|
||||||
self,
|
|
||||||
'Error',
|
|
||||||
'Duplicate or sub folder: ' + already_present + ' ',
|
|
||||||
QtWidgets.QMessageBox.Ok)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Set default name for the directory
|
# Set the treeView and QFileSystemModel to its desired state
|
||||||
directory_name = os.path.basename(add_directory).title()
|
selected_paths = [
|
||||||
data_pair = [[add_directory, directory_name, None]]
|
i for i in directory_data if directory_data[i]['check_state'] == QtCore.Qt.Checked]
|
||||||
database.DatabaseFunctions(self.database_path).set_library_paths(data_pair)
|
expand_paths = set()
|
||||||
self.generate_table()
|
for i in selected_paths:
|
||||||
|
|
||||||
def ok_pressed(self):
|
# Recursively grind down parent paths for expansion
|
||||||
# Traverse directories looking for files
|
this_path = i
|
||||||
self.thread = BackGroundBookSearch(self, self.table_model.display_data)
|
while True:
|
||||||
self.thread.finished.connect(self.do_something)
|
parent_path = os.path.dirname(this_path)
|
||||||
self.thread.start()
|
if parent_path == this_path:
|
||||||
|
break
|
||||||
|
expand_paths.add(parent_path)
|
||||||
|
this_path = parent_path
|
||||||
|
|
||||||
def do_something(self):
|
# Expand all the parent paths derived from the selected path
|
||||||
print('Book search completed')
|
if root_directory in expand_paths:
|
||||||
|
expand_paths.remove(root_directory)
|
||||||
|
|
||||||
|
for i in expand_paths:
|
||||||
|
this_index = self.filesystem_model.index(i)
|
||||||
|
self.treeView.expand(this_index)
|
||||||
|
|
||||||
|
header_sizes = self.parent.settings['settings_dialog_headers']
|
||||||
|
if header_sizes:
|
||||||
|
for count, i in enumerate((0, 4)):
|
||||||
|
self.treeView.setColumnWidth(i, int(header_sizes[count]))
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Set a QSortFilterProxy model on top of the existing QFileSystem model
|
||||||
|
# self.filesystem_proxy_model = FileSystemProxyModel()
|
||||||
|
# self.filesystem_proxy_model.setSourceModel(self.filesystem_model)
|
||||||
|
# self.treeView.setModel(self.filesystem_proxy_model)
|
||||||
|
|
||||||
|
for i in range(1, 4):
|
||||||
|
self.treeView.hideColumn(i)
|
||||||
|
|
||||||
|
def start_library_scan(self):
|
||||||
|
# TODO
|
||||||
|
# return in case the treeView is not edited
|
||||||
|
|
||||||
def cancel_pressed(self):
|
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
|
data_pairs = []
|
||||||
|
for i in self.filesystem_model.tag_data.items():
|
||||||
|
data_pairs.append([
|
||||||
|
i[0], i[1]['name'], i[1]['tags'], i[1]['check_state']
|
||||||
|
])
|
||||||
|
|
||||||
|
database.DatabaseFunctions(
|
||||||
|
self.database_path).set_library_paths(data_pairs)
|
||||||
|
|
||||||
|
if not data_pairs:
|
||||||
|
try:
|
||||||
|
if self.sender().objectName() == 'reloadLibrary':
|
||||||
|
self.show()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.parent.lib_ref.view_model.clear()
|
||||||
|
self.parent.lib_ref.table_rows = []
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# Implement cancel by restoring the table model to an older version
|
# Change this to no longer include files added manually
|
||||||
# def showEvent(self, event):
|
|
||||||
# event.accept()
|
database.DatabaseFunctions(
|
||||||
|
self.database_path).delete_from_database('*', '*')
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update the main window library filter menu
|
||||||
|
self.parent.generate_library_filter_menu(data_pairs)
|
||||||
|
|
||||||
|
# Disallow rechecking until the first check completes
|
||||||
|
self.okButton.setEnabled(False)
|
||||||
|
self.parent.reloadLibrary.setEnabled(False)
|
||||||
|
self.okButton.setToolTip('Library scan in progress...')
|
||||||
|
|
||||||
|
# Traverse directories looking for files
|
||||||
|
self.parent.statusMessage.setText('Checking library folders')
|
||||||
|
self.thread = BackGroundBookSearch(data_pairs, self)
|
||||||
|
self.thread.finished.connect(self.finished_iterating)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def finished_iterating(self):
|
||||||
|
# TODO
|
||||||
|
# Account for file tags
|
||||||
|
|
||||||
|
# The books the search thread has found
|
||||||
|
# are now in self.thread.valid_files
|
||||||
|
valid_files = [i[0] for i in self.thread.valid_files]
|
||||||
|
if not valid_files:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Hey, messaging is important, okay?
|
||||||
|
self.parent.sorterProgress.setVisible(True)
|
||||||
|
self.parent.statusMessage.setText('Parsing files')
|
||||||
|
|
||||||
|
# We now create a new thread to put those files into the database
|
||||||
|
self.thread = BackGroundBookAddition(
|
||||||
|
valid_files, self.database_path, True, self.parent)
|
||||||
|
self.thread.finished.connect(self.parent.move_on)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def cancel_pressed(self):
|
||||||
|
self.filesystem_model.tag_data = copy.deepcopy(self.tag_data_copy)
|
||||||
|
self.hide()
|
||||||
|
|
||||||
def hideEvent(self, event):
|
def hideEvent(self, event):
|
||||||
self.no_more_settings()
|
self.no_more_settings()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
def showEvent(self, event):
|
||||||
|
self.tag_data_copy = copy.deepcopy(self.filesystem_model.tag_data)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
def no_more_settings(self):
|
def no_more_settings(self):
|
||||||
self.table_model = self.old_table_model
|
self.parent.libraryToolBar.settingsButton.setChecked(False)
|
||||||
self.parent_window.libraryToolBar.settingsButton.setChecked(False)
|
self.aboutBox.hide()
|
||||||
|
self.treeView.show()
|
||||||
self.resizeEvent()
|
self.resizeEvent()
|
||||||
|
|
||||||
def resizeEvent(self, event=None):
|
def resizeEvent(self, event=None):
|
||||||
self.parent_window.settings_dialog_settings['size'] = self.size()
|
self.parent.settings['settings_dialog_size'] = self.size()
|
||||||
self.parent_window.settings_dialog_settings['position'] = self.pos()
|
self.parent.settings['settings_dialog_position'] = self.pos()
|
||||||
table_headers = []
|
table_headers = []
|
||||||
for i in range(2):
|
for i in [0, 4]:
|
||||||
table_headers.append(self.tableView.horizontalHeader().sectionSize(i))
|
table_headers.append(self.treeView.columnWidth(i))
|
||||||
self.parent_window.settings_dialog_settings['headers'] = table_headers
|
self.parent.settings['settings_dialog_headers'] = table_headers
|
||||||
|
|
||||||
|
def manage_checkboxes(self, event=None):
|
||||||
|
sender = self.sender().objectName()
|
||||||
|
|
||||||
|
sender_dict = {
|
||||||
|
'coverShadows': 'cover_shadows',
|
||||||
|
'autoTags': 'auto_tags',
|
||||||
|
'refreshLibrary': 'scan_library',
|
||||||
|
'fileRemember': 'remember_files'}
|
||||||
|
|
||||||
|
self.parent.settings[sender_dict[sender]] = not self.parent.settings[sender_dict[sender]]
|
||||||
|
|
||||||
|
def about_pressed(self):
|
||||||
|
self.treeView.setVisible(not self.treeView.isVisible())
|
||||||
|
self.aboutBox.setVisible(not self.aboutBox.isVisible())
|
||||||
|
182
sorter.py
182
sorter.py
@@ -1,17 +1,22 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# TODO
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
# See if you want to include a hash of the book's name and author
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
import io
|
# This program is free software: you can redistribute it and/or modify
|
||||||
import os
|
# it under the terms of the GNU General Public License as published by
|
||||||
import pickle
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
import hashlib
|
# (at your option) any later version.
|
||||||
from multiprocessing.dummy import Pool
|
|
||||||
from PyQt5 import QtCore, QtGui
|
|
||||||
|
|
||||||
import database
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# INSTRUCTIONS
|
||||||
# Every parser is supposed to have the following methods, even if they return None:
|
# Every parser is supposed to have the following methods, even if they return None:
|
||||||
# read_book()
|
# read_book()
|
||||||
# get_title()
|
# get_title()
|
||||||
@@ -19,19 +24,42 @@ import database
|
|||||||
# get_year()
|
# get_year()
|
||||||
# get_cover_image()
|
# get_cover_image()
|
||||||
# get_isbn()
|
# get_isbn()
|
||||||
|
# get_tags()
|
||||||
# get_contents() - Should return a tuple with 0: TOC 1: special_settings (dict)
|
# get_contents() - Should return a tuple with 0: TOC 1: special_settings (dict)
|
||||||
# Parsers for files containing only images need to return only images_only = True
|
# Parsers for files containing only images need to return only images_only = True
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Maybe shift to insert or replace instead of hash checking
|
||||||
|
# See if you want to include a hash of the book's name and author
|
||||||
|
# Change thread niceness
|
||||||
|
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import pickle
|
||||||
|
import hashlib
|
||||||
|
import threading
|
||||||
|
from multiprocessing import Pool, Manager
|
||||||
|
from PyQt5 import QtCore, QtGui
|
||||||
|
|
||||||
|
import database
|
||||||
|
|
||||||
from parsers.epub import ParseEPUB
|
from parsers.epub import ParseEPUB
|
||||||
from parsers.cbz import ParseCBZ
|
from parsers.cbz import ParseCBZ
|
||||||
from parsers.cbr import ParseCBR
|
from parsers.cbr import ParseCBR
|
||||||
|
|
||||||
available_parsers = ['epub', 'cbz', 'cbr']
|
sorter = {
|
||||||
|
'epub': ParseEPUB,
|
||||||
|
'cbz': ParseCBZ,
|
||||||
|
'cbr': ParseCBR}
|
||||||
|
|
||||||
|
available_parsers = [i for i in sorter]
|
||||||
progressbar = None # This is populated by __main__
|
progressbar = None # This is populated by __main__
|
||||||
|
progress_emitter = None # This is to be made into a global variable
|
||||||
|
|
||||||
|
|
||||||
# This is for thread safety
|
|
||||||
class UpdateProgress(QtCore.QObject):
|
class UpdateProgress(QtCore.QObject):
|
||||||
|
# This is for thread safety
|
||||||
update_signal = QtCore.pyqtSignal(int)
|
update_signal = QtCore.pyqtSignal(int)
|
||||||
|
|
||||||
def connect_to_progressbar(self):
|
def connect_to_progressbar(self):
|
||||||
@@ -42,7 +70,7 @@ class UpdateProgress(QtCore.QObject):
|
|||||||
|
|
||||||
|
|
||||||
class BookSorter:
|
class BookSorter:
|
||||||
def __init__(self, file_list, mode, database_path, temp_dir=None):
|
def __init__(self, file_list, mode, database_path, auto_tags=True, temp_dir=None):
|
||||||
# Have the GUI pass a list of files straight to here
|
# Have the GUI pass a list of files straight to here
|
||||||
# Then, on the basis of what is needed, pass the
|
# Then, on the basis of what is needed, pass the
|
||||||
# filenames to the requisite functions
|
# filenames to the requisite functions
|
||||||
@@ -51,17 +79,20 @@ class BookSorter:
|
|||||||
# Caching upon closing
|
# Caching upon closing
|
||||||
self.file_list = [i for i in file_list if os.path.exists(i)]
|
self.file_list = [i for i in file_list if os.path.exists(i)]
|
||||||
self.statistics = [0, (len(file_list))]
|
self.statistics = [0, (len(file_list))]
|
||||||
self.all_books = {}
|
|
||||||
self.hashes = []
|
self.hashes = []
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.database_path = database_path
|
self.database_path = database_path
|
||||||
|
self.auto_tags = auto_tags
|
||||||
self.temp_dir = temp_dir
|
self.temp_dir = temp_dir
|
||||||
if database_path:
|
if database_path:
|
||||||
self.database_hashes()
|
self.database_hashes()
|
||||||
|
|
||||||
|
self.threading_completed = []
|
||||||
|
self.queue = Manager().Queue()
|
||||||
|
self.processed_books = []
|
||||||
|
|
||||||
if self.mode == 'addition':
|
if self.mode == 'addition':
|
||||||
self.progress_emitter = UpdateProgress()
|
progress_object_generator()
|
||||||
self.progress_emitter.connect_to_progressbar()
|
|
||||||
|
|
||||||
def database_hashes(self):
|
def database_hashes(self):
|
||||||
# TODO
|
# TODO
|
||||||
@@ -101,32 +132,21 @@ class BookSorter:
|
|||||||
# This should speed up addition for larger files
|
# This should speed up addition for larger files
|
||||||
# without compromising the integrity of the process
|
# without compromising the integrity of the process
|
||||||
first_bytes = current_book.read(1024 * 32) # First 32KB of the file
|
first_bytes = current_book.read(1024 * 32) # First 32KB of the file
|
||||||
salt = 'Caesar si viveret, ad remum dareris'.encode()
|
|
||||||
first_bytes += salt
|
|
||||||
file_md5 = hashlib.md5(first_bytes).hexdigest()
|
file_md5 = hashlib.md5(first_bytes).hexdigest()
|
||||||
|
|
||||||
if self.mode == 'addition':
|
# Update the progress queue
|
||||||
self.statistics[0] += 1
|
self.queue.put(filename)
|
||||||
self.progress_emitter.update_progress(
|
|
||||||
self.statistics[0] * 100 // self.statistics[1])
|
|
||||||
|
|
||||||
# IF the file is NOT being loaded into the reader,
|
|
||||||
# Do not allow addition in case the file is dupicated in the directory
|
|
||||||
# OR is already in the database
|
|
||||||
# This should not get triggered in reading mode
|
# This should not get triggered in reading mode
|
||||||
if (self.mode == 'addition'
|
# IF the file is NOT being loaded into the reader,
|
||||||
and (file_md5 in self.all_books.items() or file_md5 in self.hashes)):
|
# Do not allow addition in case the file
|
||||||
|
# is already in the database
|
||||||
|
if self.mode == 'addition' and file_md5 in self.hashes:
|
||||||
return
|
return
|
||||||
|
|
||||||
# ___________SORTING TAKES PLACE HERE___________
|
|
||||||
sorter = {
|
|
||||||
'epub': ParseEPUB,
|
|
||||||
'cbz': ParseCBZ,
|
|
||||||
'cbr': ParseCBR
|
|
||||||
}
|
|
||||||
|
|
||||||
file_extension = os.path.splitext(filename)[1][1:]
|
file_extension = os.path.splitext(filename)[1][1:]
|
||||||
try:
|
try:
|
||||||
|
# Get the requisite parser from the sorter dict
|
||||||
book_ref = sorter[file_extension](filename, self.temp_dir, file_md5)
|
book_ref = sorter[file_extension](filename, self.temp_dir, file_md5)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print(filename + ' has an unsupported extension')
|
print(filename + ' has an unsupported extension')
|
||||||
@@ -137,7 +157,7 @@ class BookSorter:
|
|||||||
book_ref.read_book()
|
book_ref.read_book()
|
||||||
if book_ref.book:
|
if book_ref.book:
|
||||||
|
|
||||||
title = book_ref.get_title().title()
|
title = book_ref.get_title()
|
||||||
|
|
||||||
author = book_ref.get_author()
|
author = book_ref.get_author()
|
||||||
if not author:
|
if not author:
|
||||||
@@ -150,22 +170,32 @@ class BookSorter:
|
|||||||
|
|
||||||
isbn = book_ref.get_isbn()
|
isbn = book_ref.get_isbn()
|
||||||
|
|
||||||
# Different modes require different values
|
tags = None
|
||||||
if self.mode == 'addition':
|
if self.auto_tags:
|
||||||
cover_image_raw = book_ref.get_cover_image()
|
tags = book_ref.get_tags()
|
||||||
if cover_image_raw:
|
|
||||||
# Reduce the size of the incoming image
|
|
||||||
cover_image = resize_image(cover_image_raw)
|
|
||||||
else:
|
|
||||||
cover_image = None
|
|
||||||
|
|
||||||
self.all_books[file_md5] = {
|
this_book = {}
|
||||||
|
this_book[file_md5] = {
|
||||||
'title': title,
|
'title': title,
|
||||||
'author': author,
|
'author': author,
|
||||||
'year': year,
|
'year': year,
|
||||||
'isbn': isbn,
|
'isbn': isbn,
|
||||||
|
'hash': file_md5,
|
||||||
'path': filename,
|
'path': filename,
|
||||||
'cover_image': cover_image}
|
'tags': tags}
|
||||||
|
|
||||||
|
# Different modes require different values
|
||||||
|
if self.mode == 'addition':
|
||||||
|
# Reduce the size of the incoming image
|
||||||
|
# if one is found
|
||||||
|
|
||||||
|
cover_image_raw = book_ref.get_cover_image()
|
||||||
|
if cover_image_raw:
|
||||||
|
cover_image = resize_image(cover_image_raw)
|
||||||
|
else:
|
||||||
|
cover_image = None
|
||||||
|
|
||||||
|
this_book[file_md5]['cover_image'] = cover_image
|
||||||
|
|
||||||
if self.mode == 'reading':
|
if self.mode == 'reading':
|
||||||
all_content = book_ref.get_contents()
|
all_content = book_ref.get_contents()
|
||||||
@@ -183,25 +213,65 @@ class BookSorter:
|
|||||||
content['Invalid'] = 'Possible Parse Error'
|
content['Invalid'] = 'Possible Parse Error'
|
||||||
|
|
||||||
position = self.database_position(file_md5)
|
position = self.database_position(file_md5)
|
||||||
self.all_books[file_md5] = {
|
|
||||||
'title': title,
|
|
||||||
'author': author,
|
|
||||||
'year': year,
|
|
||||||
'isbn': isbn,
|
|
||||||
'hash': file_md5,
|
|
||||||
'path': filename,
|
|
||||||
'position': position,
|
|
||||||
'content': content,
|
|
||||||
'images_only': images_only}
|
|
||||||
|
|
||||||
|
this_book[file_md5]['position'] = position
|
||||||
|
this_book[file_md5]['content'] = content
|
||||||
|
this_book[file_md5]['images_only'] = images_only
|
||||||
|
|
||||||
|
return this_book
|
||||||
|
|
||||||
|
def read_progress(self):
|
||||||
|
while True:
|
||||||
|
processed_file = self.queue.get()
|
||||||
|
self.threading_completed.append(processed_file)
|
||||||
|
|
||||||
|
total_number = len(self.file_list)
|
||||||
|
completed_number = len(self.threading_completed)
|
||||||
|
|
||||||
|
if progress_emitter: # Skip update in reading mode
|
||||||
|
progress_emitter.update_progress(
|
||||||
|
completed_number * 100 // total_number)
|
||||||
|
|
||||||
|
if total_number == completed_number:
|
||||||
|
break
|
||||||
|
|
||||||
def initiate_threads(self):
|
def initiate_threads(self):
|
||||||
|
def pool_creator():
|
||||||
_pool = Pool(5)
|
_pool = Pool(5)
|
||||||
_pool.map(self.read_book, self.file_list)
|
self.processed_books = _pool.map(
|
||||||
|
self.read_book, self.file_list)
|
||||||
|
|
||||||
_pool.close()
|
_pool.close()
|
||||||
_pool.join()
|
_pool.join()
|
||||||
|
|
||||||
return self.all_books
|
start_time = time.time()
|
||||||
|
|
||||||
|
worker_thread = threading.Thread(target=pool_creator)
|
||||||
|
progress_thread = threading.Thread(target=self.read_progress)
|
||||||
|
worker_thread.start()
|
||||||
|
progress_thread.start()
|
||||||
|
|
||||||
|
worker_thread.join()
|
||||||
|
progress_thread.join(timeout=.5)
|
||||||
|
|
||||||
|
return_books = {}
|
||||||
|
# Exclude None returns generated in case of duplication / parse errors
|
||||||
|
self.processed_books = [i for i in self.processed_books if i]
|
||||||
|
for i in self.processed_books:
|
||||||
|
for j in i:
|
||||||
|
return_books[j] = i[j]
|
||||||
|
|
||||||
|
del self.processed_books
|
||||||
|
print('Finished processing in', time.time() - start_time)
|
||||||
|
return return_books
|
||||||
|
|
||||||
|
|
||||||
|
def progress_object_generator():
|
||||||
|
# This has to be kept separate from the BookSorter class because
|
||||||
|
# the QtObject inheritance disallows pickling
|
||||||
|
global progress_emitter
|
||||||
|
progress_emitter = UpdateProgress()
|
||||||
|
progress_emitter.connect_to_progressbar()
|
||||||
|
|
||||||
|
|
||||||
def resize_image(cover_image_raw):
|
def resize_image(cover_image_raw):
|
||||||
|
83
threaded.py
83
threaded.py
@@ -1,6 +1,23 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
from multiprocessing.dummy import Pool
|
from multiprocessing.dummy import Pool
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
@@ -26,38 +43,72 @@ class BackGroundTabUpdate(QtCore.QThread):
|
|||||||
|
|
||||||
|
|
||||||
class BackGroundBookAddition(QtCore.QThread):
|
class BackGroundBookAddition(QtCore.QThread):
|
||||||
def __init__(self, parent_window, file_list, database_path, parent=None):
|
def __init__(self, file_list, database_path, prune_required, parent=None):
|
||||||
super(BackGroundBookAddition, self).__init__(parent)
|
super(BackGroundBookAddition, self).__init__(parent)
|
||||||
self.parent_window = parent_window
|
|
||||||
self.file_list = file_list
|
self.file_list = file_list
|
||||||
|
self.parent = parent
|
||||||
self.database_path = database_path
|
self.database_path = database_path
|
||||||
|
self.prune_required = prune_required
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
books = sorter.BookSorter(
|
books = sorter.BookSorter(
|
||||||
self.file_list,
|
self.file_list,
|
||||||
'addition',
|
'addition',
|
||||||
self.database_path)
|
self.database_path,
|
||||||
|
self.parent.settings['auto_tags'])
|
||||||
parsed_books = books.initiate_threads()
|
parsed_books = books.initiate_threads()
|
||||||
|
self.parent.lib_ref.generate_model('addition', parsed_books)
|
||||||
|
if self.prune_required:
|
||||||
|
self.parent.lib_ref.prune_models(self.file_list)
|
||||||
database.DatabaseFunctions(self.database_path).add_to_database(parsed_books)
|
database.DatabaseFunctions(self.database_path).add_to_database(parsed_books)
|
||||||
self.parent_window.lib_ref.generate_model('addition', parsed_books)
|
|
||||||
|
|
||||||
|
class BackGroundBookDeletion(QtCore.QThread):
|
||||||
|
def __init__(self, hash_list, database_path, parent=None):
|
||||||
|
super(BackGroundBookDeletion, self).__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
|
self.hash_list = hash_list
|
||||||
|
self.database_path = database_path
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
database.DatabaseFunctions(
|
||||||
|
self.database_path).delete_from_database('Hash', self.hash_list)
|
||||||
|
|
||||||
|
|
||||||
class BackGroundBookSearch(QtCore.QThread):
|
class BackGroundBookSearch(QtCore.QThread):
|
||||||
def __init__(self, parent_window, data_list, parent=None):
|
# TODO
|
||||||
|
# Change existing sorter module functionality to handle preset tags
|
||||||
|
# Change database to accomodate User Tags, Folder Name, Folder Tags
|
||||||
|
|
||||||
|
def __init__(self, data_list, parent=None):
|
||||||
super(BackGroundBookSearch, self).__init__(parent)
|
super(BackGroundBookSearch, self).__init__(parent)
|
||||||
self.parent_window = parent_window
|
self.parent = parent
|
||||||
self.data_list = data_list
|
self.valid_files = []
|
||||||
self.valid_files = [] # A tuple should get added to this containing the
|
|
||||||
# file path and the folder name / tags
|
# Filter for checked directories
|
||||||
|
self.valid_directories = [
|
||||||
|
[i[0], i[1], i[2]] for i in data_list if i[3] == QtCore.Qt.Checked]
|
||||||
|
self.unwanted_directories = [
|
||||||
|
pathlib.Path(i[0]) for i in data_list if i[3] == QtCore.Qt.Unchecked]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
|
def is_wanted(directory):
|
||||||
|
directory_parents = pathlib.Path(directory).parents
|
||||||
|
for i in self.unwanted_directories:
|
||||||
|
if i in directory_parents:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def traverse_directory(incoming_data):
|
def traverse_directory(incoming_data):
|
||||||
root_directory = incoming_data[0]
|
root_directory = incoming_data[0]
|
||||||
folder_name = incoming_data[1]
|
folder_name = incoming_data[1]
|
||||||
folder_tags = incoming_data[2]
|
folder_tags = incoming_data[2]
|
||||||
|
|
||||||
for directory, subdir, files in os.walk(root_directory):
|
for directory, subdirs, files in os.walk(root_directory, topdown=True):
|
||||||
|
# Black magic fuckery
|
||||||
|
# Skip subdir tree in case it's not wanted
|
||||||
|
subdirs[:] = [d for d in subdirs if is_wanted(os.path.join(directory, d))]
|
||||||
for filename in files:
|
for filename in files:
|
||||||
if os.path.splitext(filename)[1][1:] in sorter.available_parsers:
|
if os.path.splitext(filename)[1][1:] in sorter.available_parsers:
|
||||||
self.valid_files.append(
|
self.valid_files.append(
|
||||||
@@ -65,17 +116,9 @@ class BackGroundBookSearch(QtCore.QThread):
|
|||||||
|
|
||||||
def initiate_threads():
|
def initiate_threads():
|
||||||
_pool = Pool(5)
|
_pool = Pool(5)
|
||||||
_pool.map(traverse_directory, self.data_list)
|
_pool.map(traverse_directory, self.valid_directories)
|
||||||
_pool.close()
|
_pool.close()
|
||||||
_pool.join()
|
_pool.join()
|
||||||
|
|
||||||
initiate_threads()
|
initiate_threads()
|
||||||
|
print(len(self.valid_files), 'books found')
|
||||||
# TODO
|
|
||||||
# Change existing sorter module functionality to handle
|
|
||||||
# preset tags
|
|
||||||
# Change database to accomodate User Tags, Folder Name, Folder Tags
|
|
||||||
|
|
||||||
# self.valid_files will now be added to the database
|
|
||||||
# and models will be rebuilt accordingly
|
|
||||||
# Coming soon to a commit near you
|
|
||||||
|
87
widgets.py
87
widgets.py
@@ -1,9 +1,31 @@
|
|||||||
#!usr/bin/env python3
|
#!usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017 BasioMeusPuga
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Reading modes
|
||||||
|
# Double page, Continuous etc
|
||||||
|
# Especially for comics
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||||
|
|
||||||
from resources import resources, pie_chart
|
from resources import pie_chart
|
||||||
|
|
||||||
|
|
||||||
class BookToolBar(QtWidgets.QToolBar):
|
class BookToolBar(QtWidgets.QToolBar):
|
||||||
@@ -31,6 +53,10 @@ class BookToolBar(QtWidgets.QToolBar):
|
|||||||
self.fullscreenButton = QtWidgets.QAction(
|
self.fullscreenButton = QtWidgets.QAction(
|
||||||
QtGui.QIcon.fromTheme('view-fullscreen'),
|
QtGui.QIcon.fromTheme('view-fullscreen'),
|
||||||
'Fullscreen', self)
|
'Fullscreen', self)
|
||||||
|
self.bookmarkButton = QtWidgets.QAction(
|
||||||
|
QtGui.QIcon.fromTheme('bookmarks'),
|
||||||
|
'Bookmark', self)
|
||||||
|
self.bookmarkButton.setObjectName('bookmarkButton')
|
||||||
self.resetProfile = QtWidgets.QAction(
|
self.resetProfile = QtWidgets.QAction(
|
||||||
QtGui.QIcon.fromTheme('view-refresh'),
|
QtGui.QIcon.fromTheme('view-refresh'),
|
||||||
'Reset profile', self)
|
'Reset profile', self)
|
||||||
@@ -40,6 +66,8 @@ class BookToolBar(QtWidgets.QToolBar):
|
|||||||
self.fontButton.setCheckable(True)
|
self.fontButton.setCheckable(True)
|
||||||
self.fontButton.triggered.connect(self.toggle_font_settings)
|
self.fontButton.triggered.connect(self.toggle_font_settings)
|
||||||
self.addSeparator()
|
self.addSeparator()
|
||||||
|
self.addAction(self.bookmarkButton)
|
||||||
|
self.bookmarkButton.setCheckable(True)
|
||||||
self.addAction(self.fullscreenButton)
|
self.addAction(self.fullscreenButton)
|
||||||
|
|
||||||
# Font modification
|
# Font modification
|
||||||
@@ -191,6 +219,7 @@ class BookToolBar(QtWidgets.QToolBar):
|
|||||||
self.searchBarAction = self.addWidget(self.searchBar)
|
self.searchBarAction = self.addWidget(self.searchBar)
|
||||||
|
|
||||||
self.bookActions = [
|
self.bookActions = [
|
||||||
|
self.bookmarkButton,
|
||||||
self.fullscreenButton,
|
self.fullscreenButton,
|
||||||
self.tocBoxAction,
|
self.tocBoxAction,
|
||||||
self.searchBarAction]
|
self.searchBarAction]
|
||||||
@@ -269,6 +298,11 @@ class LibraryToolBar(QtWidgets.QToolBar):
|
|||||||
QtGui.QIcon.fromTheme('table'), 'View as table', self)
|
QtGui.QIcon.fromTheme('table'), 'View as table', self)
|
||||||
self.tableViewButton.setCheckable(True)
|
self.tableViewButton.setCheckable(True)
|
||||||
|
|
||||||
|
self.libraryFilterButton = QtWidgets.QToolButton(self)
|
||||||
|
self.libraryFilterButton.setIcon(QtGui.QIcon.fromTheme('view-readermode'))
|
||||||
|
self.libraryFilterButton.setText('Filter library')
|
||||||
|
self.libraryFilterButton.setToolTip('Filter library')
|
||||||
|
|
||||||
# Auto unchecks the other QToolButton in case of clicking
|
# Auto unchecks the other QToolButton in case of clicking
|
||||||
self.viewButtons = QtWidgets.QActionGroup(self)
|
self.viewButtons = QtWidgets.QActionGroup(self)
|
||||||
self.viewButtons.setExclusive(True)
|
self.viewButtons.setExclusive(True)
|
||||||
@@ -282,6 +316,8 @@ class LibraryToolBar(QtWidgets.QToolBar):
|
|||||||
self.addAction(self.coverViewButton)
|
self.addAction(self.coverViewButton)
|
||||||
self.addAction(self.tableViewButton)
|
self.addAction(self.tableViewButton)
|
||||||
self.addSeparator()
|
self.addSeparator()
|
||||||
|
self.addWidget(self.libraryFilterButton)
|
||||||
|
self.addSeparator()
|
||||||
self.addAction(self.settingsButton)
|
self.addAction(self.settingsButton)
|
||||||
|
|
||||||
# Filter
|
# Filter
|
||||||
@@ -346,8 +382,10 @@ class Tab(QtWidgets.QWidget):
|
|||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.metadata = metadata # Save progress data into this dictionary
|
self.metadata = metadata # Save progress data into this dictionary
|
||||||
|
|
||||||
self.gridLayout = QtWidgets.QGridLayout(self)
|
self.masterLayout = QtWidgets.QHBoxLayout(self)
|
||||||
self.gridLayout.setObjectName("gridLayout")
|
self.horzLayout = QtWidgets.QSplitter(self)
|
||||||
|
self.horzLayout.setOrientation(QtCore.Qt.Horizontal)
|
||||||
|
self.masterLayout.addWidget(self.horzLayout)
|
||||||
|
|
||||||
position = self.metadata['position']
|
position = self.metadata['position']
|
||||||
|
|
||||||
@@ -374,6 +412,7 @@ class Tab(QtWidgets.QWidget):
|
|||||||
self.contentView.loadImage(chapter_content)
|
self.contentView.loadImage(chapter_content)
|
||||||
else:
|
else:
|
||||||
self.contentView = PliantQTextBrowser(self.window())
|
self.contentView = PliantQTextBrowser(self.window())
|
||||||
|
# print(dir(self.contentView.document())) ## TODO USE this for modifying formatting and searching
|
||||||
|
|
||||||
relative_path_root = os.path.join(
|
relative_path_root = os.path.join(
|
||||||
self.window().temp_dir.path(), self.metadata['hash'])
|
self.window().temp_dir.path(), self.metadata['hash'])
|
||||||
@@ -393,9 +432,20 @@ class Tab(QtWidgets.QWidget):
|
|||||||
self.contentView.setHorizontalScrollBarPolicy(
|
self.contentView.setHorizontalScrollBarPolicy(
|
||||||
QtCore.Qt.ScrollBarAlwaysOff)
|
QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
|
|
||||||
|
# Create the dock widget for context specific display
|
||||||
|
self.dockWidget = QtWidgets.QDockWidget(self)
|
||||||
|
self.dockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable)
|
||||||
|
self.dockWidget.setFloating(False)
|
||||||
|
self.dockListWidget = QtWidgets.QListWidget()
|
||||||
|
self.dockListWidget.setResizeMode(QtWidgets.QListWidget.Adjust)
|
||||||
|
self.dockListWidget.setMaximumWidth(350)
|
||||||
|
self.dockWidget.setWidget(self.dockListWidget)
|
||||||
|
self.dockWidget.hide()
|
||||||
|
|
||||||
self.generate_keyboard_shortcuts()
|
self.generate_keyboard_shortcuts()
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.contentView, 0, 0, 1, 1)
|
self.horzLayout.addWidget(self.contentView)
|
||||||
|
self.horzLayout.addWidget(self.dockWidget)
|
||||||
title = self.metadata['title']
|
title = self.metadata['title']
|
||||||
self.parent.addTab(self, title)
|
self.parent.addTab(self, title)
|
||||||
|
|
||||||
@@ -462,6 +512,11 @@ class Tab(QtWidgets.QWidget):
|
|||||||
self.contentView.clear()
|
self.contentView.clear()
|
||||||
self.contentView.setHtml(required_content)
|
self.contentView.setHtml(required_content)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# This here. Use it for stuff.
|
||||||
|
# self.contentView.document().begin().blockFormat().setLineHeight(1000, QtGui.QTextBlockFormat.FixedHeight)
|
||||||
|
# self.contentView.document().end().blockFormat().setLineHeight(1000, QtGui.QTextBlockFormat.FixedHeight)
|
||||||
|
|
||||||
def format_view(self, font, font_size, foreground, background, padding):
|
def format_view(self, font, font_size, foreground, background, padding):
|
||||||
if self.are_we_doing_images_only:
|
if self.are_we_doing_images_only:
|
||||||
# Tab color does not need to be set separately in case
|
# Tab color does not need to be set separately in case
|
||||||
@@ -473,11 +528,22 @@ class Tab(QtWidgets.QWidget):
|
|||||||
self.contentView.resizeEvent()
|
self.contentView.resizeEvent()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
# print(dir(self.contentView.document().begin().blockFormat())) ## TODO Line Height here
|
||||||
|
# self.contentView.document().begin().blockFormat().setLineHeight(1000, QtGui.QTextBlockFormat.FixedHeight)
|
||||||
|
# self.contentView.document().end().blockFormat().setLineHeight(1000, QtGui.QTextBlockFormat.FixedHeight)
|
||||||
self.contentView.setViewportMargins(padding, 0, padding, 0)
|
self.contentView.setViewportMargins(padding, 0, padding, 0)
|
||||||
self.contentView.setStyleSheet(
|
self.contentView.setStyleSheet(
|
||||||
"QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}".format(
|
"QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}".format(
|
||||||
font, font_size, foreground.name(), background.name()))
|
font, font_size, foreground.name(), background.name()))
|
||||||
|
|
||||||
|
def toggle_bookmarks(self):
|
||||||
|
self.dockWidget.setWindowTitle('Bookmarks')
|
||||||
|
|
||||||
|
if self.dockWidget.isVisible():
|
||||||
|
self.dockWidget.hide()
|
||||||
|
else:
|
||||||
|
self.dockWidget.show()
|
||||||
|
|
||||||
def sneaky_change(self):
|
def sneaky_change(self):
|
||||||
direction = -1
|
direction = -1
|
||||||
if self.sender().objectName() == 'nextChapter':
|
if self.sender().objectName() == 'nextChapter':
|
||||||
@@ -661,6 +727,7 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate):
|
|||||||
def __init__(self, temp_dir, parent=None):
|
def __init__(self, temp_dir, parent=None):
|
||||||
super(LibraryDelegate, self).__init__(parent)
|
super(LibraryDelegate, self).__init__(parent)
|
||||||
self.temp_dir = temp_dir
|
self.temp_dir = temp_dir
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
# This is a hint for the future
|
# This is a hint for the future
|
||||||
@@ -673,27 +740,27 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate):
|
|||||||
position = index.data(QtCore.Qt.UserRole + 7)
|
position = index.data(QtCore.Qt.UserRole + 7)
|
||||||
|
|
||||||
# The shadow pixmap currently is set to 420 x 600
|
# The shadow pixmap currently is set to 420 x 600
|
||||||
|
# Only draw the cover shadow in case the setting is enabled
|
||||||
|
if self.parent.settings['cover_shadows']:
|
||||||
shadow_pixmap = QtGui.QPixmap()
|
shadow_pixmap = QtGui.QPixmap()
|
||||||
shadow_pixmap.load(':/images/gray-shadow.png')
|
shadow_pixmap.load(':/images/gray-shadow.png')
|
||||||
shadow_pixmap = shadow_pixmap.scaled(160, 230, QtCore.Qt.IgnoreAspectRatio)
|
shadow_pixmap = shadow_pixmap.scaled(160, 230, QtCore.Qt.IgnoreAspectRatio)
|
||||||
shadow_x = option.rect.topLeft().x() + 10
|
shadow_x = option.rect.topLeft().x() + 10
|
||||||
shadow_y = option.rect.topLeft().y() - 5
|
shadow_y = option.rect.topLeft().y() - 5
|
||||||
|
painter.setOpacity(.7)
|
||||||
|
painter.drawPixmap(shadow_x, shadow_y, shadow_pixmap)
|
||||||
|
painter.setOpacity(1)
|
||||||
|
|
||||||
if not file_exists:
|
if not file_exists:
|
||||||
painter.setOpacity(.7)
|
painter.setOpacity(.7)
|
||||||
painter.drawPixmap(shadow_x, shadow_y, shadow_pixmap)
|
|
||||||
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
|
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
|
||||||
painter.setOpacity(1)
|
|
||||||
|
|
||||||
read_icon = pie_chart.pixmapper(-1, None, None, 36)
|
read_icon = pie_chart.pixmapper(-1, None, None, 36)
|
||||||
x_draw = option.rect.bottomRight().x() - 30
|
x_draw = option.rect.bottomRight().x() - 30
|
||||||
y_draw = option.rect.bottomRight().y() - 35
|
y_draw = option.rect.bottomRight().y() - 35
|
||||||
painter.drawPixmap(x_draw, y_draw, read_icon)
|
painter.drawPixmap(x_draw, y_draw, read_icon)
|
||||||
|
painter.setOpacity(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
painter.setOpacity(.8)
|
|
||||||
painter.drawPixmap(shadow_x, shadow_y, shadow_pixmap)
|
|
||||||
painter.setOpacity(1)
|
|
||||||
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
|
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
|
||||||
if position:
|
if position:
|
||||||
current_chapter = position['current_chapter']
|
current_chapter = position['current_chapter']
|
||||||
|
Reference in New Issue
Block a user