30 Commits
0.2 ... 0.3.1

Author SHA1 Message Date
BasioMeusPuga
34dcf9f1b4 Fix empty database triggering error at first start 2018-03-23 15:15:42 +05:30
BasioMeusPuga
7931f92335 Application icon and .desktop file
Rearrange modules because of single-version-externally-managed
2018-03-23 00:58:42 +05:30
BasioMeusPuga
42b655862c Partially fix tab close memory leak 2018-03-22 19:06:16 +05:30
BasioMeusPuga
e6eb056ec6 Make context menus more coherent
Update translations
2018-03-22 14:51:33 +05:30
BasioMeusPuga
7f5b6fc349 Multiple UI improvements 2018-03-21 21:19:28 +05:30
BasioMeusPuga
9af175b11f Merge pull request #28 from szymonpk/gentoo-ebuild
Add link to unofficial Gentoo ebuild
2018-03-21 15:31:50 +05:30
BasioMeusPuga
c783e44444 Adjust widgets to screen size
Delete key for the library
2018-03-21 15:28:58 +05:30
BasioMeusPuga
a1dba753e8 Manually added books no longer removed on library refresh
Overhaul database module
2018-03-21 15:04:28 +05:30
Szymon Szypulski
a55a0e7205 Add link to unofficial Gentoo ebuild 2018-03-21 07:31:15 +01:00
BasioMeusPuga
bb8de60efe Fix books in subdirectories getting filtered 2018-03-20 20:36:24 +05:30
BasioMeusPuga
0d8c2b6648 Multiple fixes
Update translations
2018-03-20 13:24:17 +05:30
BasioMeusPuga
64a96d816d Update translations: German 2018-03-20 08:26:35 +05:30
BasioMeusPuga
5a4af54118 Merge pull request #25 from atmaxinger/master
German translation
2018-03-20 08:19:01 +05:30
atmaxinger
4cf18e008d German translation 2018-03-20 00:10:05 +01:00
BasioMeusPuga
50cc52b116 Update README.md 2018-03-20 00:04:05 +05:30
BasioMeusPuga
35f38b9f68 French translation 2018-03-19 23:57:47 +05:30
BasioMeusPuga
c883ba0175 Merge pull request #24 from eclipseo/add_French_translation
Great work! 
Give me a minute to update the binary files.
2018-03-19 23:52:50 +05:30
BasioMeusPuga
39cf03a70e Switch context menu TOC to combobox
Update translations
2018-03-19 23:40:16 +05:30
Robert-André Mauchin
44d88d99bb Add French translation
Signed-off-by: Robert-André Mauchin <zebob.m@gmail.com>
2018-03-19 18:36:10 +01:00
BasioMeusPuga
ca67071e91 Add TOC to context menu in distraction free mode 2018-03-19 19:28:33 +05:30
BasioMeusPuga
b5acce6449 Small fixes 2018-03-19 18:26:43 +05:30
BasioMeusPuga
7bdf01a67e Spanish translation 2018-03-19 17:48:25 +05:30
BasioMeusPuga
aca08827fb Implement save as for comic/pdf view
Account for malformed container.xml for epubs
2018-03-19 01:11:55 +05:30
BasioMeusPuga
d4aaa4dc74 Update README.md 2018-03-19 00:43:18 +05:30
BasioMeusPuga
98daa40bfd Implement internationalization support 2018-03-19 00:11:06 +05:30
BasioMeusPuga
a7df896468 Mark translatable strings 2018-03-18 22:19:19 +05:30
BasioMeusPuga
fd149dcafa Usability improvements
Keyboard shortcuts
Title reporting
Context menu for comic/pdf view
2018-03-18 01:19:04 +05:30
BasioMeusPuga
0bb2e9329f Fix fullscreened widget not finding main window 2018-03-17 12:56:23 +05:30
BasioMeusPuga
89a32bfeda Add toggle for image caching
Remove PyQt5 reference from setup.py
2018-03-17 10:44:02 +05:30
BasioMeusPuga
50089cb57a Remove version requirements 2018-03-17 00:38:09 +05:30
145 changed files with 8600 additions and 2677 deletions

33
Lector.pro Normal file
View 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

View File

@@ -35,12 +35,16 @@ poppler-qt5 and python-poppler-qt5 are optional.
### Available packages
* [AUR](https://aur.archlinux.org/packages/lector-git/)
* [Gentoo (unofficial)](https://bitbucket.org/szymonsz/gen2-overlay/src/master/app-text/lector/)
## Reporting issues
When reporting issues:
## 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.
* 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.
Oh, please keep the translations short. There's only so much space for UI elements.
## Screenshots
@@ -68,6 +72,12 @@ When reporting issues:
### In program dictionary
![alt tag](https://i.imgur.com/Vh9xQUC.png)
## 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)

17
TODO
View File

@@ -1,4 +1,8 @@
TODO
General:
✓ Internationalization
Application icon
.desktop file
Options:
✓ Automatic library management
✓ Recursive file addition
@@ -51,8 +55,10 @@ 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
@@ -66,10 +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
Slider position change might be acting up too
Take metadata from the database when opening the file
Slider position change might be acting up
Deselecting all directories in the settings dialog also filters out manually added books
Secondary:
Annotations
@@ -90,9 +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
? Create emblem per filetype

View File

@@ -17,6 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import gc
import sys
import hashlib
import pathlib
@@ -42,7 +43,7 @@ from lector.settingsdialog import SettingsUI
from lector.metadatadialog import MetadataUI
from lector.definitionsdialog import DefinitionsUI
from resources import mainwindow, resources
from lector.resources import mainwindow, resources
class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
@@ -50,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
@@ -74,8 +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()
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)
@@ -100,13 +113,14 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
self.statusBar.addWidget(self.sorterProgress)
self.sorterProgress.setVisible(False)
# Statusbar - Toolbar Visibility
self.toolbarToggle.setIcon(self.QImageFactory.get_image('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)
# 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()
@@ -121,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)
@@ -196,10 +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.setFlat(True)
self.reloadLibrary.setIcon(self.QImageFactory.get_image('reload'))
self.reloadLibrary.setObjectName('reloadLibrary')
self.reloadLibrary.setToolTip('Scan library')
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(
@@ -251,17 +265,30 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
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()
@@ -289,7 +316,7 @@ 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())
@@ -402,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
@@ -420,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
@@ -441,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
@@ -458,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()
@@ -480,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
@@ -515,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)
@@ -543,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()
@@ -570,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()
@@ -578,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()
@@ -625,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())
@@ -742,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()
@@ -751,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)
@@ -869,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
@@ -885,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
@@ -893,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)
@@ -914,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
@@ -999,19 +1034,24 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
context_menu = QtWidgets.QMenu()
openAction = context_menu.addAction(
self.QImageFactory.get_image('view-readermode'), 'Start reading')
self.QImageFactory.get_image('view-readermode'),
self._translate('Main_UI', 'Start reading'))
editAction = None
if len(selected_indexes) == 1:
editAction = context_menu.addAction(
self.QImageFactory.get_image('edit-rename'), 'Edit')
self.QImageFactory.get_image('edit-rename'),
self._translate('Main_UI', 'Edit'))
deleteAction = context_menu.addAction(
self.QImageFactory.get_image('trash-empty'), 'Delete')
self.QImageFactory.get_image('trash-empty'),
self._translate('Main_UI', 'Delete'))
readAction = context_menu.addAction(
QtGui.QIcon(':/images/checkmark.svg'), 'Mark read')
QtGui.QIcon(':/images/checkmark.svg'),
self._translate('Main_UI', 'Mark read'))
unreadAction = context_menu.addAction(
QtGui.QIcon(':/images/xmark.svg'), 'Mark unread')
QtGui.QIcon(':/images/xmark.svg'),
self._translate('Main_UI', 'Mark unread'))
action = context_menu.exec_(self.sender().mapToGlobal(position))
@@ -1108,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)
@@ -1144,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:
@@ -1155,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()
@@ -1196,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()

View File

@@ -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,))

View File

@@ -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'

View File

@@ -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):

View File

@@ -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()
@@ -76,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):
@@ -105,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
@@ -281,9 +286,11 @@ class EPUB:
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>')
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]

View File

@@ -17,7 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from PyQt5 import QtGui
from resources import resources
from lector.resources import resources
class QImageFactory:

View File

@@ -20,7 +20,6 @@ import os
import pickle
import pathlib
from PyQt5 import QtGui, QtCore
from lector import database
from lector.models import TableProxyModel, ItemProxyModel
@@ -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()))

View File

@@ -19,15 +19,15 @@
from PyQt5 import QtWidgets, QtCore, QtGui
from lector import database
from resources import metadata
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):

View File

@@ -16,18 +16,17 @@
# 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 pickle
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
@@ -66,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', 'Last Read', '%', '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
@@ -88,7 +97,11 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
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
@@ -143,11 +156,8 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
return QtCore.QVariant()
if index.column() == 4:
last_accessed_time = item.data(self.role_dictionary[index.column()])
if last_accessed_time:
last_accessed = last_accessed_time
if not isinstance(last_accessed_time, QtCore.QDateTime):
last_accessed = pickle.loads(last_accessed_time)
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)
@@ -164,10 +174,13 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
output = self.common_functions.filterAcceptsRow(row, parent)
return output
def sort_table_columns(self, column):
sorting_order = self.sender().sortIndicatorOrder()
def sort_table_columns(self, column=None):
column = self.tableViewHeader.sortIndicatorSection()
sorting_order = self.tableViewHeader.sortIndicatorOrder()
self.sort(0, sorting_order)
self.setSortRole(self.role_dictionary[column])
if column != 0:
self.setSortRole(self.role_dictionary[column])
def time_convert(self, seconds):
seconds = int(seconds)

View File

@@ -22,7 +22,7 @@
import os
import time
import zipfile
from rarfile import rarfile
from lector.rarfile import rarfile
class ParseCOMIC:
@@ -49,7 +49,8 @@ class ParseCOMIC:
return
def get_title(self):
return self.book_extension[0]
title = os.path.basename(self.book_extension[0]).strip(' ')
return title
def get_author(self):
return None

View File

@@ -19,7 +19,7 @@
import os
import zipfile
from ePub.read_epub import EPUB
from lector.ePub.read_epub import EPUB
class ParseEPUB:

View File

@@ -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:

View File

@@ -17,6 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import os
from PyQt5 import QtCore
from bs4 import BeautifulSoup
@@ -48,7 +49,7 @@ class ParsePDF:
title = self.metadata.find('title').text
return title.replace('\n', '')
except AttributeError:
return 'Unknown'
return os.path.splitext(os.path.basename(self.filename))[0]
def get_author(self):
try:

View File

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 428 B

View File

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 703 B

View File

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 546 B

View File

Before

Width:  |  Height:  |  Size: 891 B

After

Width:  |  Height:  |  Size: 891 B

View File

Before

Width:  |  Height:  |  Size: 729 B

After

Width:  |  Height:  |  Size: 729 B

View 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

View File

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 561 B

View File

Before

Width:  |  Height:  |  Size: 601 B

After

Width:  |  Height:  |  Size: 601 B

View File

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 565 B

View File

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 565 B

View File

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 561 B

View File

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 565 B

View File

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 484 B

View File

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 484 B

View File

Before

Width:  |  Height:  |  Size: 694 B

After

Width:  |  Height:  |  Size: 694 B

View File

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 642 B

View File

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 859 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

View 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

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 501 B

After

Width:  |  Height:  |  Size: 501 B

View 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

View File

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 781 B

View File

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 916 B

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 790 B

View File

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 564 B

View File

Before

Width:  |  Height:  |  Size: 540 B

After

Width:  |  Height:  |  Size: 540 B

View File

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 428 B

View File

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 703 B

View File

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 546 B

View File

Before

Width:  |  Height:  |  Size: 891 B

After

Width:  |  Height:  |  Size: 891 B

View File

Before

Width:  |  Height:  |  Size: 729 B

After

Width:  |  Height:  |  Size: 729 B

View 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

View File

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 561 B

View File

Before

Width:  |  Height:  |  Size: 601 B

After

Width:  |  Height:  |  Size: 601 B

View File

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 565 B

View File

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 565 B

View File

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 561 B

View File

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 565 B

View File

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 484 B

View File

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 484 B

View File

Before

Width:  |  Height:  |  Size: 694 B

After

Width:  |  Height:  |  Size: 694 B

View File

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 642 B

View File

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 859 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

Some files were not shown because too many files have changed in this diff Show More