Fairly substantial rewrite.
This commit is contained in:
15
TODO
15
TODO
@@ -1,13 +1,12 @@
|
||||
TODO
|
||||
Options:
|
||||
Automatic library management
|
||||
✓ Recursive file addition
|
||||
Auto deletion
|
||||
Recursive file addition
|
||||
Add only one file type if multiple are present
|
||||
Remember files
|
||||
Check files (hashes) upon restart
|
||||
Show what on startup
|
||||
Draw shadows
|
||||
✓ Remember files
|
||||
✓ Check files (hashes) upon restart
|
||||
✓ Draw shadows
|
||||
Library:
|
||||
✓ sqlite3 for cover images cache
|
||||
✓ sqlite3 for storing metadata
|
||||
@@ -19,6 +18,7 @@ TODO
|
||||
✓ Tie file deletion and tab closing to model updates
|
||||
✓ Create separate thread for parser - Show progress in main window
|
||||
? Create emblem per filetype
|
||||
Memory management
|
||||
Table view
|
||||
Ignore a / the / numbers for sorting purposes
|
||||
Put the path in the scope of the search
|
||||
@@ -27,6 +27,7 @@ TODO
|
||||
Information dialog widget
|
||||
Context menu: Cache, Read, Edit database, delete, Mark read/unread
|
||||
Set focus to newly added file
|
||||
Add capability to sort by new
|
||||
Reading:
|
||||
✓ Drop down for TOC
|
||||
✓ Override the keypress event of the textedit
|
||||
@@ -45,6 +46,7 @@ TODO
|
||||
Record progress
|
||||
Pagination
|
||||
Set context menu for definitions and the like
|
||||
Hide progressbar
|
||||
Filetypes:
|
||||
✓ cbz, cbr support
|
||||
✓ Keep font settings enabled but only for background color
|
||||
@@ -59,4 +61,5 @@ TODO
|
||||
Get ISBN using python-isbnlib
|
||||
Other:
|
||||
✓ Define every widget in code
|
||||
✓ Include icons for emblems
|
||||
✓ Include icons for emblems
|
||||
Shift to logging instead of print statements
|
317
__main__.py
317
__main__.py
@@ -1,5 +1,24 @@
|
||||
#!/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 sys
|
||||
|
||||
@@ -8,9 +27,9 @@ from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
import sorter
|
||||
import database
|
||||
|
||||
from resources import mainwindow
|
||||
from resources import mainwindow, resources
|
||||
from widgets import LibraryToolBar, BookToolBar, Tab, LibraryDelegate
|
||||
from threaded import BackGroundTabUpdate, BackGroundBookAddition
|
||||
from threaded import BackGroundTabUpdate, BackGroundBookAddition, BackGroundBookDeletion
|
||||
from library import Library
|
||||
from settings import Settings
|
||||
|
||||
@@ -23,8 +42,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.setupUi(self)
|
||||
|
||||
# Empty variables that will be infested soon
|
||||
self.current_view = None
|
||||
self.last_open_books = None
|
||||
self.settings = {}
|
||||
self.last_open_tab = None
|
||||
self.last_open_path = None
|
||||
self.thread = None # Background Thread
|
||||
@@ -32,8 +50,17 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.display_profiles = None
|
||||
self.current_profile_index = None
|
||||
self.database_path = None
|
||||
self.table_header_sizes = None
|
||||
self.settings_dialog_settings = None
|
||||
self.library_filter_menu = 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
|
||||
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
|
||||
self.settings_dialog = SettingsUI(self)
|
||||
|
||||
# Create and right align the statusbar label widget
|
||||
self.statusMessage = QtWidgets.QLabel()
|
||||
# Statusbar widgets
|
||||
self.statusMessage.setObjectName('statusMessage')
|
||||
self.statusBar.addPermanentWidget(self.statusMessage)
|
||||
self.sorterProgress = QtWidgets.QProgressBar()
|
||||
self.sorterProgress.setMaximumWidth(300)
|
||||
self.sorterProgress.setObjectName('sorterProgress')
|
||||
sorter.progressbar = self.sorterProgress # This is so that updates can be
|
||||
# connected to setValue
|
||||
self.statusBar.addWidget(self.sorterProgress)
|
||||
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
|
||||
self.temp_dir = QtCore.QTemporaryDir()
|
||||
|
||||
# Init the Library
|
||||
self.lib_ref = Library(self)
|
||||
|
||||
# Toolbar display
|
||||
# Maybe make this a persistent option
|
||||
self.settings['show_toolbars'] = True
|
||||
|
||||
# Library toolbar
|
||||
self.libraryToolBar = LibraryToolBar(self)
|
||||
self.libraryToolBar.addButton.triggered.connect(self.add_books)
|
||||
self.libraryToolBar.deleteButton.triggered.connect(self.delete_books)
|
||||
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_table_proxy_model)
|
||||
self.libraryToolBar.sortingBox.activated.connect(self.lib_ref.update_proxymodel)
|
||||
|
||||
if self.current_view == 0:
|
||||
self.libraryToolBar.coverViewButton.trigger()
|
||||
elif self.current_view == 1:
|
||||
self.libraryToolBar.tableViewButton.trigger()
|
||||
|
||||
self.libraryToolBar.libraryFilterButton.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
||||
self.addToolBar(self.libraryToolBar)
|
||||
|
||||
if self.settings['current_view'] == 0:
|
||||
self.libraryToolBar.coverViewButton.trigger()
|
||||
else:
|
||||
self.libraryToolBar.tableViewButton.trigger()
|
||||
|
||||
# Book toolbar
|
||||
self.bookToolBar = BookToolBar(self)
|
||||
self.bookToolBar.bookmarkButton.triggered.connect(self.toggle_dock_widget)
|
||||
self.bookToolBar.fullscreenButton.triggered.connect(self.set_fullscreen)
|
||||
|
||||
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)
|
||||
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.setObjectName('reloadLibrary')
|
||||
self.reloadLibrary.setAutoRaise(True)
|
||||
self.reloadLibrary.setPopupMode(QtWidgets.QToolButton.InstantPopup)
|
||||
self.reloadLibrary.triggered.connect(self.switch_library_view)
|
||||
self.reloadLibrary.clicked.connect(self.settings_dialog.start_library_scan)
|
||||
# self.reloadLibrary.clicked.connect(self.cull_covers) # TODO
|
||||
|
||||
self.tabWidget.tabBar().setTabButton(
|
||||
0, QtWidgets.QTabBar.RightSide, self.reloadLibrary)
|
||||
@@ -140,7 +187,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.listView.setMouseTracking(True)
|
||||
self.listView.verticalScrollBar().setSingleStep(9)
|
||||
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
|
||||
self.tableView.doubleClicked.connect(self.library_doubleclick)
|
||||
@@ -148,11 +195,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
QtWidgets.QHeaderView.Interactive)
|
||||
self.tableView.horizontalHeader().setSortIndicator(0, QtCore.Qt.AscendingOrder)
|
||||
self.tableView.horizontalHeader().setHighlightSections(False)
|
||||
if self.table_header_sizes:
|
||||
for count, i in enumerate(self.table_header_sizes):
|
||||
if self.settings['main_window_headers']:
|
||||
for count, i in enumerate(self.settings['main_window_headers']):
|
||||
self.tableView.horizontalHeader().resizeSection(count, int(i))
|
||||
self.tableView.horizontalHeader().setStretchLastSection(True)
|
||||
self.tableView.horizontalHeader().setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
|
||||
|
||||
# Keyboard shortcuts
|
||||
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.
|
||||
# Then set the value to None for the next run
|
||||
self.open_files(self.last_open_books)
|
||||
self.last_open_books = None
|
||||
self.open_files(self.settings['last_open_books'])
|
||||
|
||||
# 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):
|
||||
if event:
|
||||
@@ -206,58 +278,95 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
|
||||
def add_books(self):
|
||||
# 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(
|
||||
self, 'Open file', self.last_open_path,
|
||||
f'eBooks ({self.available_parsers})')
|
||||
|
||||
if opened_files[0]:
|
||||
self.last_open_path = os.path.dirname(opened_files[0][0])
|
||||
self.sorterProgress.setVisible(True)
|
||||
self.statusMessage.setText('Adding books...')
|
||||
self.thread = BackGroundBookAddition(self, opened_files[0], self.database_path)
|
||||
self.thread.finished.connect(self.move_on)
|
||||
self.thread.start()
|
||||
if not opened_files[0]:
|
||||
return
|
||||
|
||||
def move_on(self):
|
||||
self.sorterProgress.setVisible(False)
|
||||
self.lib_ref.create_table_model()
|
||||
self.lib_ref.create_proxymodel()
|
||||
self.settings_dialog.okButton.setEnabled(False)
|
||||
self.reloadLibrary.setEnabled(False)
|
||||
|
||||
self.last_open_path = os.path.dirname(opened_files[0][0])
|
||||
self.sorterProgress.setVisible(True)
|
||||
self.statusMessage.setText('Adding books...')
|
||||
self.thread = BackGroundBookAddition(
|
||||
opened_files[0], self.database_path, False, self)
|
||||
self.thread.finished.connect(self.move_on)
|
||||
self.thread.start()
|
||||
|
||||
def delete_books(self):
|
||||
# TODO
|
||||
# Use maptosource() here to get the view_model
|
||||
# indices selected in the listView
|
||||
# Implement this for the tableview
|
||||
# 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()
|
||||
if selected_books:
|
||||
def ifcontinue(box_button):
|
||||
if box_button.text() == '&Yes':
|
||||
selected_hashes = []
|
||||
for i in selected_books:
|
||||
data = i.data(QtCore.Qt.UserRole + 3)
|
||||
selected_hashes.append(data['hash'])
|
||||
# Get a list of QItemSelection objects
|
||||
# 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())
|
||||
|
||||
database.DatabaseFunctions(
|
||||
self.database_path).delete_from_database(selected_hashes)
|
||||
if not selected_books:
|
||||
return
|
||||
|
||||
self.lib_ref.generate_model('build')
|
||||
self.lib_ref.create_table_model()
|
||||
self.lib_ref.create_proxymodel()
|
||||
# Deal with message box selection
|
||||
def ifcontinue(box_button):
|
||||
if box_button.text() != '&Yes':
|
||||
return
|
||||
|
||||
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_()
|
||||
# Generate list of selected indexes and deletable 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.create_table_model()
|
||||
self.lib_ref.create_proxymodel()
|
||||
|
||||
def switch_library_view(self):
|
||||
if self.libraryToolBar.coverViewButton.isChecked():
|
||||
@@ -273,8 +382,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
if self.tabWidget.currentIndex() == 0:
|
||||
|
||||
self.resizeEvent()
|
||||
self.bookToolBar.hide()
|
||||
self.libraryToolBar.show()
|
||||
if self.settings['show_toolbars']:
|
||||
self.bookToolBar.hide()
|
||||
self.libraryToolBar.show()
|
||||
|
||||
if self.lib_ref.proxy_model:
|
||||
# Making the proxy model available doesn't affect
|
||||
@@ -282,8 +392,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.statusMessage.setText(
|
||||
str(self.lib_ref.proxy_model.rowCount()) + ' Books')
|
||||
else:
|
||||
self.bookToolBar.show()
|
||||
self.libraryToolBar.hide()
|
||||
|
||||
if self.settings['show_toolbars']:
|
||||
self.bookToolBar.show()
|
||||
self.libraryToolBar.hide()
|
||||
|
||||
current_metadata = self.tabWidget.widget(
|
||||
self.tabWidget.currentIndex()).metadata
|
||||
@@ -375,14 +487,24 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
current_tab_widget = self.tabWidget.widget(current_tab)
|
||||
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()
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
# 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,
|
||||
'reading',
|
||||
self.database_path,
|
||||
True,
|
||||
self.temp_dir.path()).initiate_threads()
|
||||
|
||||
found_a_focusable_tab = False
|
||||
@@ -609,19 +732,69 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
else:
|
||||
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):
|
||||
# All tabs must be iterated upon here
|
||||
self.hide()
|
||||
self.settings_dialog.hide()
|
||||
self.temp_dir.remove()
|
||||
|
||||
self.last_open_books = []
|
||||
if self.tabWidget.count() > 1:
|
||||
self.settings['last_open_books'] = []
|
||||
if self.tabWidget.count() > 1 and self.settings['remember_files']:
|
||||
|
||||
all_metadata = []
|
||||
for i in range(1, self.tabWidget.count()):
|
||||
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)
|
||||
|
||||
Settings(self).save_settings()
|
||||
|
78
database.py
78
database.py
@@ -1,5 +1,21 @@
|
||||
#!/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 pickle
|
||||
import sqlite3
|
||||
@@ -16,13 +32,20 @@ class DatabaseInit:
|
||||
|
||||
def create_database(self):
|
||||
# TODO
|
||||
# Add a separate column for directory tags
|
||||
# Add separate columns for:
|
||||
# directory tags
|
||||
# bookmarks
|
||||
# date added
|
||||
# addition mode
|
||||
self.database.execute(
|
||||
"CREATE TABLE books \
|
||||
(id INTEGER PRIMARY KEY, Title TEXT, Author TEXT, Year INTEGER, \
|
||||
Path TEXT, Position BLOB, ISBN TEXT, Tags TEXT, Hash TEXT, CoverImage BLOB)")
|
||||
|
||||
# CheckState is the standard QtCore.Qt.Checked / Unchecked
|
||||
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.close()
|
||||
|
||||
@@ -33,15 +56,22 @@ class DatabaseFunctions:
|
||||
self.database = sqlite3.connect(database_path)
|
||||
|
||||
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:
|
||||
path = i[0]
|
||||
name = i[1]
|
||||
tags = i[2]
|
||||
is_checked = i[3]
|
||||
|
||||
sql_command = (
|
||||
"INSERT OR REPLACE INTO directories (ID, Path, Name, Tags)\
|
||||
VALUES ((SELECT ID FROM directories WHERE Path = ?), ?, ?, ?)")
|
||||
self.database.execute(sql_command, [path, path, name, tags])
|
||||
"INSERT OR REPLACE INTO directories (ID, Path, Name, Tags, CheckState)\
|
||||
VALUES ((SELECT ID FROM directories WHERE Path = ?), ?, ?, ?, ?)")
|
||||
self.database.execute(sql_command, [path, path, name, tags, is_checked])
|
||||
|
||||
self.database.commit()
|
||||
self.close_database()
|
||||
@@ -61,9 +91,15 @@ class DatabaseFunctions:
|
||||
path = i[1]['path']
|
||||
cover = i[1]['cover_image']
|
||||
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 = (
|
||||
"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
|
||||
if cover:
|
||||
@@ -72,7 +108,7 @@ class DatabaseFunctions:
|
||||
self.database.execute(
|
||||
sql_command_add,
|
||||
[title, author, year,
|
||||
path, isbn, book_hash, cover_insert])
|
||||
path, isbn, tags, book_hash, cover_insert])
|
||||
|
||||
self.database.commit()
|
||||
self.close_database()
|
||||
@@ -121,8 +157,7 @@ class DatabaseFunctions:
|
||||
else:
|
||||
return None
|
||||
|
||||
# except sqlite3.OperationalError:
|
||||
except KeyError:
|
||||
except (KeyError, sqlite3.OperationalError):
|
||||
print('SQLite is in rebellion, Commander')
|
||||
|
||||
self.close_database()
|
||||
@@ -136,7 +171,9 @@ class DatabaseFunctions:
|
||||
|
||||
sql_command = "UPDATE books SET Position = ? WHERE Hash = ?"
|
||||
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:
|
||||
print('SQLite is in rebellion, Commander')
|
||||
return
|
||||
@@ -144,22 +181,19 @@ class DatabaseFunctions:
|
||||
self.database.commit()
|
||||
self.close_database()
|
||||
|
||||
def delete_from_database(self, file_hashes):
|
||||
# file_hashes is expected as a list that will be iterated upon
|
||||
# This should enable multiple deletion
|
||||
def delete_from_database(self, column_name, target_data):
|
||||
# target_data is an iterable
|
||||
|
||||
first = file_hashes[0]
|
||||
sql_command = f"DELETE FROM books WHERE Hash = '{first}'"
|
||||
|
||||
if len(file_hashes) > 1:
|
||||
for i in file_hashes[1:]:
|
||||
sql_command += f" OR Hash = '{i}'"
|
||||
|
||||
self.database.execute(sql_command)
|
||||
if column_name == '*':
|
||||
self.database.execute('DELETE FROM books')
|
||||
else:
|
||||
sql_command = f'DELETE FROM books WHERE {column_name} = ?'
|
||||
for i in target_data:
|
||||
self.database.execute(sql_command, (i,))
|
||||
|
||||
self.database.commit()
|
||||
self.close_database()
|
||||
|
||||
|
||||
def close_database(self):
|
||||
self.database.execute("VACUUM")
|
||||
self.database.close()
|
||||
|
112
library.py
112
library.py
@@ -1,16 +1,36 @@
|
||||
#!/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 pickle
|
||||
import database
|
||||
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
from models import LibraryItemModel, MostExcellentTableModel, TableProxyModel
|
||||
from PyQt5 import QtGui, QtCore
|
||||
from models import MostExcellentTableModel, TableProxyModel
|
||||
|
||||
|
||||
class Library:
|
||||
def __init__(self, parent):
|
||||
self.parent_window = parent
|
||||
self.parent = parent
|
||||
self.view_model = None
|
||||
self.proxy_model = None
|
||||
self.table_model = None
|
||||
@@ -18,16 +38,12 @@ class Library:
|
||||
self.table_rows = []
|
||||
|
||||
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':
|
||||
self.table_rows = []
|
||||
self.view_model = LibraryItemModel()
|
||||
self.view_model = QtGui.QStandardItemModel()
|
||||
|
||||
books = database.DatabaseFunctions(
|
||||
self.parent_window.database_path).fetch_data(
|
||||
self.parent.database_path).fetch_data(
|
||||
('*',),
|
||||
'books',
|
||||
{'Title': ''},
|
||||
@@ -43,20 +59,19 @@ class Library:
|
||||
# database using background threads
|
||||
|
||||
books = []
|
||||
for i in parsed_books:
|
||||
parsed_title = parsed_books[i]['title']
|
||||
parsed_author = parsed_books[i]['author']
|
||||
parsed_year = parsed_books[i]['year']
|
||||
parsed_path = parsed_books[i]['path']
|
||||
parsed_position = None
|
||||
parsed_isbn = parsed_books[i]['isbn']
|
||||
parsed_tags = None
|
||||
parsed_hash = i
|
||||
parsed_cover = parsed_books[i]['cover_image']
|
||||
for i in parsed_books.items():
|
||||
# Scheme
|
||||
# 1: Title, 2: Author, 3: Year, 4: Path
|
||||
# 5: Position, 6: isbn, 7: Tags, 8: Hash
|
||||
# 9: CoverImage
|
||||
|
||||
_tags = i[1]['tags']
|
||||
if _tags:
|
||||
_tags = ', '.join([j for j in _tags if j])
|
||||
|
||||
books.append([
|
||||
None, parsed_title, parsed_author, parsed_year, parsed_path,
|
||||
parsed_position, parsed_isbn, parsed_tags, parsed_hash, parsed_cover])
|
||||
None, i[1]['title'], i[1]['author'], i[1]['year'], i[1]['path'],
|
||||
None, i[1]['isbn'], _tags, i[0], i[1]['cover_image']])
|
||||
|
||||
else:
|
||||
return
|
||||
@@ -134,7 +149,7 @@ class Library:
|
||||
def create_table_model(self):
|
||||
table_header = ['Title', 'Author', 'Status', 'Year', 'Tags']
|
||||
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()
|
||||
|
||||
def create_table_proxy_model(self):
|
||||
@@ -142,38 +157,73 @@ class Library:
|
||||
self.table_proxy_model.setSourceModel(self.table_model)
|
||||
self.table_proxy_model.setSortCaseSensitivity(False)
|
||||
self.table_proxy_model.sort(0, QtCore.Qt.AscendingOrder)
|
||||
self.parent_window.tableView.setModel(self.table_proxy_model)
|
||||
self.parent_window.tableView.horizontalHeader().setSortIndicator(
|
||||
self.parent.tableView.setModel(self.table_proxy_model)
|
||||
self.parent.tableView.horizontalHeader().setSortIndicator(
|
||||
0, QtCore.Qt.AscendingOrder)
|
||||
self.update_table_proxy_model()
|
||||
|
||||
def update_table_proxy_model(self):
|
||||
self.table_proxy_model.invalidateFilter()
|
||||
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
|
||||
# text in the line edit changes. So I guess it is needed.
|
||||
self.table_proxy_model.setFilterFixedString(
|
||||
self.parent_window.libraryToolBar.searchBar.text())
|
||||
self.parent.libraryToolBar.searchBar.text())
|
||||
|
||||
def create_proxymodel(self):
|
||||
self.proxy_model = QtCore.QSortFilterProxyModel()
|
||||
self.proxy_model.setSourceModel(self.view_model)
|
||||
self.proxy_model.setSortCaseSensitivity(False)
|
||||
s = QtCore.QSize(160, 250) # Set icon sizing here
|
||||
self.parent_window.listView.setIconSize(s)
|
||||
self.parent_window.listView.setModel(self.proxy_model)
|
||||
self.parent.listView.setIconSize(s)
|
||||
self.parent.listView.setModel(self.proxy_model)
|
||||
self.update_proxymodel()
|
||||
|
||||
def update_proxymodel(self):
|
||||
self.proxy_model.setFilterRole(QtCore.Qt.UserRole + 4)
|
||||
self.proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
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')
|
||||
|
||||
# Sorting according to roles and the drop down in the library
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
class LibraryItemModel(QtGui.QStandardItemModel, QtCore.QAbstractItemModel):
|
||||
class ItemProxyModel(QtCore.QSortFilterProxyModel):
|
||||
# TODO
|
||||
# Implement filterAcceptsRow
|
||||
|
||||
def __init__(self, parent=None):
|
||||
# We're using this to be able to access the match() method
|
||||
super(LibraryItemModel, self).__init__(parent)
|
||||
super(ItemProxyModel, self).__init__(parent)
|
||||
|
||||
|
||||
class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
||||
@@ -39,7 +57,7 @@ class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
||||
if not index.isValid():
|
||||
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
|
||||
if self.temp_dir:
|
||||
if role == QtCore.Qt.DecorationRole and index.column() == 2:
|
||||
@@ -71,7 +89,8 @@ class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
||||
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()]
|
||||
return value
|
||||
|
||||
@@ -84,8 +103,8 @@ class MostExcellentTableModel(QtCore.QAbstractTableModel):
|
||||
return None
|
||||
|
||||
def flags(self, index):
|
||||
# In case of the settings model, model column index 1+ are editable
|
||||
if not self.temp_dir and index.column() != 0:
|
||||
# This means only the Tags column is editable
|
||||
if self.temp_dir and index.column() == 4:
|
||||
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
|
||||
else:
|
||||
# These are standard select but don't edit values
|
||||
@@ -124,10 +143,151 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
||||
model = self.sourceModel()
|
||||
|
||||
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:
|
||||
if self.filter_string in i:
|
||||
return True
|
||||
|
||||
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
|
||||
|
||||
# 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 time
|
||||
import collections
|
||||
@@ -49,7 +68,10 @@ class ParseCBR:
|
||||
return cover_image
|
||||
|
||||
def get_isbn(self):
|
||||
return None
|
||||
return
|
||||
|
||||
def get_tags(self):
|
||||
return
|
||||
|
||||
def get_contents(self):
|
||||
file_settings = {
|
||||
|
@@ -1,5 +1,24 @@
|
||||
#!/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 time
|
||||
import zipfile
|
||||
@@ -52,7 +71,10 @@ class ParseCBZ:
|
||||
return cover_image
|
||||
|
||||
def get_isbn(self):
|
||||
return None
|
||||
return
|
||||
|
||||
def get_tags(self):
|
||||
return
|
||||
|
||||
def get_contents(self):
|
||||
file_settings = {
|
||||
|
@@ -1,12 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
# Every parser is supposed to have the following methods, even if they return None:
|
||||
# read_book()
|
||||
# get_title()
|
||||
# get_author()
|
||||
# get_year()
|
||||
# get_cover_image()
|
||||
# get_isbn()
|
||||
# get_contents() - Should return a tuple with 0: TOC 1: Deletable temp_directory
|
||||
|
||||
# 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 re
|
||||
@@ -40,13 +48,13 @@ class ParseEPUB:
|
||||
try:
|
||||
return self.book.metadata['http://purl.org/dc/elements/1.1/']['creator'][0][0]
|
||||
except KeyError:
|
||||
return None
|
||||
return
|
||||
|
||||
def get_year(self):
|
||||
try:
|
||||
return self.book.metadata['http://purl.org/dc/elements/1.1/']['date'][0][0][:4]
|
||||
except KeyError:
|
||||
return None
|
||||
return
|
||||
|
||||
def get_cover_image(self):
|
||||
# Get cover image
|
||||
@@ -89,7 +97,6 @@ class ParseEPUB:
|
||||
if i.media_type == 'image/jpeg' or i.media_type == 'image/png':
|
||||
return i.get_content()
|
||||
|
||||
|
||||
def get_isbn(self):
|
||||
try:
|
||||
identifier = self.book.metadata['http://purl.org/dc/elements/1.1/']['identifier']
|
||||
@@ -99,7 +106,15 @@ class ParseEPUB:
|
||||
isbn = i[0]
|
||||
return isbn
|
||||
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):
|
||||
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.setObjectName("gridLayout_3")
|
||||
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.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.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.tableView.setGridStyle(QtCore.Qt.NoPen)
|
||||
|
@@ -110,10 +110,11 @@ def pixmapper(current_chapter, total_chapters, temp_dir, size):
|
||||
# TODO
|
||||
# See if saving the svg to disk can be avoided
|
||||
# Shift to lines to track progress
|
||||
# Maybe make the alignment a little more uniform across emblems
|
||||
|
||||
progress_percent = int(current_chapter * 100 / total_chapters)
|
||||
generate_pie(progress_percent, temp_dir)
|
||||
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
|
||||
|
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">
|
||||
<widget class="QTableView" name="tableView">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
<enum>QFrame::Box</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
|
||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
@@ -141,7 +144,7 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<RCC>
|
||||
<qresource prefix="images">
|
||||
<file>blank.png</file>
|
||||
<file>gray-shadow.png</file>
|
||||
<file>NotFound.png</file>
|
||||
<file>checkmark.svg</file>
|
||||
|
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>879</width>
|
||||
<height>673</height>
|
||||
<width>929</width>
|
||||
<height>638</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -23,66 +23,17 @@
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTableView" name="tableView">
|
||||
<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>
|
||||
<widget class="QTreeView" name="treeView"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="tableFilterEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search for Paths, Names, Tags...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<widget class="QTextBrowser" name="aboutBox">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -90,63 +41,42 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Startup</string>
|
||||
<string>Switches</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>Auto add files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fileRemember">
|
||||
<property name="text">
|
||||
<string>Remember open files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_2">
|
||||
<property name="text">
|
||||
<string>Show Library</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_3">
|
||||
<property name="text">
|
||||
<string>Cover Shadows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="aboutButton">
|
||||
<property name="text">
|
||||
<string>About</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="refreshLibrary">
|
||||
<property name="text">
|
||||
<string>Startup: Refresh library</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fileRemember">
|
||||
<property name="text">
|
||||
<string>Remember open files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="coverShadows">
|
||||
<property name="text">
|
||||
<string>Cover shadows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoTags">
|
||||
<property name="text">
|
||||
<string>Generate tags from files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
@@ -155,6 +85,31 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="aboutButton">
|
||||
<property name="text">
|
||||
<string>About</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
# 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!
|
||||
|
||||
@@ -1117,6 +1117,50 @@ qt_resource_data = b"\
|
||||
\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\
|
||||
\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\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\
|
||||
\x34\x31\x7a\x22\x2f\x3e\x0a\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\
|
||||
\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"\
|
||||
@@ -1195,6 +1209,14 @@ qt_resource_name = b"\
|
||||
\x02\xea\x4d\x87\
|
||||
\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\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\
|
||||
\x0a\xe2\xd1\x87\
|
||||
\x00\x67\
|
||||
@@ -1203,33 +1225,32 @@ qt_resource_name = b"\
|
||||
\x0b\x5d\x1f\x07\
|
||||
\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\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"\
|
||||
\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\x74\x00\x00\x00\x00\x00\x01\x00\x00\x47\x17\
|
||||
\x00\x00\x00\x30\x00\x01\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\x48\x00\x01\x00\x00\x00\x01\x00\x00\x46\xd1\
|
||||
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x45\x17\
|
||||
\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"\
|
||||
\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\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\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x5f\xb9\x9f\xcd\x26\
|
||||
\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x47\x17\
|
||||
\x00\x00\x01\x5f\xb9\x9f\xca\xd0\
|
||||
\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\x00\x30\x00\x01\x00\x00\x00\x01\x00\x00\x45\x17\
|
||||
\x00\x00\x01\x5f\xf0\x1d\xc5\xb3\
|
||||
\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\x01\x5f\xf0\x1d\xc2\x38\
|
||||
\x00\x00\x00\x84\x00\x00\x00\x00\x00\x01\x00\x00\x48\x2d\
|
||||
\x00\x00\x01\x5f\x7e\xcc\x7f\x20\
|
||||
"
|
||||
|
||||
|
@@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_Dialog(object):
|
||||
def setupUi(self, Dialog):
|
||||
Dialog.setObjectName("Dialog")
|
||||
Dialog.resize(879, 673)
|
||||
Dialog.resize(929, 638)
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(Dialog)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
|
||||
@@ -20,66 +20,51 @@ class Ui_Dialog(object):
|
||||
self.groupBox_2.setObjectName("groupBox_2")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.tableView = QtWidgets.QTableView(self.groupBox_2)
|
||||
self.tableView.setFrameShape(QtWidgets.QFrame.NoFrame)
|
||||
self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
|
||||
self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed)
|
||||
self.tableView.setAlternatingRowColors(True)
|
||||
self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
|
||||
self.tableView.setGridStyle(QtCore.Qt.NoPen)
|
||||
self.tableView.setSortingEnabled(True)
|
||||
self.tableView.setWordWrap(False)
|
||||
self.tableView.setObjectName("tableView")
|
||||
self.tableView.horizontalHeader().setVisible(True)
|
||||
self.tableView.verticalHeader().setVisible(False)
|
||||
self.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.treeView = QtWidgets.QTreeView(self.groupBox_2)
|
||||
self.treeView.setObjectName("treeView")
|
||||
self.gridLayout_2.addWidget(self.treeView, 0, 0, 1, 1)
|
||||
self.aboutBox = QtWidgets.QTextBrowser(self.groupBox_2)
|
||||
self.aboutBox.setOpenExternalLinks(True)
|
||||
self.aboutBox.setOpenLinks(False)
|
||||
self.aboutBox.setObjectName("aboutBox")
|
||||
self.gridLayout_2.addWidget(self.aboutBox, 1, 0, 1, 1)
|
||||
self.verticalLayout_2.addWidget(self.groupBox_2)
|
||||
self.groupBox = QtWidgets.QGroupBox(Dialog)
|
||||
self.groupBox.setObjectName("groupBox")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.checkBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.checkBox.setObjectName("checkBox")
|
||||
self.horizontalLayout_3.addWidget(self.checkBox)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.refreshLibrary = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.refreshLibrary.setObjectName("refreshLibrary")
|
||||
self.horizontalLayout_4.addWidget(self.refreshLibrary)
|
||||
self.fileRemember = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.fileRemember.setObjectName("fileRemember")
|
||||
self.horizontalLayout_3.addWidget(self.fileRemember)
|
||||
self.checkBox_2 = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.checkBox_2.setObjectName("checkBox_2")
|
||||
self.horizontalLayout_3.addWidget(self.checkBox_2)
|
||||
self.checkBox_3 = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.checkBox_3.setObjectName("checkBox_3")
|
||||
self.horizontalLayout_3.addWidget(self.checkBox_3)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_3, 0, 0, 1, 1)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
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.horizontalLayout_4.addWidget(self.fileRemember)
|
||||
self.coverShadows = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.coverShadows.setObjectName("coverShadows")
|
||||
self.horizontalLayout_4.addWidget(self.coverShadows)
|
||||
self.autoTags = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.autoTags.setObjectName("autoTags")
|
||||
self.horizontalLayout_4.addWidget(self.autoTags)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout_4)
|
||||
self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
|
||||
self.verticalLayout_2.addWidget(self.groupBox)
|
||||
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)
|
||||
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
||||
@@ -88,14 +73,11 @@ class Ui_Dialog(object):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Dialog.setWindowTitle(_translate("Dialog", "Settings"))
|
||||
self.groupBox_2.setTitle(_translate("Dialog", "Library"))
|
||||
self.tableFilterEdit.setPlaceholderText(_translate("Dialog", "Search for Paths, Names, Tags..."))
|
||||
self.addButton.setText(_translate("Dialog", "Add"))
|
||||
self.removeButton.setText(_translate("Dialog", "Remove"))
|
||||
self.groupBox.setTitle(_translate("Dialog", "Startup"))
|
||||
self.checkBox.setText(_translate("Dialog", "Auto add files"))
|
||||
self.groupBox.setTitle(_translate("Dialog", "Switches"))
|
||||
self.refreshLibrary.setText(_translate("Dialog", "Startup: Refresh library"))
|
||||
self.fileRemember.setText(_translate("Dialog", "Remember open files"))
|
||||
self.checkBox_2.setText(_translate("Dialog", "Show Library"))
|
||||
self.checkBox_3.setText(_translate("Dialog", "Cover Shadows"))
|
||||
self.coverShadows.setText(_translate("Dialog", "Cover shadows"))
|
||||
self.autoTags.setText(_translate("Dialog", "Generate tags from files"))
|
||||
self.okButton.setText(_translate("Dialog", "OK"))
|
||||
self.cancelButton.setText(_translate("Dialog", "Cancel"))
|
||||
self.aboutButton.setText(_translate("Dialog", "About"))
|
||||
|
90
settings.py
90
settings.py
@@ -1,13 +1,14 @@
|
||||
#!/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
|
||||
from ast import literal_eval
|
||||
from PyQt5 import QtCore, QtGui
|
||||
|
||||
|
||||
class Settings:
|
||||
def __init__(self, parent):
|
||||
self.parent_window = parent
|
||||
self.parent = parent
|
||||
self.settings = QtCore.QSettings('Lector', 'Lector')
|
||||
|
||||
default_profile1 = {
|
||||
@@ -44,87 +45,106 @@ class Settings:
|
||||
|
||||
def read_settings(self):
|
||||
self.settings.beginGroup('mainWindow')
|
||||
self.parent_window.resize(self.settings.value('windowSize', QtCore.QSize(1299, 748)))
|
||||
self.parent_window.move(self.settings.value('windowPosition', QtCore.QPoint(0, 0)))
|
||||
self.parent_window.current_view = int(self.settings.value('currentView', 0))
|
||||
self.parent_window.table_header_sizes = self.settings.value('tableHeaders', None)
|
||||
self.parent.resize(self.settings.value('windowSize', QtCore.QSize(1299, 748)))
|
||||
self.parent.move(self.settings.value('windowPosition', QtCore.QPoint(0, 0)))
|
||||
self.parent.settings['current_view'] = int(self.settings.value('currentView', 0))
|
||||
self.parent.settings['main_window_headers'] = self.settings.value('tableHeaders', None)
|
||||
self.settings.endGroup()
|
||||
|
||||
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('~'))
|
||||
self.parent_window.database_path = self.settings.value(
|
||||
self.parent.database_path = self.settings.value(
|
||||
'databasePath',
|
||||
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)
|
||||
self.parent_window.current_profile_index = int(self.settings.value(
|
||||
self.parent.current_profile_index = int(self.settings.value(
|
||||
'currentProfileIndex', 0))
|
||||
self.parent_window.comic_profile = self.settings.value(
|
||||
self.parent.comic_profile = self.settings.value(
|
||||
'comicProfile', self.default_comic_profile)
|
||||
self.settings.endGroup()
|
||||
|
||||
self.settings.beginGroup('lastOpen')
|
||||
self.parent_window.last_open_books = self.settings.value('lastOpenFiles', [])
|
||||
self.parent_window.last_open_tab = self.settings.value('lastOpenTab', 'library')
|
||||
self.parent.settings['last_open_books'] = self.settings.value('lastOpenBooks', [])
|
||||
self.parent.last_open_tab = self.settings.value('lastOpenTab', 'library')
|
||||
self.settings.endGroup()
|
||||
|
||||
self.settings.beginGroup('settingsWindow')
|
||||
self.parent_window.settings_dialog_settings = {}
|
||||
self.parent_window.settings_dialog_settings['size'] = self.settings.value(
|
||||
self.parent.settings['settings_dialog_size'] = self.settings.value(
|
||||
'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))
|
||||
self.parent_window.settings_dialog_settings['headers'] = self.settings.value(
|
||||
self.parent.settings['settings_dialog_headers'] = self.settings.value(
|
||||
'tableHeaders', [200, 150])
|
||||
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):
|
||||
print('Saving settings...')
|
||||
current_settings = self.parent.settings
|
||||
|
||||
self.settings.beginGroup('mainWindow')
|
||||
self.settings.setValue('windowSize', self.parent_window.size())
|
||||
self.settings.setValue('windowPosition', self.parent_window.pos())
|
||||
self.settings.setValue('currentView', self.parent_window.stackedWidget.currentIndex())
|
||||
self.settings.setValue('windowSize', self.parent.size())
|
||||
self.settings.setValue('windowPosition', self.parent.pos())
|
||||
self.settings.setValue('currentView', self.parent.stackedWidget.currentIndex())
|
||||
|
||||
table_headers = []
|
||||
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.endGroup()
|
||||
|
||||
self.settings.beginGroup('runtimeVariables')
|
||||
self.settings.setValue('lastOpenPath', self.parent_window.last_open_path)
|
||||
self.settings.setValue('databasePath', self.parent_window.database_path)
|
||||
self.settings.setValue('lastOpenPath', self.parent.last_open_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)
|
||||
current_profile2 = self.parent_window.bookToolBar.profileBox.itemData(
|
||||
current_profile2 = self.parent.bookToolBar.profileBox.itemData(
|
||||
1, QtCore.Qt.UserRole)
|
||||
current_profile3 = self.parent_window.bookToolBar.profileBox.itemData(
|
||||
current_profile3 = self.parent.bookToolBar.profileBox.itemData(
|
||||
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', [
|
||||
current_profile1,
|
||||
current_profile2,
|
||||
current_profile3])
|
||||
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()
|
||||
|
||||
current_tab_index = self.parent_window.tabWidget.currentIndex()
|
||||
current_tab_index = self.parent.tabWidget.currentIndex()
|
||||
if current_tab_index == 0:
|
||||
last_open_tab = 'library'
|
||||
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.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.endGroup()
|
||||
|
||||
self.settings.beginGroup('settingsWindow')
|
||||
these_settings = self.parent_window.settings_dialog_settings
|
||||
self.settings.setValue('windowSize', these_settings['size'])
|
||||
self.settings.setValue('windowPosition', these_settings['position'])
|
||||
self.settings.setValue('tableHeaders', these_settings['headers'])
|
||||
self.settings.setValue('windowSize', current_settings['settings_dialog_size'])
|
||||
self.settings.setValue('windowPosition', current_settings['settings_dialog_position'])
|
||||
self.settings.setValue('tableHeaders', current_settings['settings_dialog_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
|
||||
|
||||
import os
|
||||
import collections
|
||||
# This file is a part of Lector, a Qt based ebook reader
|
||||
# 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
|
||||
from resources import settingswindow
|
||||
from models import MostExcellentTableModel, TableProxyModel
|
||||
from models import MostExcellentFileSystemModel, FileSystemProxyModel
|
||||
from threaded import BackGroundBookSearch, BackGroundBookAddition
|
||||
|
||||
|
||||
class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
|
||||
# TODO
|
||||
# Deletion from table
|
||||
# Cancel behavior
|
||||
# Update database on table model update
|
||||
|
||||
def __init__(self, parent_window):
|
||||
def __init__(self, parent):
|
||||
super(SettingsUI, self).__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
self.last_open_directory = None
|
||||
self.parent_window = parent_window
|
||||
self.database_path = self.parent_window.database_path
|
||||
self.parent = parent
|
||||
self.database_path = self.parent.database_path
|
||||
|
||||
self.resize(self.parent_window.settings_dialog_settings['size'])
|
||||
self.move(self.parent_window.settings_dialog_settings['position'])
|
||||
self.resize(self.parent.settings['settings_dialog_size'])
|
||||
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.thread = None
|
||||
self.filesystem_model = None
|
||||
self.tag_data_copy = None
|
||||
|
||||
self.tableFilterEdit.textChanged.connect(self.update_table_proxy_model)
|
||||
self.addButton.clicked.connect(self.add_directories)
|
||||
self.okButton.setToolTip('Save changes and start library scan')
|
||||
self.okButton.clicked.connect(self.start_library_scan)
|
||||
self.cancelButton.clicked.connect(self.cancel_pressed)
|
||||
self.okButton.clicked.connect(self.ok_pressed)
|
||||
self.aboutButton.clicked.connect(self.about_pressed)
|
||||
|
||||
self.generate_table()
|
||||
header_sizes = self.parent_window.settings_dialog_settings['headers']
|
||||
if header_sizes:
|
||||
for count, i in enumerate(header_sizes):
|
||||
self.tableView.horizontalHeader().resizeSection(count, int(i))
|
||||
# Check boxes
|
||||
self.autoTags.setChecked(self.parent.settings['auto_tags'])
|
||||
self.coverShadows.setChecked(self.parent.settings['cover_shadows'])
|
||||
self.refreshLibrary.setChecked(self.parent.settings['scan_library'])
|
||||
self.fileRemember.setChecked(self.parent.settings['remember_files'])
|
||||
|
||||
self.tableView.horizontalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.Interactive)
|
||||
self.tableView.horizontalHeader().setHighlightSections(False)
|
||||
self.tableView.horizontalHeader().setStretchLastSection(True)
|
||||
# self.tableView.horizontalHeader().setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
|
||||
self.autoTags.clicked.connect(self.manage_checkboxes)
|
||||
self.coverShadows.clicked.connect(self.manage_checkboxes)
|
||||
self.refreshLibrary.clicked.connect(self.manage_checkboxes)
|
||||
self.fileRemember.clicked.connect(self.manage_checkboxes)
|
||||
|
||||
def generate_table(self):
|
||||
# Generate the filesystem treeView
|
||||
self.generate_tree()
|
||||
|
||||
def generate_tree(self):
|
||||
# Fetch all directories in the database
|
||||
self.paths = database.DatabaseFunctions(
|
||||
paths = database.DatabaseFunctions(
|
||||
self.database_path).fetch_data(
|
||||
('Path', 'Name', 'Tags'),
|
||||
('Path', 'Name', 'Tags', 'CheckState'),
|
||||
'directories',
|
||||
{'Path': ''},
|
||||
'LIKE')
|
||||
|
||||
if not self.paths:
|
||||
self.parent.generate_library_filter_menu(paths)
|
||||
directory_data = {}
|
||||
if not paths:
|
||||
print('Database returned no paths for settings...')
|
||||
else:
|
||||
# Convert to a list because tuples, well, they're tuples
|
||||
self.paths = [list(i) for i in self.paths]
|
||||
# Convert to the dictionary format that is
|
||||
# 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.table_model = MostExcellentTableModel(
|
||||
table_header, self.paths, None)
|
||||
|
||||
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)
|
||||
self.filesystem_model = MostExcellentFileSystemModel(directory_data)
|
||||
self.filesystem_model.setFilter(QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Dirs)
|
||||
self.treeView.setModel(self.filesystem_model)
|
||||
|
||||
# TODO
|
||||
# Account for a parent folder getting added after a subfolder
|
||||
# Currently this does the inverse only
|
||||
# This here might break on them pestilent non unixy OSes
|
||||
# Check and see
|
||||
|
||||
for i in self.paths:
|
||||
already_present = os.path.realpath(i[0])
|
||||
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
|
||||
root_directory = QtCore.QDir().rootPath()
|
||||
self.treeView.setRootIndex(self.filesystem_model.setRootPath(root_directory))
|
||||
|
||||
# Set default name for the directory
|
||||
directory_name = os.path.basename(add_directory).title()
|
||||
data_pair = [[add_directory, directory_name, None]]
|
||||
database.DatabaseFunctions(self.database_path).set_library_paths(data_pair)
|
||||
self.generate_table()
|
||||
# Set the treeView and QFileSystemModel to its desired state
|
||||
selected_paths = [
|
||||
i for i in directory_data if directory_data[i]['check_state'] == QtCore.Qt.Checked]
|
||||
expand_paths = set()
|
||||
for i in selected_paths:
|
||||
|
||||
def ok_pressed(self):
|
||||
# Traverse directories looking for files
|
||||
self.thread = BackGroundBookSearch(self, self.table_model.display_data)
|
||||
self.thread.finished.connect(self.do_something)
|
||||
self.thread.start()
|
||||
# Recursively grind down parent paths for expansion
|
||||
this_path = i
|
||||
while True:
|
||||
parent_path = os.path.dirname(this_path)
|
||||
if parent_path == this_path:
|
||||
break
|
||||
expand_paths.add(parent_path)
|
||||
this_path = parent_path
|
||||
|
||||
def do_something(self):
|
||||
print('Book search completed')
|
||||
# Expand all the parent paths derived from the selected path
|
||||
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()
|
||||
|
||||
# TODO
|
||||
# Implement cancel by restoring the table model to an older version
|
||||
# def showEvent(self, event):
|
||||
# event.accept()
|
||||
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
|
||||
# Change this to no longer include files added manually
|
||||
|
||||
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):
|
||||
self.no_more_settings()
|
||||
event.accept()
|
||||
|
||||
def showEvent(self, event):
|
||||
self.tag_data_copy = copy.deepcopy(self.filesystem_model.tag_data)
|
||||
event.accept()
|
||||
|
||||
def no_more_settings(self):
|
||||
self.table_model = self.old_table_model
|
||||
self.parent_window.libraryToolBar.settingsButton.setChecked(False)
|
||||
self.parent.libraryToolBar.settingsButton.setChecked(False)
|
||||
self.aboutBox.hide()
|
||||
self.treeView.show()
|
||||
self.resizeEvent()
|
||||
|
||||
def resizeEvent(self, event=None):
|
||||
self.parent_window.settings_dialog_settings['size'] = self.size()
|
||||
self.parent_window.settings_dialog_settings['position'] = self.pos()
|
||||
self.parent.settings['settings_dialog_size'] = self.size()
|
||||
self.parent.settings['settings_dialog_position'] = self.pos()
|
||||
table_headers = []
|
||||
for i in range(2):
|
||||
table_headers.append(self.tableView.horizontalHeader().sectionSize(i))
|
||||
self.parent_window.settings_dialog_settings['headers'] = table_headers
|
||||
for i in [0, 4]:
|
||||
table_headers.append(self.treeView.columnWidth(i))
|
||||
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())
|
||||
|
184
sorter.py
184
sorter.py
@@ -1,17 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# TODO
|
||||
# See if you want to include a hash of the book's name and author
|
||||
# This file is a part of Lector, a Qt based ebook reader
|
||||
# Copyright (C) 2017 BasioMeusPuga
|
||||
|
||||
import io
|
||||
import os
|
||||
import pickle
|
||||
import hashlib
|
||||
from multiprocessing.dummy import Pool
|
||||
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.
|
||||
|
||||
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:
|
||||
# read_book()
|
||||
# get_title()
|
||||
@@ -19,19 +24,42 @@ import database
|
||||
# get_year()
|
||||
# get_cover_image()
|
||||
# get_isbn()
|
||||
# get_tags()
|
||||
# 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
|
||||
|
||||
# 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.cbz import ParseCBZ
|
||||
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__
|
||||
progress_emitter = None # This is to be made into a global variable
|
||||
|
||||
|
||||
# This is for thread safety
|
||||
class UpdateProgress(QtCore.QObject):
|
||||
# This is for thread safety
|
||||
update_signal = QtCore.pyqtSignal(int)
|
||||
|
||||
def connect_to_progressbar(self):
|
||||
@@ -42,7 +70,7 @@ class UpdateProgress(QtCore.QObject):
|
||||
|
||||
|
||||
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
|
||||
# Then, on the basis of what is needed, pass the
|
||||
# filenames to the requisite functions
|
||||
@@ -51,17 +79,20 @@ class BookSorter:
|
||||
# Caching upon closing
|
||||
self.file_list = [i for i in file_list if os.path.exists(i)]
|
||||
self.statistics = [0, (len(file_list))]
|
||||
self.all_books = {}
|
||||
self.hashes = []
|
||||
self.mode = mode
|
||||
self.database_path = database_path
|
||||
self.auto_tags = auto_tags
|
||||
self.temp_dir = temp_dir
|
||||
if database_path:
|
||||
self.database_hashes()
|
||||
|
||||
self.threading_completed = []
|
||||
self.queue = Manager().Queue()
|
||||
self.processed_books = []
|
||||
|
||||
if self.mode == 'addition':
|
||||
self.progress_emitter = UpdateProgress()
|
||||
self.progress_emitter.connect_to_progressbar()
|
||||
progress_object_generator()
|
||||
|
||||
def database_hashes(self):
|
||||
# TODO
|
||||
@@ -101,32 +132,21 @@ class BookSorter:
|
||||
# This should speed up addition for larger files
|
||||
# without compromising the integrity of the process
|
||||
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()
|
||||
|
||||
if self.mode == 'addition':
|
||||
self.statistics[0] += 1
|
||||
self.progress_emitter.update_progress(
|
||||
self.statistics[0] * 100 // self.statistics[1])
|
||||
# Update the progress queue
|
||||
self.queue.put(filename)
|
||||
|
||||
# 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
|
||||
if (self.mode == 'addition'
|
||||
and (file_md5 in self.all_books.items() or file_md5 in self.hashes)):
|
||||
# IF the file is NOT being loaded into the reader,
|
||||
# Do not allow addition in case the file
|
||||
# is already in the database
|
||||
if self.mode == 'addition' and file_md5 in self.hashes:
|
||||
return
|
||||
|
||||
# ___________SORTING TAKES PLACE HERE___________
|
||||
sorter = {
|
||||
'epub': ParseEPUB,
|
||||
'cbz': ParseCBZ,
|
||||
'cbr': ParseCBR
|
||||
}
|
||||
|
||||
file_extension = os.path.splitext(filename)[1][1:]
|
||||
try:
|
||||
# Get the requisite parser from the sorter dict
|
||||
book_ref = sorter[file_extension](filename, self.temp_dir, file_md5)
|
||||
except KeyError:
|
||||
print(filename + ' has an unsupported extension')
|
||||
@@ -137,7 +157,7 @@ class BookSorter:
|
||||
book_ref.read_book()
|
||||
if book_ref.book:
|
||||
|
||||
title = book_ref.get_title().title()
|
||||
title = book_ref.get_title()
|
||||
|
||||
author = book_ref.get_author()
|
||||
if not author:
|
||||
@@ -150,22 +170,32 @@ class BookSorter:
|
||||
|
||||
isbn = book_ref.get_isbn()
|
||||
|
||||
tags = None
|
||||
if self.auto_tags:
|
||||
tags = book_ref.get_tags()
|
||||
|
||||
this_book = {}
|
||||
this_book[file_md5] = {
|
||||
'title': title,
|
||||
'author': author,
|
||||
'year': year,
|
||||
'isbn': isbn,
|
||||
'hash': file_md5,
|
||||
'path': filename,
|
||||
'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:
|
||||
# Reduce the size of the incoming image
|
||||
cover_image = resize_image(cover_image_raw)
|
||||
else:
|
||||
cover_image = None
|
||||
|
||||
self.all_books[file_md5] = {
|
||||
'title': title,
|
||||
'author': author,
|
||||
'year': year,
|
||||
'isbn': isbn,
|
||||
'path': filename,
|
||||
'cover_image': cover_image}
|
||||
this_book[file_md5]['cover_image'] = cover_image
|
||||
|
||||
if self.mode == 'reading':
|
||||
all_content = book_ref.get_contents()
|
||||
@@ -183,25 +213,65 @@ class BookSorter:
|
||||
content['Invalid'] = 'Possible Parse Error'
|
||||
|
||||
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):
|
||||
_pool = Pool(5)
|
||||
_pool.map(self.read_book, self.file_list)
|
||||
_pool.close()
|
||||
_pool.join()
|
||||
def pool_creator():
|
||||
_pool = Pool(5)
|
||||
self.processed_books = _pool.map(
|
||||
self.read_book, self.file_list)
|
||||
|
||||
return self.all_books
|
||||
_pool.close()
|
||||
_pool.join()
|
||||
|
||||
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):
|
||||
|
83
threaded.py
83
threaded.py
@@ -1,6 +1,23 @@
|
||||
#!/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 pathlib
|
||||
from multiprocessing.dummy import Pool
|
||||
from PyQt5 import QtCore
|
||||
|
||||
@@ -26,38 +43,72 @@ class BackGroundTabUpdate(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)
|
||||
self.parent_window = parent_window
|
||||
self.file_list = file_list
|
||||
self.parent = parent
|
||||
self.database_path = database_path
|
||||
self.prune_required = prune_required
|
||||
|
||||
def run(self):
|
||||
books = sorter.BookSorter(
|
||||
self.file_list,
|
||||
'addition',
|
||||
self.database_path)
|
||||
self.database_path,
|
||||
self.parent.settings['auto_tags'])
|
||||
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)
|
||||
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):
|
||||
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)
|
||||
self.parent_window = parent_window
|
||||
self.data_list = data_list
|
||||
self.valid_files = [] # A tuple should get added to this containing the
|
||||
# file path and the folder name / tags
|
||||
self.parent = parent
|
||||
self.valid_files = []
|
||||
|
||||
# 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 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):
|
||||
root_directory = incoming_data[0]
|
||||
folder_name = incoming_data[1]
|
||||
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:
|
||||
if os.path.splitext(filename)[1][1:] in sorter.available_parsers:
|
||||
self.valid_files.append(
|
||||
@@ -65,17 +116,9 @@ class BackGroundBookSearch(QtCore.QThread):
|
||||
|
||||
def initiate_threads():
|
||||
_pool = Pool(5)
|
||||
_pool.map(traverse_directory, self.data_list)
|
||||
_pool.map(traverse_directory, self.valid_directories)
|
||||
_pool.close()
|
||||
_pool.join()
|
||||
|
||||
initiate_threads()
|
||||
|
||||
# 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
|
||||
print(len(self.valid_files), 'books found')
|
||||
|
97
widgets.py
97
widgets.py
@@ -1,9 +1,31 @@
|
||||
#!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
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
|
||||
from resources import resources, pie_chart
|
||||
from resources import pie_chart
|
||||
|
||||
|
||||
class BookToolBar(QtWidgets.QToolBar):
|
||||
@@ -31,6 +53,10 @@ class BookToolBar(QtWidgets.QToolBar):
|
||||
self.fullscreenButton = QtWidgets.QAction(
|
||||
QtGui.QIcon.fromTheme('view-fullscreen'),
|
||||
'Fullscreen', self)
|
||||
self.bookmarkButton = QtWidgets.QAction(
|
||||
QtGui.QIcon.fromTheme('bookmarks'),
|
||||
'Bookmark', self)
|
||||
self.bookmarkButton.setObjectName('bookmarkButton')
|
||||
self.resetProfile = QtWidgets.QAction(
|
||||
QtGui.QIcon.fromTheme('view-refresh'),
|
||||
'Reset profile', self)
|
||||
@@ -40,6 +66,8 @@ class BookToolBar(QtWidgets.QToolBar):
|
||||
self.fontButton.setCheckable(True)
|
||||
self.fontButton.triggered.connect(self.toggle_font_settings)
|
||||
self.addSeparator()
|
||||
self.addAction(self.bookmarkButton)
|
||||
self.bookmarkButton.setCheckable(True)
|
||||
self.addAction(self.fullscreenButton)
|
||||
|
||||
# Font modification
|
||||
@@ -191,6 +219,7 @@ class BookToolBar(QtWidgets.QToolBar):
|
||||
self.searchBarAction = self.addWidget(self.searchBar)
|
||||
|
||||
self.bookActions = [
|
||||
self.bookmarkButton,
|
||||
self.fullscreenButton,
|
||||
self.tocBoxAction,
|
||||
self.searchBarAction]
|
||||
@@ -269,6 +298,11 @@ class LibraryToolBar(QtWidgets.QToolBar):
|
||||
QtGui.QIcon.fromTheme('table'), 'View as table', self)
|
||||
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
|
||||
self.viewButtons = QtWidgets.QActionGroup(self)
|
||||
self.viewButtons.setExclusive(True)
|
||||
@@ -282,6 +316,8 @@ class LibraryToolBar(QtWidgets.QToolBar):
|
||||
self.addAction(self.coverViewButton)
|
||||
self.addAction(self.tableViewButton)
|
||||
self.addSeparator()
|
||||
self.addWidget(self.libraryFilterButton)
|
||||
self.addSeparator()
|
||||
self.addAction(self.settingsButton)
|
||||
|
||||
# Filter
|
||||
@@ -346,8 +382,10 @@ class Tab(QtWidgets.QWidget):
|
||||
self.parent = parent
|
||||
self.metadata = metadata # Save progress data into this dictionary
|
||||
|
||||
self.gridLayout = QtWidgets.QGridLayout(self)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.masterLayout = QtWidgets.QHBoxLayout(self)
|
||||
self.horzLayout = QtWidgets.QSplitter(self)
|
||||
self.horzLayout.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.masterLayout.addWidget(self.horzLayout)
|
||||
|
||||
position = self.metadata['position']
|
||||
|
||||
@@ -374,6 +412,7 @@ class Tab(QtWidgets.QWidget):
|
||||
self.contentView.loadImage(chapter_content)
|
||||
else:
|
||||
self.contentView = PliantQTextBrowser(self.window())
|
||||
# print(dir(self.contentView.document())) ## TODO USE this for modifying formatting and searching
|
||||
|
||||
relative_path_root = os.path.join(
|
||||
self.window().temp_dir.path(), self.metadata['hash'])
|
||||
@@ -393,9 +432,20 @@ class Tab(QtWidgets.QWidget):
|
||||
self.contentView.setHorizontalScrollBarPolicy(
|
||||
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.gridLayout.addWidget(self.contentView, 0, 0, 1, 1)
|
||||
self.horzLayout.addWidget(self.contentView)
|
||||
self.horzLayout.addWidget(self.dockWidget)
|
||||
title = self.metadata['title']
|
||||
self.parent.addTab(self, title)
|
||||
|
||||
@@ -462,6 +512,11 @@ class Tab(QtWidgets.QWidget):
|
||||
self.contentView.clear()
|
||||
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):
|
||||
if self.are_we_doing_images_only:
|
||||
# Tab color does not need to be set separately in case
|
||||
@@ -473,11 +528,22 @@ class Tab(QtWidgets.QWidget):
|
||||
self.contentView.resizeEvent()
|
||||
|
||||
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.setStyleSheet(
|
||||
"QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}".format(
|
||||
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):
|
||||
direction = -1
|
||||
if self.sender().objectName() == 'nextChapter':
|
||||
@@ -661,6 +727,7 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate):
|
||||
def __init__(self, temp_dir, parent=None):
|
||||
super(LibraryDelegate, self).__init__(parent)
|
||||
self.temp_dir = temp_dir
|
||||
self.parent = parent
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
# This is a hint for the future
|
||||
@@ -673,27 +740,27 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate):
|
||||
position = index.data(QtCore.Qt.UserRole + 7)
|
||||
|
||||
# The shadow pixmap currently is set to 420 x 600
|
||||
shadow_pixmap = QtGui.QPixmap()
|
||||
shadow_pixmap.load(':/images/gray-shadow.png')
|
||||
shadow_pixmap = shadow_pixmap.scaled(160, 230, QtCore.Qt.IgnoreAspectRatio)
|
||||
shadow_x = option.rect.topLeft().x() + 10
|
||||
shadow_y = option.rect.topLeft().y() - 5
|
||||
# Only draw the cover shadow in case the setting is enabled
|
||||
if self.parent.settings['cover_shadows']:
|
||||
shadow_pixmap = QtGui.QPixmap()
|
||||
shadow_pixmap.load(':/images/gray-shadow.png')
|
||||
shadow_pixmap = shadow_pixmap.scaled(160, 230, QtCore.Qt.IgnoreAspectRatio)
|
||||
shadow_x = option.rect.topLeft().x() + 10
|
||||
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:
|
||||
painter.setOpacity(.7)
|
||||
painter.drawPixmap(shadow_x, shadow_y, shadow_pixmap)
|
||||
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
|
||||
painter.setOpacity(1)
|
||||
|
||||
read_icon = pie_chart.pixmapper(-1, None, None, 36)
|
||||
x_draw = option.rect.bottomRight().x() - 30
|
||||
y_draw = option.rect.bottomRight().y() - 35
|
||||
painter.drawPixmap(x_draw, y_draw, read_icon)
|
||||
painter.setOpacity(1)
|
||||
return
|
||||
|
||||
painter.setOpacity(.8)
|
||||
painter.drawPixmap(shadow_x, shadow_y, shadow_pixmap)
|
||||
painter.setOpacity(1)
|
||||
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
|
||||
if position:
|
||||
current_chapter = position['current_chapter']
|
||||
|
Reference in New Issue
Block a user