Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
34dcf9f1b4 | ||
|
7931f92335 | ||
|
42b655862c | ||
|
e6eb056ec6 | ||
|
7f5b6fc349 | ||
|
9af175b11f | ||
|
c783e44444 | ||
|
a1dba753e8 | ||
|
a55a0e7205 | ||
|
bb8de60efe | ||
|
0d8c2b6648 | ||
|
64a96d816d | ||
|
5a4af54118 | ||
|
4cf18e008d | ||
|
50cc52b116 | ||
|
35f38b9f68 | ||
|
c883ba0175 | ||
|
39cf03a70e | ||
|
44d88d99bb | ||
|
ca67071e91 | ||
|
b5acce6449 | ||
|
7bdf01a67e | ||
|
aca08827fb | ||
|
d4aaa4dc74 | ||
|
98daa40bfd | ||
|
a7df896468 | ||
|
fd149dcafa | ||
|
0bb2e9329f | ||
|
89a32bfeda | ||
|
50089cb57a |
33
Lector.pro
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# This file is a part of Lector, a Qt based ebook reader
|
||||||
|
# Copyright (C) 2017-2018 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/>.
|
||||||
|
|
||||||
|
SOURCES += lector/__main__.py \
|
||||||
|
lector/definitionsdialog.py \
|
||||||
|
lector/metadatadialog.py \
|
||||||
|
lector/models.py \
|
||||||
|
lector/widgets.py \
|
||||||
|
lector/library.py \
|
||||||
|
lector/toolbars.py \
|
||||||
|
lector/settingsdialog.py \
|
||||||
|
resources/definitions.py \
|
||||||
|
resources/settingswindow.py \
|
||||||
|
resources/metadata.py \
|
||||||
|
resources/mainwindow.py
|
||||||
|
|
||||||
|
TRANSLATIONS += resources/translations/Lector_es.ts \
|
||||||
|
resources/translations/Lector_fr.ts \
|
||||||
|
resources/translations/Lector_de.ts \
|
||||||
|
resources/translations/SAMPLE.ts
|
18
README.md
@@ -35,12 +35,16 @@ poppler-qt5 and python-poppler-qt5 are optional.
|
|||||||
|
|
||||||
### Available packages
|
### Available packages
|
||||||
* [AUR](https://aur.archlinux.org/packages/lector-git/)
|
* [AUR](https://aur.archlinux.org/packages/lector-git/)
|
||||||
|
* [Gentoo (unofficial)](https://bitbucket.org/szymonsz/gen2-overlay/src/master/app-text/lector/)
|
||||||
|
|
||||||
## Reporting issues
|
## Translations
|
||||||
When reporting issues:
|
1. There is a `SAMPLE.ts` file in `resources/translations`. Open it in `Qt Linguist`.
|
||||||
|
2. Pick the language you wish to translate to.
|
||||||
|
3. Translate relevant strings.
|
||||||
|
4. Try to resist the urge to include profanity.
|
||||||
|
5. Save the file as `Lector_<language>` and send it to me, preferably as a pull request.
|
||||||
|
|
||||||
* If you're having trouble with a book while the rest of the application / other books work, please link to a copy of the book itself.
|
Oh, please keep the translations short. There's only so much space for UI elements.
|
||||||
* If nothing is working, please make sure the requirements mentioned above are all installed, and are at least at the version mentioned.
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
@@ -68,6 +72,12 @@ When reporting issues:
|
|||||||
### In program dictionary
|
### In program dictionary
|
||||||

|

|
||||||
|
|
||||||
|
## Reporting issues
|
||||||
|
When reporting issues:
|
||||||
|
|
||||||
|
* If you're having trouble with a book while the rest of the application / other books work, please link to a copy of the book itself.
|
||||||
|
* If nothing is working, please make sure the requirements mentioned above are all installed, and are at least at the version mentioned.
|
||||||
|
|
||||||
## Attributions
|
## Attributions
|
||||||
* [KindleUnpack](https://github.com/kevinhendricks/KindleUnpack)
|
* [KindleUnpack](https://github.com/kevinhendricks/KindleUnpack)
|
||||||
* [rarfile](https://github.com/markokr/rarfile)
|
* [rarfile](https://github.com/markokr/rarfile)
|
||||||
|
17
TODO
@@ -1,4 +1,8 @@
|
|||||||
TODO
|
TODO
|
||||||
|
General:
|
||||||
|
✓ Internationalization
|
||||||
|
Application icon
|
||||||
|
.desktop file
|
||||||
Options:
|
Options:
|
||||||
✓ Automatic library management
|
✓ Automatic library management
|
||||||
✓ Recursive file addition
|
✓ Recursive file addition
|
||||||
@@ -51,8 +55,10 @@ TODO
|
|||||||
✓ Cache next and previous images
|
✓ Cache next and previous images
|
||||||
✓ Set context menu for definitions and the like
|
✓ Set context menu for definitions and the like
|
||||||
✓ Paragraph indentation
|
✓ Paragraph indentation
|
||||||
|
✓ Comic view keyboard shortcuts
|
||||||
|
✓ Comic view context menu
|
||||||
|
Adjust key navigation according to viewport dimensions
|
||||||
Search document using QTextCursor?
|
Search document using QTextCursor?
|
||||||
Comic view keyboard shortcuts
|
|
||||||
Filetypes:
|
Filetypes:
|
||||||
✓ pdf support
|
✓ pdf support
|
||||||
Parse TOC
|
Parse TOC
|
||||||
@@ -66,10 +72,8 @@ TODO
|
|||||||
Other:
|
Other:
|
||||||
✓ Define every widget in code
|
✓ Define every widget in code
|
||||||
Bugs:
|
Bugs:
|
||||||
If there are files open and the database is deleted, TypeErrors result
|
Slider position change might be acting up
|
||||||
Cover culling does not occur if some other tab has initial focus
|
Deselecting all directories in the settings dialog also filters out manually added books
|
||||||
Slider position change might be acting up too
|
|
||||||
Take metadata from the database when opening the file
|
|
||||||
|
|
||||||
Secondary:
|
Secondary:
|
||||||
Annotations
|
Annotations
|
||||||
@@ -90,9 +94,6 @@ TODO
|
|||||||
Comic view modes
|
Comic view modes
|
||||||
Continuous paging
|
Continuous paging
|
||||||
Double pages
|
Double pages
|
||||||
Leave comic images on disk in case tab isn't closed and files are remembered
|
|
||||||
Give the comic view a 'Save image as...' option
|
|
||||||
Ignore a / the / numbers for sorting purposes
|
Ignore a / the / numbers for sorting purposes
|
||||||
? Add only one file type if multiple are present
|
? Add only one file type if multiple are present
|
||||||
? Plugin system for parsers
|
|
||||||
? Create emblem per filetype
|
? Create emblem per filetype
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import gc
|
||||||
import sys
|
import sys
|
||||||
import hashlib
|
import hashlib
|
||||||
import pathlib
|
import pathlib
|
||||||
@@ -42,7 +43,7 @@ from lector.settingsdialog import SettingsUI
|
|||||||
from lector.metadatadialog import MetadataUI
|
from lector.metadatadialog import MetadataUI
|
||||||
from lector.definitionsdialog import DefinitionsUI
|
from lector.definitionsdialog import DefinitionsUI
|
||||||
|
|
||||||
from resources import mainwindow, resources
|
from lector.resources import mainwindow, resources
|
||||||
|
|
||||||
|
|
||||||
class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||||
@@ -50,6 +51,17 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
super(MainUI, self).__init__()
|
super(MainUI, self).__init__()
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
|
# Set window icon
|
||||||
|
self.setWindowIcon(
|
||||||
|
QtGui.QIcon(':/images/Lector.png'))
|
||||||
|
|
||||||
|
# Central Widget - Make borders disappear
|
||||||
|
self.centralWidget().layout().setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# Initialize translation function
|
||||||
|
self._translate = QtCore.QCoreApplication.translate
|
||||||
|
|
||||||
# Empty variables that will be infested soon
|
# Empty variables that will be infested soon
|
||||||
self.settings = {}
|
self.settings = {}
|
||||||
self.thread = None # Background Thread
|
self.thread = None # Background Thread
|
||||||
@@ -74,8 +86,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
# Widget declarations
|
# Widget declarations
|
||||||
self.libraryFilterMenu = QtWidgets.QMenu()
|
self.libraryFilterMenu = QtWidgets.QMenu()
|
||||||
self.statusMessage = QtWidgets.QLabel()
|
self.statusMessage = QtWidgets.QLabel()
|
||||||
self.toolbarToggle = QtWidgets.QToolButton()
|
self.distractionFreeToggle = QtWidgets.QToolButton()
|
||||||
self.reloadLibrary = QtWidgets.QToolButton()
|
# self.reloadLibrary = QtWidgets.QToolButton()
|
||||||
|
self.reloadLibrary = QtWidgets.QPushButton()
|
||||||
|
|
||||||
# Create the database in case it doesn't exist
|
# Create the database in case it doesn't exist
|
||||||
database.DatabaseInit(self.database_path)
|
database.DatabaseInit(self.database_path)
|
||||||
@@ -100,13 +113,14 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.statusBar.addWidget(self.sorterProgress)
|
self.statusBar.addWidget(self.sorterProgress)
|
||||||
self.sorterProgress.setVisible(False)
|
self.sorterProgress.setVisible(False)
|
||||||
|
|
||||||
# Statusbar - Toolbar Visibility
|
# Statusbar + Toolbar Visibility
|
||||||
self.toolbarToggle.setIcon(self.QImageFactory.get_image('visibility'))
|
self.distractionFreeToggle.setIcon(self.QImageFactory.get_image('visibility'))
|
||||||
self.toolbarToggle.setObjectName('toolbarToggle')
|
self.distractionFreeToggle.setObjectName('distractionFreeToggle')
|
||||||
self.toolbarToggle.setToolTip('Toggle toolbar')
|
self.distractionFreeToggle.setToolTip(
|
||||||
self.toolbarToggle.setAutoRaise(True)
|
self._translate('Main_UI', 'Toggle distraction free mode (Ctrl + D)'))
|
||||||
self.toolbarToggle.clicked.connect(self.toggle_toolbars)
|
self.distractionFreeToggle.setAutoRaise(True)
|
||||||
self.statusBar.addPermanentWidget(self.toolbarToggle)
|
self.distractionFreeToggle.clicked.connect(self.toggle_distraction_free)
|
||||||
|
self.statusBar.addPermanentWidget(self.distractionFreeToggle)
|
||||||
|
|
||||||
# Application wide temporary directory
|
# Application wide temporary directory
|
||||||
self.temp_dir = QtCore.QTemporaryDir()
|
self.temp_dir = QtCore.QTemporaryDir()
|
||||||
@@ -121,7 +135,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
# Toolbar display
|
# Toolbar display
|
||||||
# Maybe make this a persistent option
|
# Maybe make this a persistent option
|
||||||
self.settings['show_toolbars'] = True
|
self.settings['show_bars'] = True
|
||||||
|
|
||||||
# Library toolbar
|
# Library toolbar
|
||||||
self.libraryToolBar.addButton.triggered.connect(self.add_books)
|
self.libraryToolBar.addButton.triggered.connect(self.add_books)
|
||||||
@@ -196,10 +210,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
print('Available parsers: ' + self.available_parsers)
|
print('Available parsers: ' + self.available_parsers)
|
||||||
|
|
||||||
# The library refresh button on the Library tab
|
# The library refresh button on the Library tab
|
||||||
|
self.reloadLibrary.setFlat(True)
|
||||||
self.reloadLibrary.setIcon(self.QImageFactory.get_image('reload'))
|
self.reloadLibrary.setIcon(self.QImageFactory.get_image('reload'))
|
||||||
self.reloadLibrary.setObjectName('reloadLibrary')
|
self.reloadLibrary.setObjectName('reloadLibrary')
|
||||||
self.reloadLibrary.setToolTip('Scan library')
|
self.reloadLibrary.setToolTip(self._translate('Main_UI', 'Scan library'))
|
||||||
self.reloadLibrary.setAutoRaise(True)
|
|
||||||
self.reloadLibrary.clicked.connect(self.settingsDialog.start_library_scan)
|
self.reloadLibrary.clicked.connect(self.settingsDialog.start_library_scan)
|
||||||
|
|
||||||
self.tabWidget.tabBar().setTabButton(
|
self.tabWidget.tabBar().setTabButton(
|
||||||
@@ -251,17 +265,30 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.tableView.horizontalHeader().sectionClicked.connect(
|
self.tableView.horizontalHeader().sectionClicked.connect(
|
||||||
self.lib_ref.table_proxy_model.sort_table_columns)
|
self.lib_ref.table_proxy_model.sort_table_columns)
|
||||||
self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||||
self.tableView.customContextMenuRequested.connect(self.generate_library_context_menu)
|
self.tableView.customContextMenuRequested.connect(
|
||||||
|
self.generate_library_context_menu)
|
||||||
|
|
||||||
# Keyboard shortcuts
|
# Keyboard shortcuts
|
||||||
self.ksCloseTab = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+W'), self)
|
self.ksDistractionFree = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+D'), self)
|
||||||
self.ksCloseTab.setContext(QtCore.Qt.ApplicationShortcut)
|
self.ksDistractionFree.setContext(QtCore.Qt.ApplicationShortcut)
|
||||||
self.ksCloseTab.activated.connect(self.tab_close)
|
self.ksDistractionFree.activated.connect(self.toggle_distraction_free)
|
||||||
|
|
||||||
|
self.ksOpenFile = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+O'), self)
|
||||||
|
self.ksOpenFile.setContext(QtCore.Qt.ApplicationShortcut)
|
||||||
|
self.ksOpenFile.activated.connect(self.add_books)
|
||||||
|
|
||||||
self.ksExitAll = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+Q'), self)
|
self.ksExitAll = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+Q'), self)
|
||||||
self.ksExitAll.setContext(QtCore.Qt.ApplicationShortcut)
|
self.ksExitAll.setContext(QtCore.Qt.ApplicationShortcut)
|
||||||
self.ksExitAll.activated.connect(self.closeEvent)
|
self.ksExitAll.activated.connect(self.closeEvent)
|
||||||
|
|
||||||
|
self.ksCloseTab = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+W'), self)
|
||||||
|
self.ksCloseTab.setContext(QtCore.Qt.ApplicationShortcut)
|
||||||
|
self.ksCloseTab.activated.connect(self.tab_close)
|
||||||
|
|
||||||
|
self.ksDeletePressed = QtWidgets.QShortcut(QtGui.QKeySequence('Delete'), self)
|
||||||
|
self.ksDeletePressed.setContext(QtCore.Qt.ApplicationShortcut)
|
||||||
|
self.ksDeletePressed.activated.connect(self.delete_pressed)
|
||||||
|
|
||||||
self.listView.setFocus()
|
self.listView.setFocus()
|
||||||
self.open_books_at_startup()
|
self.open_books_at_startup()
|
||||||
|
|
||||||
@@ -289,7 +316,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
file_list = [QtCore.QFileInfo(i).absoluteFilePath() for i in my_args]
|
file_list = [QtCore.QFileInfo(i).absoluteFilePath() for i in my_args]
|
||||||
books = sorter.BookSorter(
|
books = sorter.BookSorter(
|
||||||
file_list,
|
file_list,
|
||||||
'addition',
|
('addition', 'manual'),
|
||||||
self.database_path,
|
self.database_path,
|
||||||
self.settings['auto_tags'],
|
self.settings['auto_tags'],
|
||||||
self.temp_dir.path())
|
self.temp_dir.path())
|
||||||
@@ -402,9 +429,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
if self.tabWidget.currentIndex() != 0:
|
if self.tabWidget.currentIndex() != 0:
|
||||||
self.tabWidget.widget(self.tabWidget.currentIndex()).add_bookmark()
|
self.tabWidget.widget(self.tabWidget.currentIndex()).add_bookmark()
|
||||||
|
|
||||||
def test_function(self):
|
|
||||||
print('Caesar si viveret, ad remum dareris')
|
|
||||||
|
|
||||||
def resizeEvent(self, event=None):
|
def resizeEvent(self, event=None):
|
||||||
if event:
|
if event:
|
||||||
# This implies a vertical resize event only
|
# This implies a vertical resize event only
|
||||||
@@ -420,7 +444,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
# First, calculate the number of images per row
|
# First, calculate the number of images per row
|
||||||
i = self.listView.viewport().width() / default_size
|
i = self.listView.viewport().width() / default_size
|
||||||
rem = i - int(i)
|
rem = i - int(i)
|
||||||
if rem >= .11875 and rem <= .9999:
|
if rem >= .21875 and rem <= .9999:
|
||||||
num_images = int(i)
|
num_images = int(i)
|
||||||
else:
|
else:
|
||||||
num_images = int(i) - 1
|
num_images = int(i) - 1
|
||||||
@@ -441,14 +465,11 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def add_books(self):
|
def add_books(self):
|
||||||
# TODO
|
dialog_prompt = self._translate('Main_UI', 'Add books to database')
|
||||||
# Remember file addition modality
|
ebooks_string = self._translate('Main_UI', 'eBooks')
|
||||||
# If a file is added from here, it should not be removed
|
|
||||||
# from the libary in case of a database refresh
|
|
||||||
|
|
||||||
opened_files = QtWidgets.QFileDialog.getOpenFileNames(
|
opened_files = QtWidgets.QFileDialog.getOpenFileNames(
|
||||||
self, 'Open file', self.settings['last_open_path'],
|
self, dialog_prompt, self.settings['last_open_path'],
|
||||||
f'eBooks ({self.available_parsers})')
|
f'{ebooks_string} ({self.available_parsers})')
|
||||||
|
|
||||||
if not opened_files[0]:
|
if not opened_files[0]:
|
||||||
return
|
return
|
||||||
@@ -458,9 +479,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
self.settings['last_open_path'] = os.path.dirname(opened_files[0][0])
|
self.settings['last_open_path'] = os.path.dirname(opened_files[0][0])
|
||||||
self.sorterProgress.setVisible(True)
|
self.sorterProgress.setVisible(True)
|
||||||
self.statusMessage.setText('Adding books...')
|
self.statusMessage.setText(self._translate('Main_UI', 'Adding books...'))
|
||||||
self.thread = BackGroundBookAddition(
|
self.thread = BackGroundBookAddition(
|
||||||
opened_files[0], self.database_path, False, self)
|
opened_files[0], self.database_path, 'manual', self)
|
||||||
self.thread.finished.connect(self.move_on)
|
self.thread.finished.connect(self.move_on)
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
@@ -480,11 +501,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
return selected_indexes
|
return selected_indexes
|
||||||
|
|
||||||
def delete_books(self, selected_indexes=None):
|
def delete_books(self, selected_indexes=None):
|
||||||
# TODO
|
|
||||||
# ? Mirror selection
|
|
||||||
# Ask if library files are to be excluded from further scans
|
|
||||||
# Make a checkbox for this
|
|
||||||
|
|
||||||
if not selected_indexes:
|
if not selected_indexes:
|
||||||
# Get a list of QItemSelection objects
|
# Get a list of QItemSelection objects
|
||||||
# What we're interested in is the indexes()[0] in each of them
|
# What we're interested in is the indexes()[0] in each of them
|
||||||
@@ -515,26 +531,32 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
# Update the database in the background
|
# Update the database in the background
|
||||||
self.thread = BackGroundBookDeletion(
|
self.thread = BackGroundBookDeletion(
|
||||||
delete_hashes, self.database_path, self)
|
delete_hashes, self.database_path)
|
||||||
self.thread.finished.connect(self.move_on)
|
self.thread.finished.connect(self.move_on)
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
# Generate a message box to confirm deletion
|
# Generate a message box to confirm deletion
|
||||||
selected_number = len(selected_indexes)
|
selected_number = len(selected_indexes)
|
||||||
confirm_deletion = QtWidgets.QMessageBox()
|
confirm_deletion = QtWidgets.QMessageBox()
|
||||||
confirm_deletion.setText('Delete %d book(s)?' % selected_number)
|
deletion_prompt = self._translate(
|
||||||
|
'Main_UI', f'Delete {selected_number} book(s)?')
|
||||||
|
confirm_deletion.setText(deletion_prompt)
|
||||||
confirm_deletion.setIcon(QtWidgets.QMessageBox.Question)
|
confirm_deletion.setIcon(QtWidgets.QMessageBox.Question)
|
||||||
confirm_deletion.setWindowTitle('Confirm deletion')
|
confirm_deletion.setWindowTitle(self._translate('Main_UI', 'Confirm deletion'))
|
||||||
confirm_deletion.setStandardButtons(
|
confirm_deletion.setStandardButtons(
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
|
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
|
||||||
confirm_deletion.buttonClicked.connect(ifcontinue)
|
confirm_deletion.buttonClicked.connect(ifcontinue)
|
||||||
confirm_deletion.show()
|
confirm_deletion.show()
|
||||||
confirm_deletion.exec_()
|
confirm_deletion.exec_()
|
||||||
|
|
||||||
|
def delete_pressed(self):
|
||||||
|
if self.tabWidget.currentIndex() == 0:
|
||||||
|
self.delete_books()
|
||||||
|
|
||||||
def move_on(self):
|
def move_on(self):
|
||||||
self.settingsDialog.okButton.setEnabled(True)
|
self.settingsDialog.okButton.setEnabled(True)
|
||||||
self.settingsDialog.okButton.setToolTip(
|
self.settingsDialog.okButton.setToolTip(
|
||||||
'Save changes and start library scan')
|
self._translate('Main_UI', 'Save changes and start library scan'))
|
||||||
self.reloadLibrary.setEnabled(True)
|
self.reloadLibrary.setEnabled(True)
|
||||||
|
|
||||||
self.sorterProgress.setVisible(False)
|
self.sorterProgress.setVisible(False)
|
||||||
@@ -543,6 +565,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.lib_ref.update_proxymodels()
|
self.lib_ref.update_proxymodels()
|
||||||
self.lib_ref.generate_library_tags()
|
self.lib_ref.generate_library_tags()
|
||||||
|
|
||||||
|
self.statusMessage.setText(
|
||||||
|
str(self.lib_ref.item_proxy_model.rowCount()) +
|
||||||
|
self._translate('Main_UI', ' books'))
|
||||||
|
|
||||||
if not self.settings['perform_culling']:
|
if not self.settings['perform_culling']:
|
||||||
self.load_all_covers()
|
self.load_all_covers()
|
||||||
|
|
||||||
@@ -570,7 +596,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
self.resizeEvent()
|
self.resizeEvent()
|
||||||
self.start_culling_timer()
|
self.start_culling_timer()
|
||||||
if self.settings['show_toolbars']:
|
if self.settings['show_bars']:
|
||||||
self.bookToolBar.hide()
|
self.bookToolBar.hide()
|
||||||
self.libraryToolBar.show()
|
self.libraryToolBar.show()
|
||||||
|
|
||||||
@@ -578,10 +604,11 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
# Making the proxy model available doesn't affect
|
# Making the proxy model available doesn't affect
|
||||||
# memory utilization at all. Bleh.
|
# memory utilization at all. Bleh.
|
||||||
self.statusMessage.setText(
|
self.statusMessage.setText(
|
||||||
str(self.lib_ref.item_proxy_model.rowCount()) + ' Books')
|
str(self.lib_ref.item_proxy_model.rowCount()) +
|
||||||
|
self._translate('Main_UI', ' Books'))
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if self.settings['show_toolbars']:
|
if self.settings['show_bars']:
|
||||||
self.bookToolBar.show()
|
self.bookToolBar.show()
|
||||||
self.libraryToolBar.hide()
|
self.libraryToolBar.hide()
|
||||||
|
|
||||||
@@ -625,7 +652,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
self.tabWidget.widget(tab_index).update_last_accessed_time()
|
self.tabWidget.widget(tab_index).update_last_accessed_time()
|
||||||
self.tabWidget.removeTab(tab_index)
|
|
||||||
|
self.tabWidget.widget(tab_index).deleteLater()
|
||||||
|
self.tabWidget.widget(tab_index).setParent(None)
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
def set_toc_position(self, event=None):
|
def set_toc_position(self, event=None):
|
||||||
current_tab = self.tabWidget.widget(self.tabWidget.currentIndex())
|
current_tab = self.tabWidget.widget(self.tabWidget.currentIndex())
|
||||||
@@ -742,7 +772,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
contents = sorter.BookSorter(
|
contents = sorter.BookSorter(
|
||||||
file_paths,
|
file_paths,
|
||||||
'reading',
|
('reading', None),
|
||||||
self.database_path,
|
self.database_path,
|
||||||
True,
|
True,
|
||||||
self.temp_dir.path()).initiate_threads()
|
self.temp_dir.path()).initiate_threads()
|
||||||
@@ -751,7 +781,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
# New tabs are created here
|
# New tabs are created here
|
||||||
# Initial position adjustment is carried out by the tab itself
|
# Initial position adjustment is carried out by the tab itself
|
||||||
file_data = contents[i]
|
file_data = contents[i]
|
||||||
Tab(file_data, self.tabWidget)
|
Tab(file_data, self)
|
||||||
|
|
||||||
if self.settings['last_open_tab'] == 'library':
|
if self.settings['last_open_tab'] == 'library':
|
||||||
self.tabWidget.setCurrentIndex(0)
|
self.tabWidget.setCurrentIndex(0)
|
||||||
@@ -869,15 +899,19 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
profile_index, current_profile, QtCore.Qt.UserRole)
|
profile_index, current_profile, QtCore.Qt.UserRole)
|
||||||
self.format_contentView()
|
self.format_contentView()
|
||||||
|
|
||||||
def modify_comic_view(self):
|
def modify_comic_view(self, key_pressed=None):
|
||||||
signal_sender = self.sender().objectName()
|
if key_pressed:
|
||||||
|
signal_sender = None
|
||||||
|
else:
|
||||||
|
signal_sender = self.sender().objectName()
|
||||||
|
|
||||||
current_tab = self.tabWidget.widget(self.tabWidget.currentIndex())
|
current_tab = self.tabWidget.widget(self.tabWidget.currentIndex())
|
||||||
|
|
||||||
self.bookToolBar.fitWidth.setChecked(False)
|
self.bookToolBar.fitWidth.setChecked(False)
|
||||||
self.bookToolBar.bestFit.setChecked(False)
|
self.bookToolBar.bestFit.setChecked(False)
|
||||||
self.bookToolBar.originalSize.setChecked(False)
|
self.bookToolBar.originalSize.setChecked(False)
|
||||||
|
|
||||||
if signal_sender == 'zoomOut':
|
if signal_sender == 'zoomOut' or key_pressed == QtCore.Qt.Key_Minus:
|
||||||
self.comic_profile['zoom_mode'] = 'manualZoom'
|
self.comic_profile['zoom_mode'] = 'manualZoom'
|
||||||
self.comic_profile['padding'] += 50
|
self.comic_profile['padding'] += 50
|
||||||
|
|
||||||
@@ -885,7 +919,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
if self.comic_profile['padding'] * 2 > current_tab.contentView.viewport().width():
|
if self.comic_profile['padding'] * 2 > current_tab.contentView.viewport().width():
|
||||||
self.comic_profile['padding'] -= 50
|
self.comic_profile['padding'] -= 50
|
||||||
|
|
||||||
if signal_sender == 'zoomIn':
|
if signal_sender == 'zoomIn' or key_pressed in (QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal):
|
||||||
self.comic_profile['zoom_mode'] = 'manualZoom'
|
self.comic_profile['zoom_mode'] = 'manualZoom'
|
||||||
self.comic_profile['padding'] -= 50
|
self.comic_profile['padding'] -= 50
|
||||||
|
|
||||||
@@ -893,18 +927,18 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
if self.comic_profile['padding'] < 0:
|
if self.comic_profile['padding'] < 0:
|
||||||
self.comic_profile['padding'] = 0
|
self.comic_profile['padding'] = 0
|
||||||
|
|
||||||
if signal_sender == 'fitWidth':
|
if signal_sender == 'fitWidth' or key_pressed == QtCore.Qt.Key_W:
|
||||||
self.comic_profile['zoom_mode'] = 'fitWidth'
|
self.comic_profile['zoom_mode'] = 'fitWidth'
|
||||||
self.comic_profile['padding'] = 0
|
self.comic_profile['padding'] = 0
|
||||||
self.bookToolBar.fitWidth.setChecked(True)
|
self.bookToolBar.fitWidth.setChecked(True)
|
||||||
|
|
||||||
# Padding in the following cases is decided by
|
# Padding in the following cases is decided by
|
||||||
# the image pixmap loaded by the widget
|
# the image pixmap loaded by the widget
|
||||||
if signal_sender == 'bestFit':
|
if signal_sender == 'bestFit' or key_pressed == QtCore.Qt.Key_B:
|
||||||
self.comic_profile['zoom_mode'] = 'bestFit'
|
self.comic_profile['zoom_mode'] = 'bestFit'
|
||||||
self.bookToolBar.bestFit.setChecked(True)
|
self.bookToolBar.bestFit.setChecked(True)
|
||||||
|
|
||||||
if signal_sender == 'originalSize':
|
if signal_sender == 'originalSize' or key_pressed == QtCore.Qt.Key_O:
|
||||||
self.comic_profile['zoom_mode'] = 'originalSize'
|
self.comic_profile['zoom_mode'] = 'originalSize'
|
||||||
self.bookToolBar.originalSize.setChecked(True)
|
self.bookToolBar.originalSize.setChecked(True)
|
||||||
|
|
||||||
@@ -914,7 +948,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
# TODO
|
# TODO
|
||||||
# See what happens if a font isn't installed
|
# See what happens if a font isn't installed
|
||||||
|
|
||||||
current_tab = self.tabWidget.widget(self.tabWidget.currentIndex())
|
current_tab = self.tabWidget.widget(
|
||||||
|
self.tabWidget.currentIndex())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_metadata = current_tab.metadata
|
current_metadata = current_tab.metadata
|
||||||
@@ -999,19 +1034,24 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
context_menu = QtWidgets.QMenu()
|
context_menu = QtWidgets.QMenu()
|
||||||
|
|
||||||
openAction = context_menu.addAction(
|
openAction = context_menu.addAction(
|
||||||
self.QImageFactory.get_image('view-readermode'), 'Start reading')
|
self.QImageFactory.get_image('view-readermode'),
|
||||||
|
self._translate('Main_UI', 'Start reading'))
|
||||||
|
|
||||||
editAction = None
|
editAction = None
|
||||||
if len(selected_indexes) == 1:
|
if len(selected_indexes) == 1:
|
||||||
editAction = context_menu.addAction(
|
editAction = context_menu.addAction(
|
||||||
self.QImageFactory.get_image('edit-rename'), 'Edit')
|
self.QImageFactory.get_image('edit-rename'),
|
||||||
|
self._translate('Main_UI', 'Edit'))
|
||||||
|
|
||||||
deleteAction = context_menu.addAction(
|
deleteAction = context_menu.addAction(
|
||||||
self.QImageFactory.get_image('trash-empty'), 'Delete')
|
self.QImageFactory.get_image('trash-empty'),
|
||||||
|
self._translate('Main_UI', 'Delete'))
|
||||||
readAction = context_menu.addAction(
|
readAction = context_menu.addAction(
|
||||||
QtGui.QIcon(':/images/checkmark.svg'), 'Mark read')
|
QtGui.QIcon(':/images/checkmark.svg'),
|
||||||
|
self._translate('Main_UI', 'Mark read'))
|
||||||
unreadAction = context_menu.addAction(
|
unreadAction = context_menu.addAction(
|
||||||
QtGui.QIcon(':/images/xmark.svg'), 'Mark unread')
|
QtGui.QIcon(':/images/xmark.svg'),
|
||||||
|
self._translate('Main_UI', 'Mark unread'))
|
||||||
|
|
||||||
action = context_menu.exec_(self.sender().mapToGlobal(position))
|
action = context_menu.exec_(self.sender().mapToGlobal(position))
|
||||||
|
|
||||||
@@ -1108,11 +1148,13 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
checked = [i for i in directory_list if i[3] == QtCore.Qt.Checked]
|
checked = [i for i in directory_list if i[3] == QtCore.Qt.Checked]
|
||||||
filter_list = list(map(generate_name, checked))
|
filter_list = list(map(generate_name, checked))
|
||||||
filter_list.sort()
|
filter_list.sort()
|
||||||
filter_list.append('Manually Added')
|
|
||||||
filter_actions = [QtWidgets.QAction(i, self.libraryFilterMenu) for i in filter_list]
|
filter_list.append(self._translate('Main_UI', 'Manually Added'))
|
||||||
|
filter_actions = [QtWidgets.QAction(i, self.libraryFilterMenu) for i in filter_list]
|
||||||
|
|
||||||
filter_all = QtWidgets.QAction('All', self.libraryFilterMenu)
|
filter_all = QtWidgets.QAction('All', self.libraryFilterMenu)
|
||||||
filter_actions.append(filter_all)
|
filter_actions.append(filter_all)
|
||||||
|
|
||||||
for i in filter_actions:
|
for i in filter_actions:
|
||||||
i.setCheckable(True)
|
i.setCheckable(True)
|
||||||
i.setChecked(True)
|
i.setChecked(True)
|
||||||
@@ -1144,8 +1186,13 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
|
|
||||||
self.lib_ref.update_proxymodels()
|
self.lib_ref.update_proxymodels()
|
||||||
|
|
||||||
def toggle_toolbars(self):
|
def toggle_distraction_free(self):
|
||||||
self.settings['show_toolbars'] = not self.settings['show_toolbars']
|
self.settings['show_bars'] = not self.settings['show_bars']
|
||||||
|
|
||||||
|
self.statusBar.setVisible(
|
||||||
|
not self.statusBar.isVisible())
|
||||||
|
self.tabWidget.tabBar().setVisible(
|
||||||
|
not self.tabWidget.tabBar().isVisible())
|
||||||
|
|
||||||
current_tab = self.tabWidget.currentIndex()
|
current_tab = self.tabWidget.currentIndex()
|
||||||
if current_tab == 0:
|
if current_tab == 0:
|
||||||
@@ -1155,6 +1202,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
|||||||
self.bookToolBar.setVisible(
|
self.bookToolBar.setVisible(
|
||||||
not self.bookToolBar.isVisible())
|
not self.bookToolBar.isVisible())
|
||||||
|
|
||||||
|
self.start_culling_timer()
|
||||||
|
|
||||||
def closeEvent(self, event=None):
|
def closeEvent(self, event=None):
|
||||||
if event:
|
if event:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
@@ -1196,6 +1245,18 @@ def main():
|
|||||||
app = QtWidgets.QApplication(sys.argv)
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
app.setApplicationName('Lector') # This is needed for QStandardPaths
|
app.setApplicationName('Lector') # This is needed for QStandardPaths
|
||||||
# and my own hubris
|
# and my own hubris
|
||||||
|
|
||||||
|
# Internationalization support
|
||||||
|
translator = QtCore.QTranslator()
|
||||||
|
translations_found = translator.load(
|
||||||
|
QtCore.QLocale.system(), ':/translations/translations_bin/Lector_')
|
||||||
|
app.installTranslator(translator)
|
||||||
|
|
||||||
|
translations_out_string = '(Translations found)'
|
||||||
|
if not translations_found:
|
||||||
|
translations_out_string = '(No translations found)'
|
||||||
|
print(f'Locale: {QtCore.QLocale.system().name()}', translations_out_string)
|
||||||
|
|
||||||
form = MainUI()
|
form = MainUI()
|
||||||
form.show()
|
form.show()
|
||||||
form.resizeEvent()
|
form.resizeEvent()
|
||||||
|
@@ -25,29 +25,70 @@ from PyQt5 import QtCore
|
|||||||
class DatabaseInit:
|
class DatabaseInit:
|
||||||
def __init__(self, location_prefix):
|
def __init__(self, location_prefix):
|
||||||
os.makedirs(location_prefix, exist_ok=True)
|
os.makedirs(location_prefix, exist_ok=True)
|
||||||
database_path = os.path.join(location_prefix, 'Lector.db')
|
self.database_path = os.path.join(location_prefix, 'Lector.db')
|
||||||
|
|
||||||
if not os.path.exists(database_path):
|
self.books_table_columns = {
|
||||||
self.database = sqlite3.connect(database_path)
|
'id': 'INTEGER PRIMARY KEY',
|
||||||
|
'Title': 'TEXT',
|
||||||
|
'Author': 'TEXT',
|
||||||
|
'Year': 'INTEGER',
|
||||||
|
'DateAdded': 'BLOB',
|
||||||
|
'Path': 'TEXT',
|
||||||
|
'Position': 'BLOB',
|
||||||
|
'ISBN': 'TEXT',
|
||||||
|
'Tags': 'TEXT',
|
||||||
|
'Hash': 'TEXT',
|
||||||
|
'LastAccessed': 'BLOB',
|
||||||
|
'Bookmarks': 'BLOB',
|
||||||
|
'CoverImage': 'BLOB',
|
||||||
|
'Addition': 'TEXT'}
|
||||||
|
|
||||||
|
self.directories_table_columns = {
|
||||||
|
'id': 'INTEGER PRIMARY KEY',
|
||||||
|
'Path': 'TEXT',
|
||||||
|
'Name': 'TEXT',
|
||||||
|
'Tags': 'TEXT',
|
||||||
|
'CheckState': 'INTEGER'}
|
||||||
|
|
||||||
|
if os.path.exists(self.database_path):
|
||||||
|
self.check_database()
|
||||||
|
else:
|
||||||
self.create_database()
|
self.create_database()
|
||||||
|
|
||||||
def create_database(self):
|
def create_database(self):
|
||||||
# TODO
|
self.database = sqlite3.connect(self.database_path)
|
||||||
# Add separate columns for:
|
|
||||||
# addition mode
|
column_string = ', '.join(
|
||||||
self.database.execute(
|
[i[0] + ' ' + i[1] for i in self.books_table_columns.items()])
|
||||||
"CREATE TABLE books \
|
self.database.execute(f"CREATE TABLE books ({column_string})")
|
||||||
(id INTEGER PRIMARY KEY, Title TEXT, Author TEXT, Year INTEGER, DateAdded BLOB, \
|
|
||||||
Path TEXT, Position BLOB, ISBN TEXT, Tags TEXT, Hash TEXT, LastAccessed BLOB,\
|
|
||||||
Bookmarks BLOB, CoverImage BLOB)")
|
|
||||||
|
|
||||||
# CheckState is the standard QtCore.Qt.Checked / Unchecked
|
# CheckState is the standard QtCore.Qt.Checked / Unchecked
|
||||||
self.database.execute(
|
column_string = ', '.join(
|
||||||
"CREATE TABLE directories (id INTEGER PRIMARY KEY, Path TEXT, \
|
[i[0] + ' ' + i[1] for i in self.directories_table_columns.items()])
|
||||||
Name TEXT, Tags TEXT, CheckState INTEGER)")
|
self.database.execute(f"CREATE TABLE directories ({column_string})")
|
||||||
|
|
||||||
self.database.commit()
|
self.database.commit()
|
||||||
self.database.close()
|
self.database.close()
|
||||||
|
|
||||||
|
def check_database(self):
|
||||||
|
self.database = sqlite3.connect(self.database_path)
|
||||||
|
|
||||||
|
database_return = self.database.execute("PRAGMA table_info(books)").fetchall()
|
||||||
|
database_columns = [i[1] for i in database_return]
|
||||||
|
|
||||||
|
# This allows for addition of a column without having to reform the database
|
||||||
|
commit_required = False
|
||||||
|
for i in self.books_table_columns.items():
|
||||||
|
if i[0] not in database_columns:
|
||||||
|
commit_required = True
|
||||||
|
print(f'Database: Adding column {i[0]}')
|
||||||
|
sql_command = f"ALTER TABLE books ADD COLUMN {i[0]} {i[1]}"
|
||||||
|
self.database.execute(sql_command)
|
||||||
|
|
||||||
|
if commit_required:
|
||||||
|
self.database.commit()
|
||||||
|
self.database.close()
|
||||||
|
|
||||||
|
|
||||||
class DatabaseFunctions:
|
class DatabaseFunctions:
|
||||||
def __init__(self, location_prefix):
|
def __init__(self, location_prefix):
|
||||||
@@ -55,10 +96,6 @@ class DatabaseFunctions:
|
|||||||
self.database = sqlite3.connect(database_path)
|
self.database = sqlite3.connect(database_path)
|
||||||
|
|
||||||
def set_library_paths(self, data_iterable):
|
def set_library_paths(self, data_iterable):
|
||||||
# TODO
|
|
||||||
# INSERT OR REPLACE is not working
|
|
||||||
# So this is the old fashion kitchen sink approach
|
|
||||||
|
|
||||||
self.database.execute("DELETE FROM directories")
|
self.database.execute("DELETE FROM directories")
|
||||||
|
|
||||||
for i in data_iterable:
|
for i in data_iterable:
|
||||||
@@ -67,10 +104,13 @@ class DatabaseFunctions:
|
|||||||
tags = i[2]
|
tags = i[2]
|
||||||
is_checked = i[3]
|
is_checked = i[3]
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
continue # Remove invalid paths from the database
|
||||||
|
|
||||||
sql_command = (
|
sql_command = (
|
||||||
"INSERT OR REPLACE INTO directories (ID, Path, Name, Tags, CheckState)\
|
"INSERT INTO directories (Path, Name, Tags, CheckState)\
|
||||||
VALUES ((SELECT ID FROM directories WHERE Path = ?), ?, ?, ?, ?)")
|
VALUES (?, ?, ?, ?)")
|
||||||
self.database.execute(sql_command, [path, path, name, tags, is_checked])
|
self.database.execute(sql_command, [path, name, tags, is_checked])
|
||||||
|
|
||||||
self.database.commit()
|
self.database.commit()
|
||||||
self.database.close()
|
self.database.close()
|
||||||
@@ -95,6 +135,7 @@ class DatabaseFunctions:
|
|||||||
path = i[1]['path']
|
path = i[1]['path']
|
||||||
cover = i[1]['cover_image']
|
cover = i[1]['cover_image']
|
||||||
isbn = i[1]['isbn']
|
isbn = i[1]['isbn']
|
||||||
|
addition_mode = i[1]['addition_mode']
|
||||||
tags = i[1]['tags']
|
tags = i[1]['tags']
|
||||||
if tags:
|
if tags:
|
||||||
# Is a list. Needs to be a string
|
# Is a list. Needs to be a string
|
||||||
@@ -105,8 +146,9 @@ class DatabaseFunctions:
|
|||||||
|
|
||||||
sql_command_add = (
|
sql_command_add = (
|
||||||
"INSERT OR REPLACE INTO \
|
"INSERT OR REPLACE INTO \
|
||||||
books (Title, Author, Year, DateAdded, Path, ISBN, Tags, Hash, CoverImage) \
|
books (Title, Author, Year, DateAdded, Path, \
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
ISBN, Tags, Hash, CoverImage, Addition) \
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||||
|
|
||||||
cover_insert = None
|
cover_insert = None
|
||||||
if cover:
|
if cover:
|
||||||
@@ -115,7 +157,8 @@ class DatabaseFunctions:
|
|||||||
self.database.execute(
|
self.database.execute(
|
||||||
sql_command_add,
|
sql_command_add,
|
||||||
[title, author, year, current_datetime_bin,
|
[title, author, year, current_datetime_bin,
|
||||||
path, isbn, tags, book_hash, cover_insert])
|
path, isbn, tags, book_hash, cover_insert,
|
||||||
|
addition_mode])
|
||||||
|
|
||||||
self.database.commit()
|
self.database.commit()
|
||||||
self.database.close()
|
self.database.close()
|
||||||
@@ -208,9 +251,10 @@ class DatabaseFunctions:
|
|||||||
# target_data is an iterable
|
# target_data is an iterable
|
||||||
|
|
||||||
if column_name == '*':
|
if column_name == '*':
|
||||||
self.database.execute('DELETE FROM books')
|
self.database.execute(
|
||||||
|
"DELETE FROM books WHERE NOT Addition = 'manual'")
|
||||||
else:
|
else:
|
||||||
sql_command = f'DELETE FROM books WHERE {column_name} = ?'
|
sql_command = f"DELETE FROM books WHERE {column_name} = ?"
|
||||||
for i in target_data:
|
for i in target_data:
|
||||||
self.database.execute(sql_command, (i,))
|
self.database.execute(sql_command, (i,))
|
||||||
|
|
||||||
|
@@ -18,16 +18,17 @@
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from PyQt5 import QtWidgets, QtCore, QtGui, QtMultimedia
|
from PyQt5 import QtWidgets, QtCore, QtGui, QtMultimedia
|
||||||
|
from lector.resources import definitions
|
||||||
from resources import definitions
|
|
||||||
|
|
||||||
|
|
||||||
class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
|
class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(DefinitionsUI, self).__init__()
|
super(DefinitionsUI, self).__init__()
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self._translate = QtCore.QCoreApplication.translate
|
||||||
|
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
self.previous_position = None
|
||||||
|
|
||||||
self.setWindowFlags(
|
self.setWindowFlags(
|
||||||
QtCore.Qt.Popup |
|
QtCore.Qt.Popup |
|
||||||
@@ -39,6 +40,8 @@ class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
|
|||||||
mask = QtGui.QRegion(path.toFillPolygon().toPolygon())
|
mask = QtGui.QRegion(path.toFillPolygon().toPolygon())
|
||||||
self.setMask(mask)
|
self.setMask(mask)
|
||||||
|
|
||||||
|
self.definitionView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
|
|
||||||
self.app_id = 'bb7a91f9'
|
self.app_id = 'bb7a91f9'
|
||||||
self.app_key = 'fefacdf6775c347b52e9efa2efe642ef'
|
self.app_key = 'fefacdf6775c347b52e9efa2efe642ef'
|
||||||
|
|
||||||
@@ -109,8 +112,9 @@ class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
|
|||||||
html_string += f'<h2><em><strong>{word}</strong></em></h2>\n'
|
html_string += f'<h2><em><strong>{word}</strong></em></h2>\n'
|
||||||
|
|
||||||
if nothing_found:
|
if nothing_found:
|
||||||
|
nope_string = self._translate('DefinitionsUI', 'No definitions found in')
|
||||||
language = self.parent.settings['dictionary_language'].upper()
|
language = self.parent.settings['dictionary_language'].upper()
|
||||||
html_string += f'<p><em>No definitions found in {language}<em></p>\n'
|
html_string += f'<p><em>{nope_string} {language}<em></p>\n'
|
||||||
else:
|
else:
|
||||||
# Word root
|
# Word root
|
||||||
html_string += f'<p><em>Word root: <em>{word_root}</p>\n'
|
html_string += f'<p><em>Word root: <em>{word_root}</p>\n'
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||||
from resources import pie_chart
|
from lector.resources import pie_chart
|
||||||
|
|
||||||
|
|
||||||
class LibraryDelegate(QtWidgets.QStyledItemDelegate):
|
class LibraryDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
|
@@ -38,7 +38,7 @@ class EPUB:
|
|||||||
None, True)
|
None, True)
|
||||||
|
|
||||||
if not contents_path:
|
if not contents_path:
|
||||||
return False # No opf was found so processing cannot continue
|
return False # No (valid) opf was found so processing cannot continue
|
||||||
|
|
||||||
self.generate_book_metadata(contents_path)
|
self.generate_book_metadata(contents_path)
|
||||||
self.parse_toc()
|
self.parse_toc()
|
||||||
@@ -76,13 +76,17 @@ class EPUB:
|
|||||||
|
|
||||||
if xml:
|
if xml:
|
||||||
root_item = xml.find('rootfile')
|
root_item = xml.find('rootfile')
|
||||||
return root_item.get('full-path')
|
try:
|
||||||
else:
|
return root_item.get('full-path')
|
||||||
possible_filenames = ('content.opf', 'package.opf')
|
except AttributeError:
|
||||||
for i in possible_filenames:
|
print(f'ePub module: {self.filename} has a malformed container.xml')
|
||||||
presumptive_location = self.get_file_path(i)
|
return None
|
||||||
if presumptive_location:
|
|
||||||
return presumptive_location
|
possible_filenames = ('content.opf', 'package.opf')
|
||||||
|
for i in possible_filenames:
|
||||||
|
presumptive_location = self.get_file_path(i)
|
||||||
|
if presumptive_location:
|
||||||
|
return presumptive_location
|
||||||
|
|
||||||
for i in self.zip_file.filelist:
|
for i in self.zip_file.filelist:
|
||||||
if os.path.basename(i.filename) == os.path.basename(filename):
|
if os.path.basename(i.filename) == os.path.basename(filename):
|
||||||
@@ -105,7 +109,8 @@ class EPUB:
|
|||||||
#______________________________________________________
|
#______________________________________________________
|
||||||
|
|
||||||
def generate_book_metadata(self, contents_path):
|
def generate_book_metadata(self, contents_path):
|
||||||
self.book['title'] = 'Unknown'
|
self.book['title'] = os.path.splitext(
|
||||||
|
os.path.basename(self.filename))[0]
|
||||||
self.book['author'] = 'Unknown'
|
self.book['author'] = 'Unknown'
|
||||||
self.book['isbn'] = None
|
self.book['isbn'] = None
|
||||||
self.book['tags'] = None
|
self.book['tags'] = None
|
||||||
@@ -281,9 +286,11 @@ class EPUB:
|
|||||||
with open(cover_path, 'wb') as cover_temp:
|
with open(cover_path, 'wb') as cover_temp:
|
||||||
cover_temp.write(self.book['cover'])
|
cover_temp.write(self.book['cover'])
|
||||||
|
|
||||||
self.book['book_list'][0] = (
|
try:
|
||||||
'Cover', f'<center><img src="{cover_path}" alt="Cover"></center>')
|
self.book['book_list'][0] = (
|
||||||
|
'Cover', f'<center><img src="{cover_path}" alt="Cover"></center>')
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_split_content(chapter_data, split_by):
|
def get_split_content(chapter_data, split_by):
|
||||||
split_anchors = [i[0] for i in split_by]
|
split_anchors = [i[0] for i in split_by]
|
@@ -17,7 +17,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from PyQt5 import QtGui
|
from PyQt5 import QtGui
|
||||||
from resources import resources
|
from lector.resources import resources
|
||||||
|
|
||||||
|
|
||||||
class QImageFactory:
|
class QImageFactory:
|
||||||
|
@@ -20,7 +20,6 @@ import os
|
|||||||
import pickle
|
import pickle
|
||||||
import pathlib
|
import pathlib
|
||||||
from PyQt5 import QtGui, QtCore
|
from PyQt5 import QtGui, QtCore
|
||||||
|
|
||||||
from lector import database
|
from lector import database
|
||||||
from lector.models import TableProxyModel, ItemProxyModel
|
from lector.models import TableProxyModel, ItemProxyModel
|
||||||
|
|
||||||
@@ -31,6 +30,7 @@ class Library:
|
|||||||
self.view_model = None
|
self.view_model = None
|
||||||
self.item_proxy_model = None
|
self.item_proxy_model = None
|
||||||
self.table_proxy_model = None
|
self.table_proxy_model = None
|
||||||
|
self._translate = QtCore.QCoreApplication.translate
|
||||||
|
|
||||||
def generate_model(self, mode, parsed_books=None, is_database_ready=True):
|
def generate_model(self, mode, parsed_books=None, is_database_ready=True):
|
||||||
if mode == 'build':
|
if mode == 'build':
|
||||||
@@ -40,7 +40,8 @@ class Library:
|
|||||||
books = database.DatabaseFunctions(
|
books = database.DatabaseFunctions(
|
||||||
self.parent.database_path).fetch_data(
|
self.parent.database_path).fetch_data(
|
||||||
('Title', 'Author', 'Year', 'DateAdded', 'Path',
|
('Title', 'Author', 'Year', 'DateAdded', 'Path',
|
||||||
'Position', 'ISBN', 'Tags', 'Hash', 'LastAccessed'),
|
'Position', 'ISBN', 'Tags', 'Hash', 'LastAccessed',
|
||||||
|
'Addition'),
|
||||||
'books',
|
'books',
|
||||||
{'Title': ''},
|
{'Title': ''},
|
||||||
'LIKE')
|
'LIKE')
|
||||||
@@ -63,7 +64,7 @@ class Library:
|
|||||||
|
|
||||||
books.append([
|
books.append([
|
||||||
i[1]['title'], i[1]['author'], i[1]['year'], current_qdatetime,
|
i[1]['title'], i[1]['author'], i[1]['year'], current_qdatetime,
|
||||||
i[1]['path'], None, i[1]['isbn'], _tags, i[0], None])
|
i[1]['path'], None, i[1]['isbn'], _tags, i[0], None, i[1]['addition_mode']])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
@@ -75,7 +76,11 @@ class Library:
|
|||||||
author = i[1]
|
author = i[1]
|
||||||
year = i[2]
|
year = i[2]
|
||||||
path = i[4]
|
path = i[4]
|
||||||
|
addition_mode = i[10]
|
||||||
|
|
||||||
last_accessed = i[9]
|
last_accessed = i[9]
|
||||||
|
if last_accessed and not isinstance(last_accessed, QtCore.QDateTime):
|
||||||
|
last_accessed = pickle.loads(last_accessed)
|
||||||
|
|
||||||
tags = i[7]
|
tags = i[7]
|
||||||
if isinstance(tags, list): # When files are added for the first time
|
if isinstance(tags, list): # When files are added for the first time
|
||||||
@@ -102,7 +107,10 @@ class Library:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
position_perc = None
|
position_perc = None
|
||||||
|
|
||||||
file_exists = os.path.exists(path)
|
try:
|
||||||
|
file_exists = os.path.exists(path)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
print('Error with unicode encoding in the library module')
|
||||||
|
|
||||||
all_metadata = {
|
all_metadata = {
|
||||||
'title': title,
|
'title': title,
|
||||||
@@ -115,9 +123,12 @@ class Library:
|
|||||||
'tags': tags,
|
'tags': tags,
|
||||||
'hash': i[8],
|
'hash': i[8],
|
||||||
'last_accessed': last_accessed,
|
'last_accessed': last_accessed,
|
||||||
|
'addition_mode': addition_mode,
|
||||||
'file_exists': file_exists}
|
'file_exists': file_exists}
|
||||||
|
|
||||||
tooltip_string = title + '\nAuthor: ' + author + '\nYear: ' + str(year)
|
author_string = self._translate('Library', 'Author')
|
||||||
|
year_string = self._translate('Library', 'Year')
|
||||||
|
tooltip_string = f'{title} \n{author_string}: {author} \n{year_string}: {str(year)}'
|
||||||
|
|
||||||
# Additional data can be set using an incrementing
|
# Additional data can be set using an incrementing
|
||||||
# QtCore.Qt.UserRole
|
# QtCore.Qt.UserRole
|
||||||
@@ -162,7 +173,8 @@ class Library:
|
|||||||
self.parent.listView.setIconSize(s)
|
self.parent.listView.setIconSize(s)
|
||||||
self.parent.listView.setModel(self.item_proxy_model)
|
self.parent.listView.setModel(self.item_proxy_model)
|
||||||
|
|
||||||
self.table_proxy_model = TableProxyModel(self.parent.temp_dir.path())
|
self.table_proxy_model = TableProxyModel(
|
||||||
|
self.parent.temp_dir.path(), self.parent.tableView.horizontalHeader())
|
||||||
self.table_proxy_model.setSourceModel(self.view_model)
|
self.table_proxy_model.setSourceModel(self.view_model)
|
||||||
self.table_proxy_model.setSortCaseSensitivity(False)
|
self.table_proxy_model.setSortCaseSensitivity(False)
|
||||||
self.parent.tableView.setModel(self.table_proxy_model)
|
self.parent.tableView.setModel(self.table_proxy_model)
|
||||||
@@ -180,6 +192,9 @@ class Library:
|
|||||||
self.parent.libraryToolBar.searchBar.text())
|
self.parent.libraryToolBar.searchBar.text())
|
||||||
# ^^^ This isn't needed, but it forces a model update every time the
|
# ^^^ This isn't needed, but it forces a model update every time the
|
||||||
# text in the line edit changes. So I guess it is needed.
|
# text in the line edit changes. So I guess it is needed.
|
||||||
|
self.table_proxy_model.sort_table_columns(
|
||||||
|
self.parent.tableView.horizontalHeader().sortIndicatorSection())
|
||||||
|
self.table_proxy_model.sort_table_columns()
|
||||||
|
|
||||||
# Item proxy model
|
# Item proxy model
|
||||||
self.item_proxy_model.invalidateFilter()
|
self.item_proxy_model.invalidateFilter()
|
||||||
@@ -191,7 +206,8 @@ class Library:
|
|||||||
self.parent.libraryToolBar.searchBar.text())
|
self.parent.libraryToolBar.searchBar.text())
|
||||||
|
|
||||||
self.parent.statusMessage.setText(
|
self.parent.statusMessage.setText(
|
||||||
str(self.item_proxy_model.rowCount()) + ' books')
|
str(self.item_proxy_model.rowCount()) +
|
||||||
|
self._translate('Library', ' books'))
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# Allow sorting by type
|
# Allow sorting by type
|
||||||
@@ -227,11 +243,22 @@ class Library:
|
|||||||
{'Path': ''},
|
{'Path': ''},
|
||||||
'LIKE')
|
'LIKE')
|
||||||
|
|
||||||
if not db_library_directories: # Empty database / table
|
if db_library_directories: # Empty database / table
|
||||||
return
|
library_directories = {
|
||||||
|
i[0]: (i[1], i[2]) for i in db_library_directories}
|
||||||
|
|
||||||
library_directories = {
|
else:
|
||||||
i[0]: (i[1], i[2]) for i in db_library_directories}
|
db_library_directories = database.DatabaseFunctions(
|
||||||
|
self.parent.database_path).fetch_data(
|
||||||
|
('Path',),
|
||||||
|
'books', # THIS CHECKS THE BOOKS TABLE
|
||||||
|
{'Path': ''},
|
||||||
|
'LIKE')
|
||||||
|
|
||||||
|
library_directories = None
|
||||||
|
if db_library_directories:
|
||||||
|
library_directories = {
|
||||||
|
i[0]: (None, None) for i in db_library_directories}
|
||||||
|
|
||||||
def get_tags(all_metadata):
|
def get_tags(all_metadata):
|
||||||
path = os.path.dirname(all_metadata['path'])
|
path = os.path.dirname(all_metadata['path'])
|
||||||
@@ -243,7 +270,7 @@ class Library:
|
|||||||
if directory_name:
|
if directory_name:
|
||||||
directory_name = directory_name.lower()
|
directory_name = directory_name.lower()
|
||||||
else:
|
else:
|
||||||
directory_name = path.rsplit('/')[-1].lower()
|
directory_name = i.rsplit(os.sep)[-1].lower()
|
||||||
|
|
||||||
directory_tags = library_directories[i][1]
|
directory_tags = library_directories[i][1]
|
||||||
if directory_tags:
|
if directory_tags:
|
||||||
@@ -251,9 +278,13 @@ class Library:
|
|||||||
|
|
||||||
return directory_name, directory_tags
|
return directory_name, directory_tags
|
||||||
|
|
||||||
return 'manually added', None
|
# A file is assigned a 'manually added' tag in case it isn't
|
||||||
|
# in any designated library directory
|
||||||
|
added_string = self._translate('Library', 'manually added')
|
||||||
|
return added_string.lower(), None
|
||||||
|
|
||||||
# Generate tags for the QStandardItemModel
|
# Generate tags for the QStandardItemModel
|
||||||
|
# This isn't triggered for an empty view model
|
||||||
for i in range(self.view_model.rowCount()):
|
for i in range(self.view_model.rowCount()):
|
||||||
this_item = self.view_model.item(i, 0)
|
this_item = self.view_model.item(i, 0)
|
||||||
all_metadata = this_item.data(QtCore.Qt.UserRole + 3)
|
all_metadata = this_item.data(QtCore.Qt.UserRole + 3)
|
||||||
@@ -267,23 +298,24 @@ class Library:
|
|||||||
# All files in unselected directories will have to be removed
|
# All files in unselected directories will have to be removed
|
||||||
# from both of the models
|
# from both of the models
|
||||||
# They will also have to be deleted from the library
|
# They will also have to be deleted from the library
|
||||||
valid_paths = set(valid_paths)
|
invalid_paths = []
|
||||||
|
|
||||||
# Get all paths
|
|
||||||
all_paths = set()
|
|
||||||
for i in range(self.view_model.rowCount()):
|
|
||||||
item = self.view_model.item(i, 0)
|
|
||||||
item_metadata = item.data(QtCore.Qt.UserRole + 3)
|
|
||||||
book_path = item_metadata['path']
|
|
||||||
all_paths.add(book_path)
|
|
||||||
|
|
||||||
invalid_paths = all_paths - valid_paths
|
|
||||||
|
|
||||||
deletable_persistent_indexes = []
|
deletable_persistent_indexes = []
|
||||||
|
|
||||||
for i in range(self.view_model.rowCount()):
|
for i in range(self.view_model.rowCount()):
|
||||||
item = self.view_model.item(i)
|
item = self.view_model.item(i)
|
||||||
path = item.data(QtCore.Qt.UserRole + 3)['path']
|
|
||||||
if path in invalid_paths:
|
item_metadata = item.data(QtCore.Qt.UserRole + 3)
|
||||||
|
book_path = item_metadata['path']
|
||||||
|
try:
|
||||||
|
addition_mode = item_metadata['addition_mode']
|
||||||
|
except KeyError:
|
||||||
|
addition_mode = 'automatic'
|
||||||
|
print('Libary: Error setting addition mode for prune')
|
||||||
|
|
||||||
|
if (book_path not in valid_paths and
|
||||||
|
(addition_mode != 'manual' or addition_mode is None)):
|
||||||
|
|
||||||
|
invalid_paths.append(book_path)
|
||||||
deletable_persistent_indexes.append(
|
deletable_persistent_indexes.append(
|
||||||
QtCore.QPersistentModelIndex(item.index()))
|
QtCore.QPersistentModelIndex(item.index()))
|
||||||
|
|
||||||
|
@@ -19,15 +19,15 @@
|
|||||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||||
|
|
||||||
from lector import database
|
from lector import database
|
||||||
|
|
||||||
from resources import metadata
|
|
||||||
from lector.widgets import PliantQGraphicsScene
|
from lector.widgets import PliantQGraphicsScene
|
||||||
|
from lector.resources import metadata
|
||||||
|
|
||||||
|
|
||||||
class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(MetadataUI, self).__init__()
|
super(MetadataUI, self).__init__()
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self._translate = QtCore.QCoreApplication.translate
|
||||||
|
|
||||||
self.setWindowFlags(
|
self.setWindowFlags(
|
||||||
QtCore.Qt.Popup |
|
QtCore.Qt.Popup |
|
||||||
@@ -85,7 +85,7 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
|||||||
graphics_scene.addPixmap(image_pixmap)
|
graphics_scene.addPixmap(image_pixmap)
|
||||||
self.coverView.setScene(graphics_scene)
|
self.coverView.setScene(graphics_scene)
|
||||||
|
|
||||||
def ok_pressed(self, event):
|
def ok_pressed(self, event=None):
|
||||||
book_item = self.parent.lib_ref.view_model.item(self.book_index.row())
|
book_item = self.parent.lib_ref.view_model.item(self.book_index.row())
|
||||||
|
|
||||||
title = self.titleLine.text()
|
title = self.titleLine.text()
|
||||||
@@ -97,7 +97,9 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
year = self.book_year
|
year = self.book_year
|
||||||
|
|
||||||
tooltip_string = title + '\nAuthor: ' + author + '\nYear: ' + str(year)
|
author_string = self._translate('MetadataUI', 'Author')
|
||||||
|
year_string = self._translate('MetadataUI', 'Year')
|
||||||
|
tooltip_string = f'{title} \n{author_string}: {author} \n{year_string}: {str(year)}'
|
||||||
|
|
||||||
book_item.setData(title, QtCore.Qt.UserRole)
|
book_item.setData(title, QtCore.Qt.UserRole)
|
||||||
book_item.setData(author, QtCore.Qt.UserRole + 1)
|
book_item.setData(author, QtCore.Qt.UserRole + 1)
|
||||||
@@ -123,7 +125,7 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
|||||||
database.DatabaseFunctions(self.database_path).modify_metadata(
|
database.DatabaseFunctions(self.database_path).modify_metadata(
|
||||||
database_dict, book_hash)
|
database_dict, book_hash)
|
||||||
|
|
||||||
def cancel_pressed(self, event):
|
def cancel_pressed(self, event=None):
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
def generate_display_position(self, mouse_cursor_position):
|
def generate_display_position(self, mouse_cursor_position):
|
||||||
|
@@ -16,18 +16,17 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import pickle
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from resources import pie_chart
|
from lector.resources import pie_chart
|
||||||
|
|
||||||
|
|
||||||
class BookmarkProxyModel(QtCore.QSortFilterProxyModel):
|
class BookmarkProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(BookmarkProxyModel, self).__init__(parent)
|
super(BookmarkProxyModel, self).__init__(parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.filter_string = None
|
self.filter_text = None
|
||||||
|
|
||||||
def setFilterParams(self, filter_text):
|
def setFilterParams(self, filter_text):
|
||||||
self.filter_text = filter_text
|
self.filter_text = filter_text
|
||||||
@@ -66,10 +65,20 @@ class ItemProxyModel(QtCore.QSortFilterProxyModel):
|
|||||||
|
|
||||||
|
|
||||||
class TableProxyModel(QtCore.QSortFilterProxyModel):
|
class TableProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
def __init__(self, temp_dir, parent=None):
|
def __init__(self, temp_dir, tableViewHeader, parent=None):
|
||||||
super(TableProxyModel, self).__init__(parent)
|
super(TableProxyModel, self).__init__(parent)
|
||||||
|
self.tableViewHeader = tableViewHeader
|
||||||
|
self._translate = QtCore.QCoreApplication.translate
|
||||||
|
|
||||||
|
title_string = self._translate('TableProxyModel', 'Title')
|
||||||
|
author_string = self._translate('TableProxyModel', 'Author')
|
||||||
|
year_string = self._translate('TableProxyModel', 'Year')
|
||||||
|
lastread_string = self._translate('TableProxyModel', 'Last Read')
|
||||||
|
tags_string = self._translate('TableProxyModel', 'Tags')
|
||||||
self.header_data = [
|
self.header_data = [
|
||||||
None, 'Title', 'Author', 'Year', 'Last Read', '%', 'Tags']
|
None, title_string, author_string,
|
||||||
|
year_string, lastread_string, '%', tags_string]
|
||||||
|
|
||||||
self.temp_dir = temp_dir
|
self.temp_dir = temp_dir
|
||||||
self.filter_text = None
|
self.filter_text = None
|
||||||
self.active_library_filters = None
|
self.active_library_filters = None
|
||||||
@@ -88,7 +97,11 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
|||||||
|
|
||||||
def headerData(self, column, orientation, role):
|
def headerData(self, column, orientation, role):
|
||||||
if role == QtCore.Qt.DisplayRole:
|
if role == QtCore.Qt.DisplayRole:
|
||||||
return self.header_data[column]
|
try:
|
||||||
|
return self.header_data[column]
|
||||||
|
except IndexError:
|
||||||
|
print('Table proxy model: Can\'t find header for column', column)
|
||||||
|
return 'IndexError'
|
||||||
|
|
||||||
def flags(self, index):
|
def flags(self, index):
|
||||||
# Tag editing will take place by way of a right click menu
|
# Tag editing will take place by way of a right click menu
|
||||||
@@ -143,11 +156,8 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
|||||||
return QtCore.QVariant()
|
return QtCore.QVariant()
|
||||||
|
|
||||||
if index.column() == 4:
|
if index.column() == 4:
|
||||||
last_accessed_time = item.data(self.role_dictionary[index.column()])
|
last_accessed = item.data(self.role_dictionary[index.column()])
|
||||||
if last_accessed_time:
|
if last_accessed:
|
||||||
last_accessed = last_accessed_time
|
|
||||||
if not isinstance(last_accessed_time, QtCore.QDateTime):
|
|
||||||
last_accessed = pickle.loads(last_accessed_time)
|
|
||||||
right_now = QtCore.QDateTime().currentDateTime()
|
right_now = QtCore.QDateTime().currentDateTime()
|
||||||
time_diff = last_accessed.msecsTo(right_now)
|
time_diff = last_accessed.msecsTo(right_now)
|
||||||
return self.time_convert(time_diff // 1000)
|
return self.time_convert(time_diff // 1000)
|
||||||
@@ -164,10 +174,13 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
|||||||
output = self.common_functions.filterAcceptsRow(row, parent)
|
output = self.common_functions.filterAcceptsRow(row, parent)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def sort_table_columns(self, column):
|
def sort_table_columns(self, column=None):
|
||||||
sorting_order = self.sender().sortIndicatorOrder()
|
column = self.tableViewHeader.sortIndicatorSection()
|
||||||
|
sorting_order = self.tableViewHeader.sortIndicatorOrder()
|
||||||
|
|
||||||
self.sort(0, sorting_order)
|
self.sort(0, sorting_order)
|
||||||
self.setSortRole(self.role_dictionary[column])
|
if column != 0:
|
||||||
|
self.setSortRole(self.role_dictionary[column])
|
||||||
|
|
||||||
def time_convert(self, seconds):
|
def time_convert(self, seconds):
|
||||||
seconds = int(seconds)
|
seconds = int(seconds)
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import zipfile
|
import zipfile
|
||||||
from rarfile import rarfile
|
from lector.rarfile import rarfile
|
||||||
|
|
||||||
|
|
||||||
class ParseCOMIC:
|
class ParseCOMIC:
|
||||||
@@ -49,7 +49,8 @@ class ParseCOMIC:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.book_extension[0]
|
title = os.path.basename(self.book_extension[0]).strip(' ')
|
||||||
|
return title
|
||||||
|
|
||||||
def get_author(self):
|
def get_author(self):
|
||||||
return None
|
return None
|
@@ -19,7 +19,7 @@
|
|||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from ePub.read_epub import EPUB
|
from lector.ePub.read_epub import EPUB
|
||||||
|
|
||||||
|
|
||||||
class ParseEPUB:
|
class ParseEPUB:
|
@@ -24,8 +24,8 @@ import sys
|
|||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from ePub.read_epub import EPUB
|
from lector.ePub.read_epub import EPUB
|
||||||
import KindleUnpack.kindleunpack as KindleUnpack
|
import lector.KindleUnpack.kindleunpack as KindleUnpack
|
||||||
|
|
||||||
|
|
||||||
class ParseMOBI:
|
class ParseMOBI:
|
@@ -17,6 +17,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ class ParsePDF:
|
|||||||
title = self.metadata.find('title').text
|
title = self.metadata.find('title').text
|
||||||
return title.replace('\n', '')
|
return title.replace('\n', '')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return 'Unknown'
|
return os.path.splitext(os.path.basename(self.filename))[0]
|
||||||
|
|
||||||
def get_author(self):
|
def get_author(self):
|
||||||
try:
|
try:
|
Before Width: | Height: | Size: 428 B After Width: | Height: | Size: 428 B |
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 703 B |
Before Width: | Height: | Size: 546 B After Width: | Height: | Size: 546 B |
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 891 B |
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 729 B |
8
lector/resources/raw/DarkIcons/filesaveas.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<defs>
|
||||||
|
<style id="current-color-scheme" type="text/css">
|
||||||
|
.ColorScheme-Text { color:#5c616c; } .ColorScheme-Highlight { color:#5294e2; }
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor" class="ColorScheme-Text" d="M 5.9980469 1.0195312 L 5.9980469 7.0195312 L 3.6582031 7.0195312 L 7.9902344 13.324219 L 12.371094 7.0195312 L 9.9980469 7.0195312 L 9.9980469 1.0488281 L 5.9980469 1.0195312 z M 1 14.03125 L 1 16 L 15.005859 16 L 15 14.03125 L 1 14.03125 z" transform="translate(4 4)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 586 B |
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 601 B After Width: | Height: | Size: 601 B |
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 694 B After Width: | Height: | Size: 694 B |
Before Width: | Height: | Size: 642 B After Width: | Height: | Size: 642 B |
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 859 B |
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
8
lector/resources/raw/DarkIcons/search.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
|
||||||
|
<defs>
|
||||||
|
<style id="current-color-scheme" type="text/css">
|
||||||
|
.ColorScheme-Text { color:#5c616c; } .ColorScheme-Highlight { color:#5294e2; }
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor" class="ColorScheme-Text" d="M 6.4902344 0.99609375 C 3.4613344 0.99609375 0.99023438 3.4706937 0.99023438 6.4960938 C 0.99023438 9.5214938 3.4613344 11.996094 6.4902344 11.996094 C 7.6422344 11.996094 8.7279444 11.638254 9.6152344 11.027344 L 13.302734 14.714844 A 1.0055 1.0055 0 1 0 14.708984 13.277344 L 11.021484 9.5898438 C 11.632274 8.7038438 12.021484 7.6459938 12.021484 6.4960938 C 12.021484 3.4706937 9.5190344 0.99609375 6.4902344 0.99609375 z M 6.4902344 2.9960938 C 8.4376344 2.9960938 9.9902344 4.5508938 9.9902344 6.4960938 C 9.9902344 8.4411937 8.4376344 9.9960938 6.4902344 9.9960938 C 4.5428344 9.9960938 2.9902344 8.4411937 2.9902344 6.4960938 C 2.9902344 4.5508938 4.5428344 2.9960938 6.4902344 2.9960938 z" transform="translate(3 3)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 501 B After Width: | Height: | Size: 501 B |
8
lector/resources/raw/DarkIcons/tableofcontents.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
|
||||||
|
<defs>
|
||||||
|
<style id="current-color-scheme" type="text/css">
|
||||||
|
.ColorScheme-Text { color:#5c616c; } .ColorScheme-Highlight { color:#5294e2; }
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor" class="ColorScheme-Text" d="M 1 3.0039062 L 1 5.0039062 L 3 5.0039062 L 3 3.0039062 L 1 3.0039062 z M 5 3.0039062 L 5 5.0039062 L 15 5.0039062 L 15 3.0039062 L 5 3.0039062 z M 1 7.0039062 L 1 9.0039062 L 3 9.0039062 L 3 7.0039062 L 1 7.0039062 z M 5 7.0039062 L 5 9.0039062 L 15 9.0039062 L 15 7.0039062 L 5 7.0039062 z M 1 11.003906 L 1 13.003906 L 3 13.003906 L 3 11.003906 L 1 11.003906 z M 5 11.003906 L 5 13.003906 L 15 13.003906 L 15 11.003906 L 5 11.003906 z" transform="translate(3 3)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 782 B |
Before Width: | Height: | Size: 781 B After Width: | Height: | Size: 781 B |
Before Width: | Height: | Size: 916 B After Width: | Height: | Size: 916 B |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 790 B After Width: | Height: | Size: 790 B |
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 564 B |
Before Width: | Height: | Size: 540 B After Width: | Height: | Size: 540 B |
Before Width: | Height: | Size: 514 B After Width: | Height: | Size: 514 B |
BIN
lector/resources/raw/Lector.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 428 B After Width: | Height: | Size: 428 B |
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 703 B |
Before Width: | Height: | Size: 546 B After Width: | Height: | Size: 546 B |
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 891 B |
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 729 B |
8
lector/resources/raw/LightIcons/filesaveas.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<defs>
|
||||||
|
<style id="current-color-scheme" type="text/css">
|
||||||
|
.ColorScheme-Text { color:#d3dae3; } .ColorScheme-Highlight { color:#5294e2; }
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor" class="ColorScheme-Text" d="M 5.9980469 1.0195312 L 5.9980469 7.0195312 L 3.6582031 7.0195312 L 7.9902344 13.324219 L 12.371094 7.0195312 L 9.9980469 7.0195312 L 9.9980469 1.0488281 L 5.9980469 1.0195312 z M 1 14.03125 L 1 16 L 15.005859 16 L 15 14.03125 L 1 14.03125 z" transform="translate(4 4)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 586 B |
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 601 B After Width: | Height: | Size: 601 B |
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 694 B After Width: | Height: | Size: 694 B |
Before Width: | Height: | Size: 642 B After Width: | Height: | Size: 642 B |
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 859 B |
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |