Compare commits
57 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 | ||
|
a62e681223 | ||
|
efe52cd3cb | ||
|
23aff44412 | ||
|
e8e3b81871 | ||
|
03b683e05d | ||
|
5b3759afe6 | ||
|
fc2fcb5361 | ||
|
6ee135a52b | ||
|
55545f62e7 | ||
|
160226c6cd | ||
|
5d3ce17447 | ||
|
03afc6933f | ||
|
c9559daaf6 | ||
|
3c293a39d3 | ||
|
a87cd24c3d | ||
|
8564ede48b | ||
|
e0b20e36dd | ||
|
c2db6c13b0 | ||
|
dbff4cbcca | ||
|
ce9ee4ccb2 | ||
|
ca3d747136 | ||
|
9e5559bbfa | ||
|
9e9d7cca90 | ||
|
05e1655fd9 | ||
|
f9bcc399e8 | ||
|
1cd6ff6b58 | ||
|
79180885b5 |
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
__pycache__/
|
||||
.gitignore
|
||||
.vscode/
|
||||
parsers/__pycache__/
|
||||
books/
|
||||
Examples/
|
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
|
51
README.md
@@ -2,21 +2,49 @@
|
||||
Qt based ebook reader
|
||||
|
||||
Currently supports:
|
||||
* pdf
|
||||
* epub
|
||||
* mobi
|
||||
* azw / azw3 / azw4
|
||||
* cbr / cbz
|
||||
|
||||
Support for a bunch of other formats is coming. Please see the TODO for additional information.
|
||||
|
||||
## Requirements
|
||||
* Qt5
|
||||
* PyQt5
|
||||
* python-requests
|
||||
* python-beautifulsoup4
|
||||
| Package | Version tested |
|
||||
| --- | --- |
|
||||
| Qt5 | 5.10.1 |
|
||||
| Python | 3.6 |
|
||||
| PyQt5 | 5.10.1 |
|
||||
| python-requests | 2.18.4 |
|
||||
| python-beautifulsoup4 | 4.6.0 |
|
||||
| poppler-qt5 | 0.61.1 |
|
||||
| python-poppler-qt5 | 0.24.2 |
|
||||
|
||||
poppler-qt5 and python-poppler-qt5 are optional.
|
||||
|
||||
## Installation
|
||||
0. Install dependencies
|
||||
### Manual
|
||||
0. Install dependencies - I recommend using your package manager for this.
|
||||
1. Clone repository
|
||||
2. Launch with \_\_main\_\_.py
|
||||
2. Type the following in the root directory:
|
||||
|
||||
$ python setup.py build
|
||||
# python setup.py install
|
||||
3. OR launch with `lector/__main__.py`
|
||||
|
||||
### Available packages
|
||||
* [AUR](https://aur.archlinux.org/packages/lector-git/)
|
||||
* [Gentoo (unofficial)](https://bitbucket.org/szymonsz/gen2-overlay/src/master/app-text/lector/)
|
||||
|
||||
## Translations
|
||||
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.
|
||||
|
||||
Oh, please keep the translations short. There's only so much space for UI elements.
|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -43,3 +71,14 @@ Currently supports:
|
||||
|
||||
### 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
|
||||
* [KindleUnpack](https://github.com/kevinhendricks/KindleUnpack)
|
||||
* [rarfile](https://github.com/markokr/rarfile)
|
||||
* [Papirus icon theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme)
|
||||
|
24
TODO
@@ -1,4 +1,8 @@
|
||||
TODO
|
||||
General:
|
||||
✓ Internationalization
|
||||
Application icon
|
||||
.desktop file
|
||||
Options:
|
||||
✓ Automatic library management
|
||||
✓ Recursive file addition
|
||||
@@ -24,6 +28,7 @@ TODO
|
||||
✓ Context menu: Cache, Read, Edit database, delete, Mark read/unread
|
||||
✓ Information dialog widget
|
||||
✓ Allow editing of database data through the UI + for Bookmarks
|
||||
✓ Include (action) icons with the applications
|
||||
Set focus to newly added file
|
||||
Reading:
|
||||
✓ Drop down for TOC
|
||||
@@ -50,9 +55,13 @@ TODO
|
||||
✓ Cache next and previous images
|
||||
✓ Set context menu for definitions and the like
|
||||
✓ Paragraph indentation
|
||||
✓ Comic view keyboard shortcuts
|
||||
✓ Comic view context menu
|
||||
Adjust key navigation according to viewport dimensions
|
||||
Search document using QTextCursor?
|
||||
Comic view keyboard shortcuts
|
||||
Filetypes:
|
||||
✓ pdf support
|
||||
Parse TOC
|
||||
✓ epub support
|
||||
✓ Homegrown solution please
|
||||
✓ cbz, cbr support
|
||||
@@ -63,9 +72,8 @@ TODO
|
||||
Other:
|
||||
✓ Define every widget in code
|
||||
Bugs:
|
||||
If there are files open and the database is deleted, TypeErrors result
|
||||
Cover culling does not occur if some other tab has initial focus
|
||||
Exiting with Ctrl + Q does not save the cursor position INITIALLY
|
||||
Slider position change might be acting up
|
||||
Deselecting all directories in the settings dialog also filters out manually added books
|
||||
|
||||
Secondary:
|
||||
Annotations
|
||||
@@ -76,7 +84,7 @@ TODO
|
||||
Goodreads API: Ratings, Read, Recommendations
|
||||
Get ISBN using python-isbnlib
|
||||
Pagination
|
||||
Use embedded fonts
|
||||
Use embedded fonts + CSS
|
||||
Scrolling: Smooth / By Line
|
||||
Spacebar should not cut off lines at the top
|
||||
Shift to logging instead of print statements
|
||||
@@ -86,10 +94,6 @@ TODO
|
||||
Comic view modes
|
||||
Continuous paging
|
||||
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
|
||||
? Add only one file type if multiple are present
|
||||
? Plugin system for parsers
|
||||
? pdf support
|
||||
? Create emblem per filetype
|
||||
? Create emblem per filetype
|
||||
|
@@ -246,13 +246,13 @@ class MobiMLConverter(object):
|
||||
# handle case of end tag with no beginning by injecting empty begin tag
|
||||
taginfo = ('begin', tname, None)
|
||||
htmlstr += self.processtag(taginfo)
|
||||
print " - fixed by injecting empty start tag ", tname
|
||||
print(" - fixed by injecting empty start tag ", tname)
|
||||
self.path.append(tname)
|
||||
elif len(self.path) > 1 and tname == self.path[-2]:
|
||||
# handle case of dangling missing end
|
||||
taginfo = ('end', self.path[-1], None)
|
||||
htmlstr += self.processtag(taginfo)
|
||||
print " - fixed by injecting end tag ", self.path[-1]
|
||||
print(" - fixed by injecting end tag ", self.path[-1])
|
||||
self.path.pop()
|
||||
self.path.pop()
|
||||
|
||||
@@ -504,18 +504,18 @@ def main(argv=sys.argv):
|
||||
infile = argv[1]
|
||||
|
||||
try:
|
||||
print 'Converting Mobi Markup Language to XHTML'
|
||||
print('Converting Mobi Markup Language to XHTML')
|
||||
mlc = MobiMLConverter(infile)
|
||||
print 'Processing ...'
|
||||
print('Processing ...')
|
||||
htmlstr, css, cssname = mlc.processml()
|
||||
outname = infile.rsplit('.',1)[0] + '_converted.html'
|
||||
file(outname, 'wb').write(htmlstr)
|
||||
file(cssname, 'wb').write(css)
|
||||
print 'Completed'
|
||||
print 'XHTML version of book can be found at: ' + outname
|
||||
print('Completed')
|
||||
print('XHTML version of book can be found at: ', outname)
|
||||
|
||||
except ValueError, e:
|
||||
print "Error: %s" % e
|
||||
except ValueError as e:
|
||||
print("Error: %s" % e)
|
||||
return 1
|
||||
|
||||
return 0
|
@@ -17,24 +17,33 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import gc
|
||||
import sys
|
||||
import hashlib
|
||||
import pathlib
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
|
||||
import sorter
|
||||
import database
|
||||
# This allows for the program to be launched from the
|
||||
# dir where it's been copied instead of needing to be
|
||||
# installed
|
||||
install_dir = os.path.realpath(__file__)
|
||||
install_dir = pathlib.Path(install_dir).parents[1]
|
||||
sys.path.append(str(install_dir))
|
||||
|
||||
from resources import mainwindow, resources
|
||||
from toolbars import LibraryToolBar, BookToolBar
|
||||
from widgets import Tab
|
||||
from delegates import LibraryDelegate
|
||||
from threaded import BackGroundTabUpdate, BackGroundBookAddition, BackGroundBookDeletion
|
||||
from library import Library
|
||||
from settings import Settings
|
||||
from lector import database
|
||||
from lector import sorter
|
||||
from lector.toolbars import LibraryToolBar, BookToolBar
|
||||
from lector.widgets import Tab
|
||||
from lector.delegates import LibraryDelegate
|
||||
from lector.threaded import BackGroundTabUpdate, BackGroundBookAddition, BackGroundBookDeletion
|
||||
from lector.library import Library
|
||||
from lector.guifunctions import QImageFactory
|
||||
from lector.settings import Settings
|
||||
from lector.settingsdialog import SettingsUI
|
||||
from lector.metadatadialog import MetadataUI
|
||||
from lector.definitionsdialog import DefinitionsUI
|
||||
|
||||
from settingsdialog import SettingsUI
|
||||
from metadatadialog import MetadataUI
|
||||
from definitionsdialog import DefinitionsUI
|
||||
from lector.resources import mainwindow, resources
|
||||
|
||||
|
||||
class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
@@ -42,6 +51,17 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
super(MainUI, self).__init__()
|
||||
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
|
||||
self.settings = {}
|
||||
self.thread = None # Background Thread
|
||||
@@ -52,6 +72,13 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.database_path = None
|
||||
self.active_library_filters = []
|
||||
|
||||
# Initialize application
|
||||
Settings(self).read_settings() # This should populate all variables that need
|
||||
# to be remembered across sessions
|
||||
|
||||
# Initialize icon factory
|
||||
self.QImageFactory = QImageFactory(self)
|
||||
|
||||
# Initialize toolbars
|
||||
self.libraryToolBar = LibraryToolBar(self)
|
||||
self.bookToolBar = BookToolBar(self)
|
||||
@@ -59,12 +86,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
# Widget declarations
|
||||
self.libraryFilterMenu = 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
|
||||
# to be remembered across sessions
|
||||
self.distractionFreeToggle = QtWidgets.QToolButton()
|
||||
# self.reloadLibrary = QtWidgets.QToolButton()
|
||||
self.reloadLibrary = QtWidgets.QPushButton()
|
||||
|
||||
# Create the database in case it doesn't exist
|
||||
database.DatabaseInit(self.database_path)
|
||||
@@ -89,22 +113,14 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.statusBar.addWidget(self.sorterProgress)
|
||||
self.sorterProgress.setVisible(False)
|
||||
|
||||
# Statusbar - Toolbar Visibility
|
||||
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)
|
||||
# Statusbar + Toolbar Visibility
|
||||
self.distractionFreeToggle.setIcon(self.QImageFactory.get_image('visibility'))
|
||||
self.distractionFreeToggle.setObjectName('distractionFreeToggle')
|
||||
self.distractionFreeToggle.setToolTip(
|
||||
self._translate('Main_UI', 'Toggle distraction free mode (Ctrl + D)'))
|
||||
self.distractionFreeToggle.setAutoRaise(True)
|
||||
self.distractionFreeToggle.clicked.connect(self.toggle_distraction_free)
|
||||
self.statusBar.addPermanentWidget(self.distractionFreeToggle)
|
||||
|
||||
# Application wide temporary directory
|
||||
self.temp_dir = QtCore.QTemporaryDir()
|
||||
@@ -119,7 +135,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
|
||||
# Toolbar display
|
||||
# Maybe make this a persistent option
|
||||
self.settings['show_toolbars'] = True
|
||||
self.settings['show_bars'] = True
|
||||
|
||||
# Library toolbar
|
||||
self.libraryToolBar.addButton.triggered.connect(self.add_books)
|
||||
@@ -194,9 +210,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
print('Available parsers: ' + self.available_parsers)
|
||||
|
||||
# The library refresh button on the Library tab
|
||||
self.reloadLibrary.setIcon(QtGui.QIcon.fromTheme('reload'))
|
||||
self.reloadLibrary.setFlat(True)
|
||||
self.reloadLibrary.setIcon(self.QImageFactory.get_image('reload'))
|
||||
self.reloadLibrary.setObjectName('reloadLibrary')
|
||||
self.reloadLibrary.setAutoRaise(True)
|
||||
self.reloadLibrary.setToolTip(self._translate('Main_UI', 'Scan library'))
|
||||
self.reloadLibrary.clicked.connect(self.settingsDialog.start_library_scan)
|
||||
|
||||
self.tabWidget.tabBar().setTabButton(
|
||||
@@ -243,22 +260,35 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
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().resizeSection(4, 1)
|
||||
self.tableView.horizontalHeader().resizeSection(5, 1)
|
||||
self.tableView.horizontalHeader().setStretchLastSection(True)
|
||||
self.tableView.horizontalHeader().sectionClicked.connect(
|
||||
self.lib_ref.table_proxy_model.sort_table_columns)
|
||||
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
|
||||
self.ksCloseTab = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+W'), self)
|
||||
self.ksCloseTab.setContext(QtCore.Qt.ApplicationShortcut)
|
||||
self.ksCloseTab.activated.connect(self.tab_close)
|
||||
self.ksDistractionFree = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+D'), self)
|
||||
self.ksDistractionFree.setContext(QtCore.Qt.ApplicationShortcut)
|
||||
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.setContext(QtCore.Qt.ApplicationShortcut)
|
||||
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.open_books_at_startup()
|
||||
|
||||
@@ -286,12 +316,15 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
file_list = [QtCore.QFileInfo(i).absoluteFilePath() for i in my_args]
|
||||
books = sorter.BookSorter(
|
||||
file_list,
|
||||
'addition',
|
||||
('addition', 'manual'),
|
||||
self.database_path,
|
||||
self.settings['auto_tags'],
|
||||
self.temp_dir.path())
|
||||
|
||||
parsed_books = books.initiate_threads()
|
||||
if not parsed_books:
|
||||
return
|
||||
|
||||
database.DatabaseFunctions(self.database_path).add_to_database(parsed_books)
|
||||
self.lib_ref.generate_model('addition', parsed_books, True)
|
||||
|
||||
@@ -365,6 +398,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
{'Hash': ''},
|
||||
'LIKE')
|
||||
|
||||
if not all_covers_db:
|
||||
return
|
||||
|
||||
all_covers = {
|
||||
i[0]: i[1] for i in all_covers_db}
|
||||
|
||||
@@ -393,9 +429,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
if self.tabWidget.currentIndex() != 0:
|
||||
self.tabWidget.widget(self.tabWidget.currentIndex()).add_bookmark()
|
||||
|
||||
def test_function(self):
|
||||
print('Caesar si viveret, ad remum dareris')
|
||||
|
||||
def resizeEvent(self, event=None):
|
||||
if event:
|
||||
# This implies a vertical resize event only
|
||||
@@ -411,7 +444,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
# First, calculate the number of images per row
|
||||
i = self.listView.viewport().width() / default_size
|
||||
rem = i - int(i)
|
||||
if rem >= .11875 and rem <= .9999:
|
||||
if rem >= .21875 and rem <= .9999:
|
||||
num_images = int(i)
|
||||
else:
|
||||
num_images = int(i) - 1
|
||||
@@ -432,14 +465,11 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
return
|
||||
|
||||
def add_books(self):
|
||||
# TODO
|
||||
# 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
|
||||
|
||||
dialog_prompt = self._translate('Main_UI', 'Add books to database')
|
||||
ebooks_string = self._translate('Main_UI', 'eBooks')
|
||||
opened_files = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
self, 'Open file', self.settings['last_open_path'],
|
||||
f'eBooks ({self.available_parsers})')
|
||||
self, dialog_prompt, self.settings['last_open_path'],
|
||||
f'{ebooks_string} ({self.available_parsers})')
|
||||
|
||||
if not opened_files[0]:
|
||||
return
|
||||
@@ -449,9 +479,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
|
||||
self.settings['last_open_path'] = os.path.dirname(opened_files[0][0])
|
||||
self.sorterProgress.setVisible(True)
|
||||
self.statusMessage.setText('Adding books...')
|
||||
self.statusMessage.setText(self._translate('Main_UI', 'Adding books...'))
|
||||
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.start()
|
||||
|
||||
@@ -471,11 +501,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
return selected_indexes
|
||||
|
||||
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:
|
||||
# Get a list of QItemSelection objects
|
||||
# What we're interested in is the indexes()[0] in each of them
|
||||
@@ -506,26 +531,32 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
|
||||
# Update the database in the background
|
||||
self.thread = BackGroundBookDeletion(
|
||||
delete_hashes, self.database_path, self)
|
||||
delete_hashes, self.database_path)
|
||||
self.thread.finished.connect(self.move_on)
|
||||
self.thread.start()
|
||||
|
||||
# Generate a message box to confirm deletion
|
||||
selected_number = len(selected_indexes)
|
||||
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.setWindowTitle('Confirm deletion')
|
||||
confirm_deletion.setWindowTitle(self._translate('Main_UI', 'Confirm deletion'))
|
||||
confirm_deletion.setStandardButtons(
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
|
||||
confirm_deletion.buttonClicked.connect(ifcontinue)
|
||||
confirm_deletion.show()
|
||||
confirm_deletion.exec_()
|
||||
|
||||
def delete_pressed(self):
|
||||
if self.tabWidget.currentIndex() == 0:
|
||||
self.delete_books()
|
||||
|
||||
def move_on(self):
|
||||
self.settingsDialog.okButton.setEnabled(True)
|
||||
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.sorterProgress.setVisible(False)
|
||||
@@ -534,6 +565,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.lib_ref.update_proxymodels()
|
||||
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']:
|
||||
self.load_all_covers()
|
||||
|
||||
@@ -561,7 +596,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
|
||||
self.resizeEvent()
|
||||
self.start_culling_timer()
|
||||
if self.settings['show_toolbars']:
|
||||
if self.settings['show_bars']:
|
||||
self.bookToolBar.hide()
|
||||
self.libraryToolBar.show()
|
||||
|
||||
@@ -569,10 +604,11 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
# Making the proxy model available doesn't affect
|
||||
# memory utilization at all. Bleh.
|
||||
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:
|
||||
|
||||
if self.settings['show_toolbars']:
|
||||
if self.settings['show_bars']:
|
||||
self.bookToolBar.show()
|
||||
self.libraryToolBar.hide()
|
||||
|
||||
@@ -616,7 +652,10 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.thread.start()
|
||||
|
||||
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):
|
||||
current_tab = self.tabWidget.widget(self.tabWidget.currentIndex())
|
||||
@@ -733,7 +772,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
|
||||
contents = sorter.BookSorter(
|
||||
file_paths,
|
||||
'reading',
|
||||
('reading', None),
|
||||
self.database_path,
|
||||
True,
|
||||
self.temp_dir.path()).initiate_threads()
|
||||
@@ -742,7 +781,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
# New tabs are created here
|
||||
# Initial position adjustment is carried out by the tab itself
|
||||
file_data = contents[i]
|
||||
Tab(file_data, self.tabWidget)
|
||||
Tab(file_data, self)
|
||||
|
||||
if self.settings['last_open_tab'] == 'library':
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
@@ -860,15 +899,19 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
profile_index, current_profile, QtCore.Qt.UserRole)
|
||||
self.format_contentView()
|
||||
|
||||
def modify_comic_view(self):
|
||||
signal_sender = self.sender().objectName()
|
||||
def modify_comic_view(self, key_pressed=None):
|
||||
if key_pressed:
|
||||
signal_sender = None
|
||||
else:
|
||||
signal_sender = self.sender().objectName()
|
||||
|
||||
current_tab = self.tabWidget.widget(self.tabWidget.currentIndex())
|
||||
|
||||
self.bookToolBar.fitWidth.setChecked(False)
|
||||
self.bookToolBar.bestFit.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['padding'] += 50
|
||||
|
||||
@@ -876,7 +919,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
if self.comic_profile['padding'] * 2 > current_tab.contentView.viewport().width():
|
||||
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['padding'] -= 50
|
||||
|
||||
@@ -884,18 +927,18 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
if 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['padding'] = 0
|
||||
self.bookToolBar.fitWidth.setChecked(True)
|
||||
|
||||
# Padding in the following cases is decided by
|
||||
# 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.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.bookToolBar.originalSize.setChecked(True)
|
||||
|
||||
@@ -905,7 +948,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
# TODO
|
||||
# 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:
|
||||
current_metadata = current_tab.metadata
|
||||
@@ -990,19 +1034,24 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
context_menu = QtWidgets.QMenu()
|
||||
|
||||
openAction = context_menu.addAction(
|
||||
QtGui.QIcon.fromTheme('view-readermode'), 'Start reading')
|
||||
self.QImageFactory.get_image('view-readermode'),
|
||||
self._translate('Main_UI', 'Start reading'))
|
||||
|
||||
editAction = None
|
||||
if len(selected_indexes) == 1:
|
||||
editAction = context_menu.addAction(
|
||||
QtGui.QIcon.fromTheme('edit-rename'), 'Edit')
|
||||
self.QImageFactory.get_image('edit-rename'),
|
||||
self._translate('Main_UI', 'Edit'))
|
||||
|
||||
deleteAction = context_menu.addAction(
|
||||
QtGui.QIcon.fromTheme('trash-empty'), 'Delete')
|
||||
self.QImageFactory.get_image('trash-empty'),
|
||||
self._translate('Main_UI', 'Delete'))
|
||||
readAction = context_menu.addAction(
|
||||
QtGui.QIcon.fromTheme('vcs-normal'), 'Mark read')
|
||||
QtGui.QIcon(':/images/checkmark.svg'),
|
||||
self._translate('Main_UI', 'Mark read'))
|
||||
unreadAction = context_menu.addAction(
|
||||
QtGui.QIcon.fromTheme('emblem-unavailable'), 'Mark unread')
|
||||
QtGui.QIcon(':/images/xmark.svg'),
|
||||
self._translate('Main_UI', 'Mark unread'))
|
||||
|
||||
action = context_menu.exec_(self.sender().mapToGlobal(position))
|
||||
|
||||
@@ -1099,11 +1148,13 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
checked = [i for i in directory_list if i[3] == QtCore.Qt.Checked]
|
||||
filter_list = list(map(generate_name, checked))
|
||||
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_actions.append(filter_all)
|
||||
|
||||
for i in filter_actions:
|
||||
i.setCheckable(True)
|
||||
i.setChecked(True)
|
||||
@@ -1135,8 +1186,13 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
|
||||
self.lib_ref.update_proxymodels()
|
||||
|
||||
def toggle_toolbars(self):
|
||||
self.settings['show_toolbars'] = not self.settings['show_toolbars']
|
||||
def toggle_distraction_free(self):
|
||||
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()
|
||||
if current_tab == 0:
|
||||
@@ -1146,6 +1202,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
self.bookToolBar.setVisible(
|
||||
not self.bookToolBar.isVisible())
|
||||
|
||||
self.start_culling_timer()
|
||||
|
||||
def closeEvent(self, event=None):
|
||||
if event:
|
||||
event.ignore()
|
||||
@@ -1187,6 +1245,18 @@ def main():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
app.setApplicationName('Lector') # This is needed for QStandardPaths
|
||||
# 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.show()
|
||||
form.resizeEvent()
|
@@ -25,29 +25,70 @@ from PyQt5 import QtCore
|
||||
class DatabaseInit:
|
||||
def __init__(self, location_prefix):
|
||||
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.database = sqlite3.connect(database_path)
|
||||
self.books_table_columns = {
|
||||
'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()
|
||||
|
||||
def create_database(self):
|
||||
# TODO
|
||||
# Add separate columns for:
|
||||
# addition mode
|
||||
self.database.execute(
|
||||
"CREATE TABLE books \
|
||||
(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)")
|
||||
self.database = sqlite3.connect(self.database_path)
|
||||
|
||||
column_string = ', '.join(
|
||||
[i[0] + ' ' + i[1] for i in self.books_table_columns.items()])
|
||||
self.database.execute(f"CREATE TABLE books ({column_string})")
|
||||
|
||||
# CheckState is the standard QtCore.Qt.Checked / Unchecked
|
||||
self.database.execute(
|
||||
"CREATE TABLE directories (id INTEGER PRIMARY KEY, Path TEXT, \
|
||||
Name TEXT, Tags TEXT, CheckState INTEGER)")
|
||||
column_string = ', '.join(
|
||||
[i[0] + ' ' + i[1] for i in self.directories_table_columns.items()])
|
||||
self.database.execute(f"CREATE TABLE directories ({column_string})")
|
||||
|
||||
self.database.commit()
|
||||
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:
|
||||
def __init__(self, location_prefix):
|
||||
@@ -55,10 +96,6 @@ 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:
|
||||
@@ -67,10 +104,13 @@ class DatabaseFunctions:
|
||||
tags = i[2]
|
||||
is_checked = i[3]
|
||||
|
||||
if not os.path.exists(path):
|
||||
continue # Remove invalid paths from the database
|
||||
|
||||
sql_command = (
|
||||
"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])
|
||||
"INSERT INTO directories (Path, Name, Tags, CheckState)\
|
||||
VALUES (?, ?, ?, ?)")
|
||||
self.database.execute(sql_command, [path, name, tags, is_checked])
|
||||
|
||||
self.database.commit()
|
||||
self.database.close()
|
||||
@@ -95,6 +135,7 @@ class DatabaseFunctions:
|
||||
path = i[1]['path']
|
||||
cover = i[1]['cover_image']
|
||||
isbn = i[1]['isbn']
|
||||
addition_mode = i[1]['addition_mode']
|
||||
tags = i[1]['tags']
|
||||
if tags:
|
||||
# Is a list. Needs to be a string
|
||||
@@ -105,8 +146,9 @@ class DatabaseFunctions:
|
||||
|
||||
sql_command_add = (
|
||||
"INSERT OR REPLACE INTO \
|
||||
books (Title, Author, Year, DateAdded, Path, ISBN, Tags, Hash, CoverImage) \
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
books (Title, Author, Year, DateAdded, Path, \
|
||||
ISBN, Tags, Hash, CoverImage, Addition) \
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
|
||||
cover_insert = None
|
||||
if cover:
|
||||
@@ -115,7 +157,8 @@ class DatabaseFunctions:
|
||||
self.database.execute(
|
||||
sql_command_add,
|
||||
[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.close()
|
||||
@@ -208,9 +251,10 @@ class DatabaseFunctions:
|
||||
# target_data is an iterable
|
||||
|
||||
if column_name == '*':
|
||||
self.database.execute('DELETE FROM books')
|
||||
self.database.execute(
|
||||
"DELETE FROM books WHERE NOT Addition = 'manual'")
|
||||
else:
|
||||
sql_command = f'DELETE FROM books WHERE {column_name} = ?'
|
||||
sql_command = f"DELETE FROM books WHERE {column_name} = ?"
|
||||
for i in target_data:
|
||||
self.database.execute(sql_command, (i,))
|
||||
|
@@ -18,16 +18,17 @@
|
||||
|
||||
import requests
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui, QtMultimedia
|
||||
|
||||
from resources import definitions
|
||||
from lector.resources import definitions
|
||||
|
||||
|
||||
class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
|
||||
def __init__(self, parent):
|
||||
super(DefinitionsUI, self).__init__()
|
||||
self.setupUi(self)
|
||||
self._translate = QtCore.QCoreApplication.translate
|
||||
|
||||
self.parent = parent
|
||||
self.previous_position = None
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.Popup |
|
||||
@@ -39,6 +40,8 @@ class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
|
||||
mask = QtGui.QRegion(path.toFillPolygon().toPolygon())
|
||||
self.setMask(mask)
|
||||
|
||||
self.definitionView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
|
||||
self.app_id = 'bb7a91f9'
|
||||
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'
|
||||
|
||||
if nothing_found:
|
||||
nope_string = self._translate('DefinitionsUI', 'No definitions found in')
|
||||
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:
|
||||
# Word root
|
||||
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/>.
|
||||
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
from resources import pie_chart
|
||||
from lector.resources import pie_chart
|
||||
|
||||
|
||||
class LibraryDelegate(QtWidgets.QStyledItemDelegate):
|
@@ -17,7 +17,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
from urllib.parse import unquote
|
||||
|
||||
@@ -29,6 +28,7 @@ class EPUB:
|
||||
self.filename = filename
|
||||
self.zip_file = None
|
||||
self.book = {}
|
||||
self.book['split_chapters'] = {}
|
||||
|
||||
def read_epub(self):
|
||||
# This is the function that should error out in
|
||||
@@ -38,7 +38,7 @@ class EPUB:
|
||||
None, True)
|
||||
|
||||
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.parse_toc()
|
||||
@@ -57,7 +57,8 @@ class EPUB:
|
||||
try:
|
||||
this_xml = self.zip_file.read(filename).decode()
|
||||
except KeyError:
|
||||
print(str(filename) + ' not found in zip')
|
||||
short_filename = os.path.basename(self.filename)
|
||||
print(f'{str(filename)} not found in {short_filename}')
|
||||
return
|
||||
|
||||
root = BeautifulSoup(this_xml, parser)
|
||||
@@ -75,13 +76,17 @@ class EPUB:
|
||||
|
||||
if xml:
|
||||
root_item = xml.find('rootfile')
|
||||
return root_item.get('full-path')
|
||||
else:
|
||||
possible_filenames = ('content.opf', 'package.opf')
|
||||
for i in possible_filenames:
|
||||
presumptive_location = self.get_file_path(i)
|
||||
if presumptive_location:
|
||||
return presumptive_location
|
||||
try:
|
||||
return root_item.get('full-path')
|
||||
except AttributeError:
|
||||
print(f'ePub module: {self.filename} has a malformed container.xml')
|
||||
return None
|
||||
|
||||
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:
|
||||
if os.path.basename(i.filename) == os.path.basename(filename):
|
||||
@@ -104,7 +109,8 @@ class EPUB:
|
||||
#______________________________________________________
|
||||
|
||||
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['isbn'] = None
|
||||
self.book['tags'] = None
|
||||
@@ -127,7 +133,7 @@ class EPUB:
|
||||
|
||||
try:
|
||||
self.book['year'] = int(self.book['year'][:4])
|
||||
except (TypeError, KeyError, IndexError):
|
||||
except (TypeError, KeyError, IndexError, ValueError):
|
||||
self.book['year'] = 9999
|
||||
|
||||
# Get identifier
|
||||
@@ -170,7 +176,7 @@ class EPUB:
|
||||
self.get_file_path(cover_href))
|
||||
|
||||
if not self.book['cover']:
|
||||
# If no cover is located the conventioanl way,
|
||||
# If no cover is located the conventional way,
|
||||
# we go looking for the largest image in the book
|
||||
biggest_image_size = 0
|
||||
biggest_image = None
|
||||
@@ -215,33 +221,32 @@ class EPUB:
|
||||
for i in navpoints:
|
||||
chapter_title = i.find('text').text
|
||||
chapter_source = i.find('content').get('src')
|
||||
chapter_source = unquote(chapter_source.split('#')[0])
|
||||
self.book['navpoint_dict'][chapter_source] = chapter_title
|
||||
chapter_source_file = unquote(chapter_source.split('#')[0])
|
||||
|
||||
if '#' in chapter_source:
|
||||
try:
|
||||
self.book['split_chapters'][chapter_source_file].append(
|
||||
(chapter_source.split('#')[1], chapter_title))
|
||||
except KeyError:
|
||||
self.book['split_chapters'][chapter_source_file] = []
|
||||
self.book['split_chapters'][chapter_source_file].append(
|
||||
(chapter_source.split('#')[1], chapter_title))
|
||||
|
||||
self.book['navpoint_dict'][chapter_source_file] = chapter_title
|
||||
|
||||
def parse_chapters(self, temp_dir=None, split_large_xml=False):
|
||||
no_title_chapter = 0
|
||||
self.book['book_list'] = []
|
||||
|
||||
for i in self.book['chapters_in_order']:
|
||||
chapter_data = self.read_from_zip(i).decode()
|
||||
|
||||
if not split_large_xml:
|
||||
try:
|
||||
self.book['book_list'].append(
|
||||
(self.book['navpoint_dict'][i], chapter_data))
|
||||
except KeyError:
|
||||
fallback_title = str(no_title_chapter)
|
||||
self.book['book_list'].append(
|
||||
(fallback_title, chapter_data))
|
||||
no_title_chapter += 1
|
||||
if i in self.book['split_chapters'] and not split_large_xml:
|
||||
split_chapters = get_split_content(
|
||||
chapter_data, self.book['split_chapters'][i])
|
||||
self.book['book_list'].extend(split_chapters)
|
||||
|
||||
cover_path = os.path.join(temp_dir, os.path.basename(self.filename)) + '- cover'
|
||||
with open(cover_path, 'wb') as cover_temp:
|
||||
cover_temp.write(self.book['cover'])
|
||||
|
||||
self.book['book_list'][0] = (
|
||||
'Cover', f'<center><img src="{cover_path}" alt="Cover"></center>')
|
||||
|
||||
else:
|
||||
elif split_large_xml:
|
||||
# https://stackoverflow.com/questions/14444732/how-to-split-a-html-page-to-multiple-pages-using-python-and-beautiful-soup
|
||||
markup = BeautifulSoup(chapter_data, 'xml')
|
||||
chapters = []
|
||||
@@ -264,13 +269,56 @@ class EPUB:
|
||||
for this_chapter in chapters:
|
||||
fallback_title = str(no_title_chapter)
|
||||
self.book['book_list'].append(
|
||||
(fallback_title, this_chapter))
|
||||
(fallback_title, this_chapter + ('<br/>' * 8)))
|
||||
no_title_chapter += 1
|
||||
else:
|
||||
try:
|
||||
self.book['book_list'].append(
|
||||
(self.book['navpoint_dict'][i], chapter_data + ('<br/>' * 8)))
|
||||
except KeyError:
|
||||
fallback_title = str(no_title_chapter)
|
||||
self.book['book_list'].append(
|
||||
(fallback_title, chapter_data))
|
||||
no_title_chapter += 1
|
||||
|
||||
def main():
|
||||
book = EPUB(sys.argv[1])
|
||||
book.read_epub()
|
||||
book.parse_chapters()
|
||||
cover_path = os.path.join(temp_dir, os.path.basename(self.filename)) + '- cover'
|
||||
if self.book['cover']:
|
||||
with open(cover_path, 'wb') as cover_temp:
|
||||
cover_temp.write(self.book['cover'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
try:
|
||||
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):
|
||||
split_anchors = [i[0] for i in split_by]
|
||||
chapter_titles = [i[1] for i in split_by]
|
||||
return_list = []
|
||||
|
||||
xml = BeautifulSoup(chapter_data, 'lxml')
|
||||
xml_string = xml.body.prettify()
|
||||
|
||||
for count, i in enumerate(split_anchors):
|
||||
this_split = xml_string.split(i)
|
||||
current_chapter = this_split[0]
|
||||
|
||||
bs_obj = BeautifulSoup(current_chapter, 'lxml')
|
||||
# Since tags correspond to data following them, the first
|
||||
# chunk will be ignored
|
||||
# As will all empty chapters
|
||||
if bs_obj.text == '\n' or bs_obj.text == '' or count == 0:
|
||||
continue
|
||||
bs_obj_string = str(bs_obj).replace('">', '', 1) + ('<br/>' * 8)
|
||||
|
||||
return_list.append(
|
||||
(chapter_titles[count - 1], bs_obj_string))
|
||||
xml_string = this_split[1]
|
||||
|
||||
bs_obj = BeautifulSoup(xml_string, 'lxml')
|
||||
bs_obj_string = str(bs_obj).replace('">', '', 1) + ('<br/>' * 8)
|
||||
return_list.append(
|
||||
(chapter_titles[-1], bs_obj_string))
|
||||
|
||||
return return_list
|
32
lector/guifunctions.py
Normal file
@@ -0,0 +1,32 @@
|
||||
#!usr/bin/env python3
|
||||
|
||||
# This file is a part of Lector, a Qt based ebook reader
|
||||
# Copyright (C) 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/>.
|
||||
|
||||
from PyQt5 import QtGui
|
||||
from lector.resources import resources
|
||||
|
||||
|
||||
class QImageFactory:
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
|
||||
def get_image(self, image_name):
|
||||
icon_theme = self.parent.settings['icon_theme']
|
||||
icon_path = f':/images/{icon_theme}/{image_name}.svg'
|
||||
|
||||
this_qicon = QtGui.QIcon(icon_path)
|
||||
return this_qicon
|
@@ -20,9 +20,8 @@ import os
|
||||
import pickle
|
||||
import pathlib
|
||||
from PyQt5 import QtGui, QtCore
|
||||
|
||||
import database
|
||||
from models import TableProxyModel, ItemProxyModel
|
||||
from lector import database
|
||||
from lector.models import TableProxyModel, ItemProxyModel
|
||||
|
||||
|
||||
class Library:
|
||||
@@ -31,6 +30,7 @@ class Library:
|
||||
self.view_model = None
|
||||
self.item_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):
|
||||
if mode == 'build':
|
||||
@@ -40,7 +40,8 @@ class Library:
|
||||
books = database.DatabaseFunctions(
|
||||
self.parent.database_path).fetch_data(
|
||||
('Title', 'Author', 'Year', 'DateAdded', 'Path',
|
||||
'Position', 'ISBN', 'Tags', 'Hash', 'LastAccessed'),
|
||||
'Position', 'ISBN', 'Tags', 'Hash', 'LastAccessed',
|
||||
'Addition'),
|
||||
'books',
|
||||
{'Title': ''},
|
||||
'LIKE')
|
||||
@@ -63,7 +64,7 @@ class Library:
|
||||
|
||||
books.append([
|
||||
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:
|
||||
return
|
||||
@@ -75,7 +76,11 @@ class Library:
|
||||
author = i[1]
|
||||
year = i[2]
|
||||
path = i[4]
|
||||
addition_mode = i[10]
|
||||
|
||||
last_accessed = i[9]
|
||||
if last_accessed and not isinstance(last_accessed, QtCore.QDateTime):
|
||||
last_accessed = pickle.loads(last_accessed)
|
||||
|
||||
tags = i[7]
|
||||
if isinstance(tags, list): # When files are added for the first time
|
||||
@@ -102,7 +107,10 @@ class Library:
|
||||
except KeyError:
|
||||
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 = {
|
||||
'title': title,
|
||||
@@ -115,9 +123,12 @@ class Library:
|
||||
'tags': tags,
|
||||
'hash': i[8],
|
||||
'last_accessed': last_accessed,
|
||||
'addition_mode': addition_mode,
|
||||
'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
|
||||
# QtCore.Qt.UserRole
|
||||
@@ -162,7 +173,8 @@ class Library:
|
||||
self.parent.listView.setIconSize(s)
|
||||
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.setSortCaseSensitivity(False)
|
||||
self.parent.tableView.setModel(self.table_proxy_model)
|
||||
@@ -180,6 +192,9 @@ class Library:
|
||||
self.parent.libraryToolBar.searchBar.text())
|
||||
# ^^^ 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.sort_table_columns(
|
||||
self.parent.tableView.horizontalHeader().sortIndicatorSection())
|
||||
self.table_proxy_model.sort_table_columns()
|
||||
|
||||
# Item proxy model
|
||||
self.item_proxy_model.invalidateFilter()
|
||||
@@ -191,7 +206,8 @@ class Library:
|
||||
self.parent.libraryToolBar.searchBar.text())
|
||||
|
||||
self.parent.statusMessage.setText(
|
||||
str(self.item_proxy_model.rowCount()) + ' books')
|
||||
str(self.item_proxy_model.rowCount()) +
|
||||
self._translate('Library', ' books'))
|
||||
|
||||
# TODO
|
||||
# Allow sorting by type
|
||||
@@ -227,11 +243,22 @@ class Library:
|
||||
{'Path': ''},
|
||||
'LIKE')
|
||||
|
||||
if not db_library_directories: # Empty database / table
|
||||
return
|
||||
if db_library_directories: # Empty database / table
|
||||
library_directories = {
|
||||
i[0]: (i[1], i[2]) for i in db_library_directories}
|
||||
|
||||
library_directories = {
|
||||
i[0]: (i[1], i[2]) for i in db_library_directories}
|
||||
else:
|
||||
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):
|
||||
path = os.path.dirname(all_metadata['path'])
|
||||
@@ -243,7 +270,7 @@ class Library:
|
||||
if directory_name:
|
||||
directory_name = directory_name.lower()
|
||||
else:
|
||||
directory_name = path.rsplit('/')[-1].lower()
|
||||
directory_name = i.rsplit(os.sep)[-1].lower()
|
||||
|
||||
directory_tags = library_directories[i][1]
|
||||
if directory_tags:
|
||||
@@ -251,9 +278,13 @@ class Library:
|
||||
|
||||
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
|
||||
# This isn't triggered for an empty view model
|
||||
for i in range(self.view_model.rowCount()):
|
||||
this_item = self.view_model.item(i, 0)
|
||||
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
|
||||
# from both of the models
|
||||
# They will also have to be deleted from the library
|
||||
valid_paths = set(valid_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
|
||||
|
||||
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:
|
||||
|
||||
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(
|
||||
QtCore.QPersistentModelIndex(item.index()))
|
||||
|
@@ -18,16 +18,16 @@
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
import database
|
||||
|
||||
from resources import metadata
|
||||
from widgets import PliantQGraphicsScene
|
||||
from lector import database
|
||||
from lector.widgets import PliantQGraphicsScene
|
||||
from lector.resources import metadata
|
||||
|
||||
|
||||
class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
||||
def __init__(self, parent):
|
||||
super(MetadataUI, self).__init__()
|
||||
self.setupUi(self)
|
||||
self._translate = QtCore.QCoreApplication.translate
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.Popup |
|
||||
@@ -85,7 +85,7 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
||||
graphics_scene.addPixmap(image_pixmap)
|
||||
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())
|
||||
|
||||
title = self.titleLine.text()
|
||||
@@ -97,7 +97,9 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
||||
except ValueError:
|
||||
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(author, QtCore.Qt.UserRole + 1)
|
||||
@@ -123,7 +125,7 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
|
||||
database.DatabaseFunctions(self.database_path).modify_metadata(
|
||||
database_dict, book_hash)
|
||||
|
||||
def cancel_pressed(self, event):
|
||||
def cancel_pressed(self, event=None):
|
||||
self.hide()
|
||||
|
||||
def generate_display_position(self, mouse_cursor_position):
|
@@ -19,14 +19,14 @@
|
||||
import pathlib
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from resources import pie_chart
|
||||
from lector.resources import pie_chart
|
||||
|
||||
|
||||
class BookmarkProxyModel(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, parent=None):
|
||||
super(BookmarkProxyModel, self).__init__(parent)
|
||||
self.parent = parent
|
||||
self.filter_string = None
|
||||
self.filter_text = None
|
||||
|
||||
def setFilterParams(self, filter_text):
|
||||
self.filter_text = filter_text
|
||||
@@ -65,10 +65,20 @@ class ItemProxyModel(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)
|
||||
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 = [
|
||||
None, 'Title', 'Author', 'Year', '%', 'Tags']
|
||||
None, title_string, author_string,
|
||||
year_string, lastread_string, '%', tags_string]
|
||||
|
||||
self.temp_dir = temp_dir
|
||||
self.filter_text = None
|
||||
self.active_library_filters = None
|
||||
@@ -77,16 +87,21 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
||||
1: QtCore.Qt.UserRole, # Title
|
||||
2: QtCore.Qt.UserRole + 1, # Author
|
||||
3: QtCore.Qt.UserRole + 2, # Year
|
||||
4: QtCore.Qt.UserRole + 7, # Position percentage
|
||||
5: QtCore.Qt.UserRole + 4} # Tags
|
||||
4: QtCore.Qt.UserRole + 12, # Last read
|
||||
5: QtCore.Qt.UserRole + 7, # Position percentage
|
||||
6: QtCore.Qt.UserRole + 4} # Tags
|
||||
self.common_functions = ProxyModelsCommonFunctions(self)
|
||||
|
||||
def columnCount(self, parent):
|
||||
return 6
|
||||
return 7
|
||||
|
||||
def headerData(self, column, orientation, role):
|
||||
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):
|
||||
# Tag editing will take place by way of a right click menu
|
||||
@@ -97,11 +112,12 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
||||
source_index = self.mapToSource(index)
|
||||
item = self.sourceModel().item(source_index.row(), 0)
|
||||
|
||||
if role == QtCore.Qt.TextAlignmentRole and index.column() == 3:
|
||||
return QtCore.Qt.AlignHCenter
|
||||
if role == QtCore.Qt.TextAlignmentRole:
|
||||
if index.column() in (3, 4):
|
||||
return QtCore.Qt.AlignHCenter
|
||||
|
||||
if role == QtCore.Qt.DecorationRole:
|
||||
if index.column() == 4:
|
||||
if index.column() == 5:
|
||||
return_pixmap = None
|
||||
|
||||
file_exists = item.data(QtCore.Qt.UserRole + 5)
|
||||
@@ -136,11 +152,17 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
||||
return return_pixmap
|
||||
|
||||
elif role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
|
||||
if index.column() in (0, 4): # Cover and Status
|
||||
if index.column() in (0, 5): # Cover and Status
|
||||
return QtCore.QVariant()
|
||||
|
||||
return item.data(self.role_dictionary[index.column()])
|
||||
if index.column() == 4:
|
||||
last_accessed = item.data(self.role_dictionary[index.column()])
|
||||
if last_accessed:
|
||||
right_now = QtCore.QDateTime().currentDateTime()
|
||||
time_diff = last_accessed.msecsTo(right_now)
|
||||
return self.time_convert(time_diff // 1000)
|
||||
|
||||
return item.data(self.role_dictionary[index.column()])
|
||||
else:
|
||||
return QtCore.QVariant()
|
||||
|
||||
@@ -152,11 +174,28 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
|
||||
output = self.common_functions.filterAcceptsRow(row, parent)
|
||||
return output
|
||||
|
||||
def sort_table_columns(self, column):
|
||||
sorting_order = self.sender().sortIndicatorOrder()
|
||||
self.sort(0, sorting_order)
|
||||
self.setSortRole(self.role_dictionary[column])
|
||||
def sort_table_columns(self, column=None):
|
||||
column = self.tableViewHeader.sortIndicatorSection()
|
||||
sorting_order = self.tableViewHeader.sortIndicatorOrder()
|
||||
|
||||
self.sort(0, sorting_order)
|
||||
if column != 0:
|
||||
self.setSortRole(self.role_dictionary[column])
|
||||
|
||||
def time_convert(self, seconds):
|
||||
seconds = int(seconds)
|
||||
m, s = divmod(seconds, 60)
|
||||
h, m = divmod(m, 60)
|
||||
d, h = divmod(h, 24)
|
||||
|
||||
if d > 0:
|
||||
return f'{d}d'
|
||||
if h > 0:
|
||||
return f'{h}h'
|
||||
if m > 0:
|
||||
return f'{m}m'
|
||||
else:
|
||||
return '<1m'
|
||||
|
||||
class ProxyModelsCommonFunctions:
|
||||
def __init__(self, parent_model):
|
79
lector/parsers/comicbooks.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This file is a part of Lector, a Qt based ebook reader
|
||||
# Copyright (C) 2017-18 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
|
||||
from lector.rarfile import rarfile
|
||||
|
||||
|
||||
class ParseCOMIC:
|
||||
def __init__(self, filename, *args):
|
||||
self.filename = filename
|
||||
self.book = None
|
||||
self.image_list = None
|
||||
self.book_extension = os.path.splitext(self.filename)
|
||||
|
||||
def read_book(self):
|
||||
try:
|
||||
if self.book_extension[1] == '.cbz':
|
||||
self.book = zipfile.ZipFile(
|
||||
self.filename, mode='r', allowZip64=True)
|
||||
self.image_list = [i.filename for i in self.book.infolist() if not i.is_dir()]
|
||||
|
||||
elif self.book_extension[1] == '.cbr':
|
||||
self.book = rarfile.RarFile(self.filename)
|
||||
self.image_list = [i.filename for i in self.book.infolist() if not i.isdir()]
|
||||
|
||||
self.image_list.sort()
|
||||
except: # Specifying no exception here is warranted
|
||||
print('Cannot parse ' + self.filename)
|
||||
return
|
||||
|
||||
def get_title(self):
|
||||
title = os.path.basename(self.book_extension[0]).strip(' ')
|
||||
return title
|
||||
|
||||
def get_author(self):
|
||||
return None
|
||||
|
||||
def get_year(self):
|
||||
creation_time = time.ctime(os.path.getctime(self.filename))
|
||||
creation_year = creation_time.split()[-1]
|
||||
return creation_year
|
||||
|
||||
def get_cover_image(self):
|
||||
# The first image in the archive may not be the cover
|
||||
# It is implied, however, that the first image in order
|
||||
# will be the cover
|
||||
return self.book.read(self.image_list[0])
|
||||
|
||||
def get_isbn(self):
|
||||
return None
|
||||
|
||||
def get_tags(self):
|
||||
return None
|
||||
|
||||
def get_contents(self):
|
||||
file_settings = {'images_only': True}
|
||||
contents = [(f'Page {count + 1}', i) for count, i in enumerate(self.image_list)]
|
||||
|
||||
return contents, file_settings
|
@@ -19,7 +19,7 @@
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from ePub.read_epub import EPUB
|
||||
from lector.ePub.read_epub import EPUB
|
||||
|
||||
|
||||
class ParseEPUB:
|
||||
@@ -28,9 +28,8 @@ class ParseEPUB:
|
||||
# Maybe also include book description
|
||||
self.book_ref = None
|
||||
self.book = None
|
||||
self.temp_dir = temp_dir
|
||||
self.filename = filename
|
||||
self.file_md5 = file_md5
|
||||
self.extract_path = os.path.join(temp_dir, file_md5)
|
||||
|
||||
def read_book(self):
|
||||
self.book_ref = EPUB(self.filename)
|
||||
@@ -59,10 +58,9 @@ class ParseEPUB:
|
||||
return self.book['tags']
|
||||
|
||||
def get_contents(self):
|
||||
extract_path = os.path.join(self.temp_dir, self.file_md5)
|
||||
zipfile.ZipFile(self.filename).extractall(extract_path)
|
||||
zipfile.ZipFile(self.filename).extractall(self.extract_path)
|
||||
|
||||
self.book_ref.parse_chapters(temp_dir=self.temp_dir)
|
||||
self.book_ref.parse_chapters(temp_dir=self.extract_path)
|
||||
file_settings = {
|
||||
'images_only': False}
|
||||
return self.book['book_list'], file_settings
|
@@ -24,8 +24,8 @@ import sys
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
from ePub.read_epub import EPUB
|
||||
import KindleUnpack.kindleunpack as KindleUnpack
|
||||
from lector.ePub.read_epub import EPUB
|
||||
import lector.KindleUnpack.kindleunpack as KindleUnpack
|
||||
|
||||
|
||||
class ParseMOBI:
|
105
lector/parsers/pdf.py
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This file is a part of Lector, a Qt based ebook reader
|
||||
# Copyright (C) 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/>.
|
||||
|
||||
import io
|
||||
import os
|
||||
from PyQt5 import QtCore
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
proceed = True
|
||||
try:
|
||||
import popplerqt5
|
||||
except ImportError:
|
||||
print('python-poppler-qt5 is not installed. Pdf files will not work.')
|
||||
proceed = False
|
||||
|
||||
class ParsePDF:
|
||||
def __init__(self, filename, *args):
|
||||
self.filename = filename
|
||||
self.book = None
|
||||
self.metadata = None
|
||||
|
||||
def read_book(self):
|
||||
if not proceed:
|
||||
return
|
||||
|
||||
self.book = popplerqt5.Poppler.Document.load(self.filename)
|
||||
if not self.book:
|
||||
return
|
||||
|
||||
self.metadata = BeautifulSoup(self.book.metadata(), 'xml')
|
||||
|
||||
def get_title(self):
|
||||
try:
|
||||
title = self.metadata.find('title').text
|
||||
return title.replace('\n', '')
|
||||
except AttributeError:
|
||||
return os.path.splitext(os.path.basename(self.filename))[0]
|
||||
|
||||
def get_author(self):
|
||||
try:
|
||||
author = self.metadata.find('creator').text
|
||||
return author.replace('\n', '')
|
||||
except AttributeError:
|
||||
return 'Unknown'
|
||||
|
||||
def get_year(self):
|
||||
try:
|
||||
year = self.metadata.find('MetadataDate').text
|
||||
return year.replace('\n', '')
|
||||
except AttributeError:
|
||||
return 9999
|
||||
|
||||
def get_cover_image(self):
|
||||
self.book.setRenderHint(
|
||||
popplerqt5.Poppler.Document.Antialiasing
|
||||
and popplerqt5.Poppler.Document.TextAntialiasing)
|
||||
|
||||
cover_page = self.book.page(0)
|
||||
cover_image = cover_page.renderToImage(300, 300)
|
||||
return resize_image(cover_image)
|
||||
|
||||
def get_isbn(self):
|
||||
return None
|
||||
|
||||
def get_tags(self):
|
||||
try:
|
||||
tags = self.metadata.find('Keywords').text
|
||||
return tags.replace('\n', '')
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def get_contents(self):
|
||||
file_settings = {'images_only': True}
|
||||
contents = [(f'Page {i + 1}', i) for i in range(self.book.numPages())]
|
||||
|
||||
return contents, file_settings
|
||||
|
||||
|
||||
def resize_image(cover_image):
|
||||
cover_image = cover_image.scaled(
|
||||
420, 600, QtCore.Qt.IgnoreAspectRatio)
|
||||
|
||||
byte_array = QtCore.QByteArray()
|
||||
buffer = QtCore.QBuffer(byte_array)
|
||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||
cover_image.save(buffer, 'jpg', 75)
|
||||
|
||||
cover_image_final = io.BytesIO(byte_array)
|
||||
cover_image_final.seek(0)
|
||||
return cover_image_final.getvalue()
|
0
lector/resources/__init__.py
Normal file
8
lector/resources/raw/DarkIcons/add.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 7 3 L 7 7 L 3 7 L 3 9 L 7 9 L 7 13 L 9 13 L 9 9 L 13 9 L 13 7 L 9 7 L 9 3 L 7 3 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 428 B |
8
lector/resources/raw/DarkIcons/bookmark-new.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 4 0.00390625 C 4 0.00390625 3 0.00390625 3 1.0039062 L 3 15.003906 L 8 12.003906 L 13 15.003906 L 13 1.0039062 C 13 1.0039062 13 0.00390625 12 0.00390625 L 4 0.00390625 z M 7 3.0039062 L 9 3.0039062 L 9 5.0039062 L 11 5.0039062 L 11 7.0039062 L 9 7.0039062 L 9 9.0039062 L 7 9.0039062 L 7 7.0039062 L 5 7.0039062 L 5 5.0039062 L 7 5.0039062 L 7 3.0039062 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 703 B |
8
lector/resources/raw/DarkIcons/bookmarks.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 8 0.390625 L 5.8808594 5.8847656 L 0 6.2011719 L 4.5722656 9.9160156 L 3.0566406 15.607422 L 8 12.40625 L 12.943359 15.607422 L 11.427734 9.9160156 L 16 6.2011719 L 10.119141 5.8847656 L 8 0.390625 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 546 B |
8
lector/resources/raw/DarkIcons/color-picker.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 8 1 C 7.79297 1.66364 7.5132275 2.3110656 7.2109375 2.9472656 C 5.6704375 6.0974656 3.2599437 8.2540875 3.0273438 10.242188 C 3.0213438 10.271888 3.0052 10.304384 3 10.333984 L 3.0195312 10.339844 C 3.0145313 10.408244 3 10.476722 3 10.544922 C 3 13.005122 5.2386 15 8 15 C 10.7614 15 13 13.005122 13 10.544922 C 13 10.476722 12.985469 10.408214 12.980469 10.339844 L 13 10.333984 C 12.995 10.304484 12.978956 10.271887 12.972656 10.242188 C 12.740106 8.2539875 10.329662 6.0973656 8.7890625 2.9472656 C 8.4867825 2.3110456 8.20702 1.6636 8 1 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 891 B |
8
lector/resources/raw/DarkIcons/edit-rename.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 12.210938 1 C 11.998438 1 11.784141 1.0830469 11.619141 1.2480469 L 9.9902344 2.8886719 L 13.109375 6.0078125 L 14.75 4.3789062 C 15.08 4.0489063 15.08 3.5272656 14.75 3.1972656 L 12.800781 1.2480469 C 12.635781 1.0830469 12.423437 1 12.210938 1 z M 8.8691406 4.0078125 L 0.99023438 11.888672 L 0.99023438 15.007812 L 4.109375 15.007812 L 11.990234 7.1289062 L 8.8691406 4.0078125 z" transform="translate(4 4)"/>
|
||||
</svg>
|
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 |
8
lector/resources/raw/DarkIcons/format-indent-less.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 1 5 L 1 7 L 9 7.0039062 L 9 5.0039062 L 1 5 z M 15 5.0039062 L 10 8.0039062 L 15 11.003906 L 15 5.0039062 z M 1 9 L 1 11 L 9 11 L 9 9 L 1 9 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 561 B |
8
lector/resources/raw/DarkIcons/format-indent-more.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 1 5.0039062 L 1 11.003906 L 6 8.0039062 L 1 5.0039062 z M 7 5.0039062 L 7 7.0039062 L 15 7.0039062 L 15 5.0039062 L 7 5.0039062 z M 15 9 L 7 9.0039062 L 7 11.003906 L 15 11 L 15 9 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 601 B |
8
lector/resources/raw/DarkIcons/format-justify-center.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 4 5.0039062 L 4 7.0039062 L 12 7.0039062 L 12 5.0039062 L 4 5.0039062 z M 4 9.0039062 L 4 11.003906 L 12 11.003906 L 12 9.0039062 L 4 9.0039062 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 565 B |
8
lector/resources/raw/DarkIcons/format-justify-fill.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 1 5.0039062 L 1 7.0039062 L 15 7.0039062 L 15 5.0039062 L 1 5.0039062 z M 1 9.0039062 L 1 11.003906 L 15 11.003906 L 15 9.0039062 L 1 9.0039062 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 565 B |
8
lector/resources/raw/DarkIcons/format-justify-left.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 1 5.0039062 L 1 7.0039062 L 9 7.0039062 L 9 5.0039062 L 1 5.0039062 z M 1 9.0039062 L 1 11.003906 L 9 11.003906 L 9 9.0039062 L 1 9.0039062 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 561 B |
8
lector/resources/raw/DarkIcons/format-justify-right.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 7 5.0039062 L 7 7.0039062 L 15 7.0039062 L 15 5.0039062 L 7 5.0039062 z M 7 9.0039062 L 7 11.003906 L 15 11.003906 L 15 9.0039062 L 7 9.0039062 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 565 B |
@@ -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 3 L 5 4 L 2 4 L 2 12 L 5 12 L 5 13 L 14 13 L 14 10 L 5 10 L 5 11 L 3 11 L 3 9 L 4 9 L 4 7 L 3 7 L 3 5 L 5 5 L 5 6 L 14 6 L 14 3 L 5 3 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 484 B |
@@ -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 2 L 5 3 L 2 3 L 2 13 L 5 13 L 5 14 L 14 14 L 14 11 L 5 11 L 5 12 L 3 12 L 3 9 L 4 9 L 4 7 L 3 7 L 3 4 L 5 4 L 5 5 L 14 5 L 14 2 L 5 2 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 484 B |
8
lector/resources/raw/DarkIcons/gtk-select-font.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 7 1 L 2 15 L 4.5 15 L 5.5625 12 L 10.4375 12 L 11.5 15 L 14.28125 15 L 9 1 L 7 1 z M 14 1 A 1 1 0 0 0 13 2 A 1 1 0 0 0 14 3 A 1 1 0 0 0 15 2 A 1 1 0 0 0 14 1 z M 14 4 A 1 1 0 0 0 13 5 A 1 1 0 0 0 14 6 A 1 1 0 0 0 15 5 A 1 1 0 0 0 14 4 z M 8 5 L 9.75 10 L 6.25 10 L 8 5 z M 14 7 A 1 1 0 0 0 13 8 A 1 1 0 0 0 14 9 A 1 1 0 0 0 15 8 A 1 1 0 0 0 14 7 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 694 B |
8
lector/resources/raw/DarkIcons/mail-thread-watch.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="m4.5 3a3.5 5 0 0 0 -3.5 5 3.5 5 0 0 0 3.5 5 3.5 5 0 0 0 3.5 -5 3.5 5 0 0 0 -3.5 -5zm3.5 5a3.5 5 0 0 0 3.5 5 3.5 5 0 0 0 3.5 -5 3.5 5 0 0 0 -3.5 -5 3.5 5 0 0 0 -3.5 5zm-3 0a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2zm7 0a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 642 B |
8
lector/resources/raw/DarkIcons/reload.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 8 1.015625 C 4.134 1.015625 1 4.149625 1 8.015625 C 1 11.881625 4.134 15.015625 8 15.015625 C 11.1748 15.015625 13.86145 12.912425 14.71875 10.015625 L 12.5625 10.015625 C 11.78823 11.775125 10.0457 13.015625 8 13.015625 C 5.2386 13.015625 3 10.777025 3 8.015625 C 3 5.254225 5.2386 3.015625 8 3.015625 C 9.3816 3.015625 10.615525 3.59065 11.515625 4.5 L 9.0058594 7.015625 L 15.005859 7.015625 L 15.005859 1.015625 L 12.953125 3.0683594 C 11.683125 1.8033594 9.9339063 1.015625 8.0039062 1.015625 L 8 1.015625 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 859 B |
8
lector/resources/raw/DarkIcons/remove.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 3 7 L 3 9 L 13 9 L 13 7 L 3 7 z" transform="translate(4 4)"/>
|
||||
</svg>
|
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 |
8
lector/resources/raw/DarkIcons/settings.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 6.25 1 L 6.0957031 2.84375 A 5.5 5.5 0 0 0 4.4882812 3.7734375 L 2.8125 2.984375 L 1.0625 6.015625 L 2.5839844 7.0722656 A 5.5 5.5 0 0 0 2.5 8 A 5.5 5.5 0 0 0 2.5800781 8.9316406 L 1.0625 9.984375 L 2.8125 13.015625 L 4.484375 12.228516 A 5.5 5.5 0 0 0 6.0957031 13.152344 L 6.2460938 15.001953 L 9.7460938 15.001953 L 9.9003906 13.158203 A 5.5 5.5 0 0 0 11.507812 12.228516 L 13.183594 13.017578 L 14.933594 9.9863281 L 13.412109 8.9296875 A 5.5 5.5 0 0 0 13.496094 8.0019531 A 5.5 5.5 0 0 0 13.416016 7.0703125 L 14.933594 6.0175781 L 13.183594 2.9863281 L 11.511719 3.7734375 A 5.5 5.5 0 0 0 9.9003906 2.8496094 L 9.75 1 L 6.25 1 z M 8 6 A 2 2 0 0 1 10 8 A 2 2 0 0 1 8 10 A 2 2 0 0 1 6 8 A 2 2 0 0 1 8 6 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
8
lector/resources/raw/DarkIcons/table.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="m1 1v14h14v-14h-14zm2 2h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm-8 4h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm-8 4h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2z" transform="translate(4 4)"/>
|
||||
</svg>
|
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 |
8
lector/resources/raw/DarkIcons/trash-empty.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 6 0.9921875 C 5 0.9921875 5 1.9921875 5 1.9921875 L 2 1.9921875 C 2 1.9921875 1 1.9956938 1 2.9960938 L 1 3.9960938 L 14 3.9921875 L 14 2.9960938 C 14 1.9960938 13 1.9921875 13 1.9921875 L 10 1.9921875 C 10 1.9921875 10 0.9921875 9 0.9921875 L 6 0.9921875 z M 2 4.9960938 L 2 13.996094 C 2.00005 14.519674 2.47642 14.996044 3 14.996094 L 12 14.996094 C 12.52358 14.996044 12.99995 14.519674 13 13.996094 L 13 4.9960938 L 2 4.9960938 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 781 B |
8
lector/resources/raw/DarkIcons/view-fullscreen.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 2 2.0039062 C 1 2.0039062 1 3.0039062 1 3.0039062 L 1 7.0039062 L 3 7.0039062 L 3 4.0039062 L 6 4.0039062 L 6 2.0039062 L 2 2.0039062 z M 10 2.0039062 L 10 4.0039062 L 13 4.0039062 L 13 7.0039062 L 15 7.0039062 L 15 3.0039062 C 15 2.0039062 14 2.0039062 14 2.0039062 L 10 2.0039062 z M 1 9.0039062 L 1 13.003906 C 1 14.003906 2 14.003906 2 14.003906 L 6 14.003906 L 6 12.003906 L 3 12.003906 L 3 9.0039062 L 1 9.0039062 z M 13 9.0039062 L 13 12.003906 L 10 12.003906 L 10 14.003906 L 14 14.003906 C 14 14.003906 15 14.003906 15 13.003906 L 15 9.0039062 L 13 9.0039062 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 916 B |
8
lector/resources/raw/DarkIcons/view-grid.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 1.25 3.0039062 C 1.1115 3.0039063 1 3.1154062 1 3.2539062 L 1 4.7539062 C 1 4.8924062 1.1115 5.0039062 1.25 5.0039062 L 2.75 5.0039062 C 2.8885 5.0039062 3 4.8924062 3 4.7539062 L 3 3.2539062 C 3 3.1154062 2.8885 3.0039062 2.75 3.0039062 L 1.25 3.0039062 z M 5.25 3.0039062 C 5.1115 3.0039063 5 3.1154062 5 3.2539062 L 5 4.7539062 C 5 4.8924062 5.1115 5.0039062 5.25 5.0039062 L 6.75 5.0039062 C 6.8885 5.0039062 7 4.8924062 7 4.7539062 L 7 3.2539062 C 7 3.1154062 6.8885 3.0039062 6.75 3.0039062 L 5.25 3.0039062 z M 9.25 3.0039062 C 9.1115 3.0039063 9 3.1154062 9 3.2539062 L 9 4.7539062 C 9 4.8924062 9.1115 5.0039062 9.25 5.0039062 L 10.75 5.0039062 C 10.8885 5.0039062 11 4.8924062 11 4.7539062 L 11 3.2539062 C 11 3.1154062 10.8885 3.0039062 10.75 3.0039062 L 9.25 3.0039062 z M 13.25 3.0039062 C 13.1115 3.0039063 13 3.1154062 13 3.2539062 L 13 4.7539062 C 13 4.8924062 13.1115 5.0039062 13.25 5.0039062 L 14.75 5.0039062 C 14.8885 5.0039062 15 4.8924062 15 4.7539062 L 15 3.2539062 C 15 3.1154062 14.8885 3.0039062 14.75 3.0039062 L 13.25 3.0039062 z M 1.25 7.0039062 C 1.1115 7.0039063 1 7.1154063 1 7.2539062 L 1 8.7539062 C 1 8.8924063 1.1115 9.0039062 1.25 9.0039062 L 2.75 9.0039062 C 2.8885 9.0039062 3 8.8924063 3 8.7539062 L 3 7.2539062 C 3 7.1154063 2.8885 7.0039062 2.75 7.0039062 L 1.25 7.0039062 z M 5.25 7.0039062 C 5.1115 7.0039063 5 7.1154063 5 7.2539062 L 5 8.7539062 C 5 8.8924063 5.1115 9.0039062 5.25 9.0039062 L 6.75 9.0039062 C 6.8885 9.0039062 7 8.8924063 7 8.7539062 L 7 7.2539062 C 7 7.1154063 6.8885 7.0039062 6.75 7.0039062 L 5.25 7.0039062 z M 9.25 7.0039062 C 9.1115 7.0039063 9 7.1154063 9 7.2539062 L 9 8.7539062 C 9 8.8924063 9.1115 9.0039062 9.25 9.0039062 L 10.75 9.0039062 C 10.8885 9.0039062 11 8.8924063 11 8.7539062 L 11 7.2539062 C 11 7.1154063 10.8885 7.0039062 10.75 7.0039062 L 9.25 7.0039062 z M 13.25 7.0039062 C 13.1115 7.0039063 13 7.1154063 13 7.2539062 L 13 8.7539062 C 13 8.8924063 13.1115 9.0039062 13.25 9.0039062 L 14.75 9.0039062 C 14.8885 9.0039062 15 8.8924063 15 8.7539062 L 15 7.2539062 C 15 7.1154063 14.8885 7.0039062 14.75 7.0039062 L 13.25 7.0039062 z M 1.25 11.003906 C 1.1115 11.003906 1 11.115406 1 11.253906 L 1 12.753906 C 1 12.892406 1.1115 13.003906 1.25 13.003906 L 2.75 13.003906 C 2.8885 13.003906 3 12.892406 3 12.753906 L 3 11.253906 C 3 11.115406 2.8885 11.003906 2.75 11.003906 L 1.25 11.003906 z M 5.25 11.003906 C 5.1115 11.003906 5 11.115406 5 11.253906 L 5 12.753906 C 5 12.892406 5.1115 13.003906 5.25 13.003906 L 6.75 13.003906 C 6.8885 13.003906 7 12.892406 7 12.753906 L 7 11.253906 C 7 11.115406 6.8885 11.003906 6.75 11.003906 L 5.25 11.003906 z M 9.25 11.003906 C 9.1115 11.003906 9 11.115406 9 11.253906 L 9 12.753906 C 9 12.892406 9.1115 13.003906 9.25 13.003906 L 10.75 13.003906 C 10.8885 13.003906 11 12.892406 11 12.753906 L 11 11.253906 C 11 11.115406 10.8885 11.003906 10.75 11.003906 L 9.25 11.003906 z M 13.25 11.003906 C 13.1115 11.003906 13 11.115406 13 11.253906 L 13 12.753906 C 13 12.892406 13.1115 13.003906 13.25 13.003906 L 14.75 13.003906 C 14.8885 13.003906 15 12.892406 15 12.753906 L 15 11.253906 C 15 11.115406 14.8885 11.003906 14.75 11.003906 L 13.25 11.003906 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
8
lector/resources/raw/DarkIcons/view-readermode.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 10.029297 1 C 9.8819169 1.003 9.7293425 1.0150094 9.5703125 1.0371094 C 9.0034025 1.1159094 8.3186875 1.3914781 7.5546875 1.7988281 C 6.0140875 0.94567812 4.8137406 0.89374688 3.8066406 1.1542969 C 2.7378406 1.4308069 1.868925 1.8823869 0.515625 1.8417969 L 0 1.8261719 L 0 16 L 15 16 L 15 1.84375 L 14.482422 1.8613281 C 12.965822 1.9165281 12.211922 1.464645 11.232422 1.171875 C 10.865122 1.062075 10.471417 0.99098 10.029297 1 z M 5.21875 1.9941406 C 5.71774 2.0327406 6.2822 2.213495 7 2.609375 L 7 11.333984 C 5.8956 10.692224 4.7902063 10.643969 3.8164062 10.886719 C 3.1632062 11.049539 2.5692 11.237652 2 11.388672 L 2 2.6621094 C 2.8021 2.5141794 3.4740875 2.2830225 4.0546875 2.1328125 C 4.2857975 2.0730125 4.5091312 2.02398 4.7382812 2 C 4.8933012 1.9838 5.05242 1.9813406 5.21875 1.9941406 z M 10.337891 2.0019531 C 10.543791 2.0284531 10.742569 2.079065 10.949219 2.140625 C 11.483649 2.300375 12.1426 2.5531719 13 2.7011719 L 13 11.384766 C 12.43016 11.232366 11.837453 11.042719 11.189453 10.880859 C 10.209613 10.636099 9.0981 10.696078 8 11.367188 L 8 2.6679688 C 8.70328 2.2824487 9.2453875 2.0716725 9.6796875 2.0078125 C 9.9189475 1.9726125 10.131991 1.9756531 10.337891 2.0019531 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
8
lector/resources/raw/DarkIcons/visibility.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 8 3.0039062 C 6.4492 3.0190063 4.8879094 3.3732319 3.5371094 4.1386719 C 2.9987094 4.4892919 2.3523344 4.9421175 1.8652344 5.3984375 C 1.0987444 6.1488575 0.4427 7.0244062 0 8.0039062 C 1.2149 10.683506 3.8859187 12.6474 6.8242188 12.9375 C 8.7516188 13.15561 10.768591 12.822631 12.462891 11.869141 C 13.001291 11.518521 13.647666 11.065695 14.134766 10.609375 C 14.901256 9.858955 15.5573 8.9834063 16 8.0039062 C 14.785 5.3245062 12.114181 3.3601125 9.1757812 3.0703125 C 8.7859013 3.0248425 8.39251 3.0038963 8 3.0039062 z M 8 5.0019531 L 8 5.0039062 C 9.607 4.9683062 11.0303 6.4057062 11 8.0039062 C 11.0515 9.7703063 9.2909813 11.294844 7.5507812 10.964844 C 5.7931812 10.758504 4.5587188 8.7851344 5.1367188 7.1152344 C 5.5058788 5.8858344 6.7125 4.9866531 8 5.0019531 z M 8 7.0039062 A 1 1 0 0 0 7 8.0039062 A 1 1 0 0 0 8 9.0039062 A 1 1 0 0 0 9 8.0039062 A 1 1 0 0 0 8 7.0039062 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
8
lector/resources/raw/DarkIcons/zoom-fit-best.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 3.1894531 2.0039062 C 2.5267531 2.0039062 2.0019531 2.5527963 2.0019531 3.2226562 L 2.0019531 7.0039062 L 4.0019531 7.0039062 L 4.0019531 4.0039062 L 7.0019531 4.0039062 L 7.0019531 2.0039062 L 3.1894531 2.0039062 z M 9.0019531 2.0039062 L 9.0019531 4.0039062 L 12.001953 4.0039062 L 12.001953 7.0039062 L 14.001953 7.0039062 L 14.001953 3.2226562 C 14.001953 2.5528963 13.477153 2.0039062 12.814453 2.0039062 L 9.0019531 2.0039062 z M 2.0019531 9.0039062 L 2.0019531 12.785156 C 2.0019531 13.454916 2.5267531 14.003906 3.1894531 14.003906 L 7.0019531 14.003906 L 7.0019531 12.003906 L 4.0019531 12.003906 L 4.0019531 9.0039062 L 2.0019531 9.0039062 z M 12.001953 9.0039062 L 12.001953 12.003906 L 9.0019531 12.003906 L 9.0019531 14.003906 L 12.814453 14.003906 C 13.477153 14.003906 14.001953 13.455016 14.001953 12.785156 L 14.001953 9.0039062 L 12.001953 9.0039062 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
8
lector/resources/raw/DarkIcons/zoom-fit-width.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 3.1875 2 C 2.5248 2 2 2.54895 2 3.21875 L 2 7 L 4 7 L 4 4 L 7 4 L 7 2 L 3.1875 2 z M 9 2 L 9 4 L 12 4 L 12 7 L 14 7 L 14 3.21875 C 14 2.54885 13.4755 2 12.8125 2 L 9 2 z M 7 5 L 7 11 L 9 11 L 9 5 L 7 5 z M 6 6 L 4 8 L 6 10 L 6 6 z M 10 6 L 10 10 L 12 8 L 10 6 z M 2 9 L 2 12.78125 C 2 13.45125 2.5248 14 3.1875 14 L 7 14 L 7 12 L 4 12 L 4 9 L 2 9 z M 12 9 L 12 12 L 9 12 L 9 14 L 12.8125 14 C 13.4755 14 14 13.45125 14 12.78125 L 14 9 L 12 9 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 790 B |
8
lector/resources/raw/DarkIcons/zoom-in.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 3 2 C 2.446 2 2 2.446 2 3 L 2 13 C 2 13.554 2.446 14 3 14 L 13 14 C 13.554 14 14 13.554 14 13 L 14 3 C 14 2.446 13.554 2 13 2 L 3 2 z M 7 5 L 9 5 L 9 7 L 11 7 L 11 9 L 9 9 L 9 11 L 7 11 L 7 9 L 5 9 L 5 7 L 7 7 L 7 5 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 564 B |
8
lector/resources/raw/DarkIcons/zoom-original.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 3 2 C 2.446 2 2 2.446 2 3 L 2 13 C 2 13.554 2.446 14 3 14 L 13 14 C 13.554 14 14 13.554 14 13 L 14 3 C 14 2.446 13.554 2 13 2 L 3 2 z M 7 5 L 9 5 L 9 11 L 7 11 L 7 7 L 6 7 L 6 6 C 6 6 7 6 7 5 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 540 B |
8
lector/resources/raw/DarkIcons/zoom-out.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 3 2 C 2.446 2 2 2.446 2 3 L 2 13 C 2 13.554 2.446 14 3 14 L 13 14 C 13.554 14 14 13.554 14 13 L 14 3 C 14 2.446 13.554 2 13 2 L 3 2 z M 5 7 L 11 7 L 11 9 L 5 9 L 5 7 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 514 B |
BIN
lector/resources/raw/Lector.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
8
lector/resources/raw/LightIcons/add.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 7 3 L 7 7 L 3 7 L 3 9 L 7 9 L 7 13 L 9 13 L 9 9 L 13 9 L 13 7 L 9 7 L 9 3 L 7 3 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 428 B |
8
lector/resources/raw/LightIcons/bookmark-new.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:#d3dae3; } .ColorScheme-Highlight { color:#5294e2; }
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor" class="ColorScheme-Text" d="M 4 0.00390625 C 4 0.00390625 3 0.00390625 3 1.0039062 L 3 15.003906 L 8 12.003906 L 13 15.003906 L 13 1.0039062 C 13 1.0039062 13 0.00390625 12 0.00390625 L 4 0.00390625 z M 7 3.0039062 L 9 3.0039062 L 9 5.0039062 L 11 5.0039062 L 11 7.0039062 L 9 7.0039062 L 9 9.0039062 L 7 9.0039062 L 7 7.0039062 L 5 7.0039062 L 5 5.0039062 L 7 5.0039062 L 7 3.0039062 z" transform="translate(3 3)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 703 B |
8
lector/resources/raw/LightIcons/bookmarks.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 8 0.390625 L 5.8808594 5.8847656 L 0 6.2011719 L 4.5722656 9.9160156 L 3.0566406 15.607422 L 8 12.40625 L 12.943359 15.607422 L 11.427734 9.9160156 L 16 6.2011719 L 10.119141 5.8847656 L 8 0.390625 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 546 B |
8
lector/resources/raw/LightIcons/color-picker.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 8 1 C 7.79297 1.66364 7.5132275 2.3110656 7.2109375 2.9472656 C 5.6704375 6.0974656 3.2599437 8.2540875 3.0273438 10.242188 C 3.0213438 10.271888 3.0052 10.304384 3 10.333984 L 3.0195312 10.339844 C 3.0145313 10.408244 3 10.476722 3 10.544922 C 3 13.005122 5.2386 15 8 15 C 10.7614 15 13 13.005122 13 10.544922 C 13 10.476722 12.985469 10.408214 12.980469 10.339844 L 13 10.333984 C 12.995 10.304484 12.978956 10.271887 12.972656 10.242188 C 12.740106 8.2539875 10.329662 6.0973656 8.7890625 2.9472656 C 8.4867825 2.3110456 8.20702 1.6636 8 1 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 891 B |
8
lector/resources/raw/LightIcons/edit-rename.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 12.210938 1 C 11.998438 1 11.784141 1.0830469 11.619141 1.2480469 L 9.9902344 2.8886719 L 13.109375 6.0078125 L 14.75 4.3789062 C 15.08 4.0489063 15.08 3.5272656 14.75 3.1972656 L 12.800781 1.2480469 C 12.635781 1.0830469 12.423437 1 12.210938 1 z M 8.8691406 4.0078125 L 0.99023438 11.888672 L 0.99023438 15.007812 L 4.109375 15.007812 L 11.990234 7.1289062 L 8.8691406 4.0078125 z" transform="translate(4 4)"/>
|
||||
</svg>
|
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 |
8
lector/resources/raw/LightIcons/format-indent-less.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 1 5 L 1 7 L 9 7.0039062 L 9 5.0039062 L 1 5 z M 15 5.0039062 L 10 8.0039062 L 15 11.003906 L 15 5.0039062 z M 1 9 L 1 11 L 9 11 L 9 9 L 1 9 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 561 B |
8
lector/resources/raw/LightIcons/format-indent-more.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 1 5.0039062 L 1 11.003906 L 6 8.0039062 L 1 5.0039062 z M 7 5.0039062 L 7 7.0039062 L 15 7.0039062 L 15 5.0039062 L 7 5.0039062 z M 15 9 L 7 9.0039062 L 7 11.003906 L 15 11 L 15 9 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 601 B |
@@ -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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 4 5.0039062 L 4 7.0039062 L 12 7.0039062 L 12 5.0039062 L 4 5.0039062 z M 4 9.0039062 L 4 11.003906 L 12 11.003906 L 12 9.0039062 L 4 9.0039062 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 565 B |
8
lector/resources/raw/LightIcons/format-justify-fill.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 1 5.0039062 L 1 7.0039062 L 15 7.0039062 L 15 5.0039062 L 1 5.0039062 z M 1 9.0039062 L 1 11.003906 L 15 11.003906 L 15 9.0039062 L 1 9.0039062 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 565 B |
8
lector/resources/raw/LightIcons/format-justify-left.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 1 5.0039062 L 1 7.0039062 L 9 7.0039062 L 9 5.0039062 L 1 5.0039062 z M 1 9.0039062 L 1 11.003906 L 9 11.003906 L 9 9.0039062 L 1 9.0039062 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 561 B |
8
lector/resources/raw/LightIcons/format-justify-right.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 1 1 L 1 3 L 15 3 L 15 1 L 1 1 z M 7 5.0039062 L 7 7.0039062 L 15 7.0039062 L 15 5.0039062 L 7 5.0039062 z M 7 9.0039062 L 7 11.003906 L 15 11.003906 L 15 9.0039062 L 7 9.0039062 z M 1 13 L 1 15 L 15 15 L 15 13 L 1 13 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 565 B |
@@ -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 3 L 5 4 L 2 4 L 2 12 L 5 12 L 5 13 L 14 13 L 14 10 L 5 10 L 5 11 L 3 11 L 3 9 L 4 9 L 4 7 L 3 7 L 3 5 L 5 5 L 5 6 L 14 6 L 14 3 L 5 3 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 484 B |
@@ -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 2 L 5 3 L 2 3 L 2 13 L 5 13 L 5 14 L 14 14 L 14 11 L 5 11 L 5 12 L 3 12 L 3 9 L 4 9 L 4 7 L 3 7 L 3 4 L 5 4 L 5 5 L 14 5 L 14 2 L 5 2 z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 484 B |
8
lector/resources/raw/LightIcons/gtk-select-font.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:#d3dae3; } .ColorScheme-Highlight { color:#5294e2; }
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor" class="ColorScheme-Text" d="M 7 1 L 2 15 L 4.5 15 L 5.5625 12 L 10.4375 12 L 11.5 15 L 14.28125 15 L 9 1 L 7 1 z M 14 1 A 1 1 0 0 0 13 2 A 1 1 0 0 0 14 3 A 1 1 0 0 0 15 2 A 1 1 0 0 0 14 1 z M 14 4 A 1 1 0 0 0 13 5 A 1 1 0 0 0 14 6 A 1 1 0 0 0 15 5 A 1 1 0 0 0 14 4 z M 8 5 L 9.75 10 L 6.25 10 L 8 5 z M 14 7 A 1 1 0 0 0 13 8 A 1 1 0 0 0 14 9 A 1 1 0 0 0 15 8 A 1 1 0 0 0 14 7 z" transform="translate(3 3)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 694 B |
8
lector/resources/raw/LightIcons/mail-thread-watch.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="m4.5 3a3.5 5 0 0 0 -3.5 5 3.5 5 0 0 0 3.5 5 3.5 5 0 0 0 3.5 -5 3.5 5 0 0 0 -3.5 -5zm3.5 5a3.5 5 0 0 0 3.5 5 3.5 5 0 0 0 3.5 -5 3.5 5 0 0 0 -3.5 -5 3.5 5 0 0 0 -3.5 5zm-3 0a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2zm7 0a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2z" transform="translate(4 4)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 642 B |