141 Commits

Author SHA1 Message Date
BasioMeusPuga
4389a0f5aa Merge pull request #120 from terrycloth/packaging
AppStream metadata, for giving Lector a proper page in Linux app stores
2020-01-15 00:08:44 +05:30
Andrew Toskin
b54ff37828 Replace empty dummy text with actual release notes, at least for now
We don't necessarily *need* to maintain release notes in the
.metainfo.xml file, but for my local test package builds, it would be
better to have actual information than to leave in the dummy text. (The
duplicate empty versions cause it to fail validation.)
2020-01-13 11:02:32 -08:00
Andrew Toskin
e7dd10fa3a Preferred naming scheme for .desktop files is also "reverse-DNS" 2019-12-30 20:40:40 -08:00
Andrew Toskin
3ede5b78fa Add AppStream .metainfo.xml file for Linux app stores
I had to make a number of assumptions here, like what the ID and
metainfo.xml license should be, whether to include the releases, etc. I
think it will be easier to talk about them in the pull request, though,
as then I'll be able to highlight the relevant lines for each point.
2019-12-30 20:37:19 -08:00
BasioMeusPuga
418d9e0c1c Merge pull request #116 from timgates42/bugfix/typo_specific
Fix simple typo: specifc -> specific
2019-12-02 22:33:28 -08:00
Tim Gates
e977826ea1 Fix simple typo: specifc -> specific 2019-12-03 15:05:57 +11:00
BasioMeusPuga
c661ed54de Update translations: Portuguese 2019-10-31 09:10:17 -07:00
BasioMeusPuga
dd3aa8a49c Merge pull request #112 from elisamalzoni/master
Portuguese support
2019-10-31 08:59:18 -07:00
elisamalzoni
65ad48c442 Portuguese support 2019-10-29 09:44:32 -03:00
BasioMeusPuga
fd433d6432 Tag generation stopgap 2019-08-26 17:42:56 -07:00
BasioMeusPuga
2bc73450fe Update translations: Czech 2019-07-06 21:47:45 -07:00
BasioMeusPuga
8b6800e14f Merge pull request #104 from VirtualThief/txt-support
Support for TXT files
2019-07-06 04:42:29 -07:00
Dmitrii Petukhov
d0cdd531a9 Check for textile installation 2019-07-06 07:37:32 +01:00
Dmitrii Petukhov
5e74b6f261 Fix for bookmarks in books without cover 2019-07-06 07:30:40 +01:00
Dmitrii Petukhov
24e45ac2b7 Support for opening txt files 2019-07-06 00:09:50 +01:00
BasioMeusPuga
916bdb5b14 Update translations: Japanese 2019-05-26 07:43:12 -04:00
BasioMeusPuga
ca108da948 Merge pull request #100 from sorairolake/japanese-translation
Add Japanese translation
2019-05-26 07:36:29 -04:00
Shun Sakai
6762f2cfce Add Japanese translation 2019-05-24 22:02:10 +09:00
BasioMeusPuga
0aea9ec33b Implement text pagination
Start double page mode
2019-04-01 22:25:09 -04:00
BasioMeusPuga
af1b988d93 Minor fixes 2019-03-22 21:35:09 -04:00
BasioMeusPuga
8fd6a0d432 Improve navigation bar 2019-03-16 21:53:19 -04:00
BasioMeusPuga
56f15528c2 Markdown support 2019-03-16 12:02:30 -04:00
BasioMeusPuga
f358ad169c Preliminary Navigation Bar 2019-03-16 10:27:07 -04:00
BasioMeusPuga
4cf0a9e78c Implement image rotation 2019-03-16 00:06:53 -04:00
BasioMeusPuga
38de0dcd13 Improve DjVu support 2019-03-15 19:08:06 -04:00
BasioMeusPuga
eb49ca92a4 Update README.md 2019-03-14 23:07:37 -04:00
BasioMeusPuga
bf93c7beab Preliminary DjVu support
SideDock fade in animation
2019-03-14 23:00:42 -04:00
BasioMeusPuga
ca57983739 Fix cover image name assignment 2019-03-11 07:06:54 -04:00
BasioMeusPuga
c71985f621 Minor fixes 2019-03-09 10:11:12 -05:00
BasioMeusPuga
c8fe0ba8b6 Make dependency checks less... crashy 2019-03-03 07:56:05 -05:00
BasioMeusPuga
d6df28c503 Merge pull request #92 from guoyunhe/hidpi-icons
Make icons sharp in HiDPI screen
2019-03-02 19:47:50 -05:00
Guo Yunhe
75ace25c57 Make icons sharp in HiDPI screen 2019-03-02 13:37:04 +01:00
BasioMeusPuga
d2d7dc2c8f Update for release 2019-03-01 23:17:22 -05:00
BasioMeusPuga
f622b0c23e Implement image color inversion 2019-02-19 00:01:07 +05:30
BasioMeusPuga
f312714a2c Multiple fixes
MuPDF import error
Definition text color
Database logging
2019-02-15 00:17:47 +05:30
BasioMeusPuga
c3f26ca225 Whoops 2019-02-13 00:27:48 +05:30
BasioMeusPuga
f6c7307647 Update README.md 2019-02-13 00:12:01 +05:30
BasioMeusPuga
b1714b9674 Error notifications
In application log viewer / database reset
Cleanup settings navigation
2019-02-13 00:09:37 +05:30
BasioMeusPuga
fa030e3060 Update flatpak manifest
Implement automated missing cover downloading
2019-02-12 11:51:16 +05:30
BasioMeusPuga
564db06179 Multiple fixes
Images are now center aligned
Better logging
2019-02-11 11:54:05 +05:30
BasioMeusPuga
3cd75807f9 Fix MOBI parser
Update Kindleunpack
Discover new and exciting bugs
2019-02-10 17:58:35 +05:30
BasioMeusPuga
f6f9d01060 Cleanup parsers 2019-02-10 09:03:12 +05:30
BasioMeusPuga
c6e30b67ad Improve EPUB parser compatibility and speed
Completely break MOBI parser
2019-02-10 06:47:51 +05:30
BasioMeusPuga
e4be239bf0 Overhaul EPUB parsing and ToC generation 2019-02-09 04:21:22 +05:30
BasioMeusPuga
1e004774c9 Search Google books for missing covers
Small fixes
2019-02-05 23:12:22 +05:30
BasioMeusPuga
91ca1e2190 Improve fb2 parsing
Miscellaneous fixes to navigation
2019-02-05 02:50:47 +05:30
BasioMeusPuga
d1662b47d9 Small fixes 2019-02-02 13:04:38 +05:30
BasioMeusPuga
dfe0fceea9 Small fixes
Compulsive refactor
2019-01-31 13:00:21 +05:30
BasioMeusPuga
268014cc3a Refactor sideDock 2019-01-31 01:51:47 +05:30
BasioMeusPuga
d1b1d7c59c Cleanup 2019-01-29 07:32:52 +05:30
BasioMeusPuga
470fc1078f Multiple fixes
Update translations
2019-01-28 02:28:43 +05:30
BasioMeusPuga
96f4d9193a Update requirements.txt 2019-01-26 19:53:40 +05:30
BasioMeusPuga
7aa42603bd Debulk widgets module 2019-01-26 19:44:58 +05:30
BasioMeusPuga
9a6392d1e6 Update README.md 2019-01-26 19:42:33 +05:30
BasioMeusPuga
739b84e9f4 Overhaul TOC generation and navigation 2019-01-26 19:03:30 +05:30
BasioMeusPuga
66746b4eaa Improve cover creation for PDFs 2019-01-22 23:32:25 +05:30
BasioMeusPuga
164450a888 Shift to MuPDF backend for pdf rendering 2019-01-22 22:36:32 +05:30
BasioMeusPuga
191ea7ef3a Update README.md 2019-01-19 22:34:21 +05:30
BasioMeusPuga
ca8ddd38a2 Improve logging
requirements.txt
Small UI fixes
2019-01-19 22:29:56 +05:30
BasioMeusPuga
a45e183914 Implement logging 2019-01-19 20:31:19 +05:30
BasioMeusPuga
506c458544 Update readme
Begin logging
Account for fb2 books without covers
2019-01-19 01:19:58 +05:30
BasioMeusPuga
5e3987dc04 Improve comic view 2019-01-17 23:03:28 +05:30
BasioMeusPuga
5b8bc1d707 Comic page increment setting 2019-01-17 22:42:11 +05:30
BasioMeusPuga
b3e4060661 UI cleanup 2019-01-17 22:17:22 +05:30
BasioMeusPuga
2185e9fcf7 Tab reordering 2019-01-17 21:53:04 +05:30
BasioMeusPuga
5d35319164 Improve mouse pointer hiding
Improve search result formatting
2019-01-16 12:58:40 +05:30
BasioMeusPuga
c6d24fd970 Multiple fixes 2019-01-16 12:25:59 +05:30
BasioMeusPuga
17f39c557b Manga mode
Comics are parsed for images only
Miscellaneous fixes
2019-01-14 15:54:29 +05:30
BasioMeusPuga
f997bc9c9a Search result highlighting
Disable UI elements when irrelevant
2019-01-09 13:50:08 +05:30
BasioMeusPuga
930a97a8fa Implement search 2019-01-09 05:58:47 +05:30
BasioMeusPuga
026fff3d7a Implement search UI
Discovered severe inadequacies. Some of them were in the program.
2019-01-08 05:47:50 +05:30
BasioMeusPuga
f9eec130dd Update translations 2019-01-03 04:05:52 +05:30
BasioMeusPuga
d75689ea97 Consolidate docks
Update copyright
Pondered the nature of protein powder
2019-01-03 03:28:31 +05:30
BasioMeusPuga
6ea5635d28 Cleanup 2018-12-01 18:59:49 +05:30
BasioMeusPuga
4b9221128c Implement single/double page modes for comics/pdfs 2018-10-31 10:39:21 +05:30
BasioMeusPuga
b5349315be UI elements for page modes 2018-10-29 23:08:34 +05:30
BasioMeusPuga
ee18f157f1 Account for absence of Qt Multimedia 2018-10-20 03:49:45 +05:30
BasioMeusPuga
ae325736d5 Update README.md 2018-07-11 13:09:16 -04:00
BasioMeusPuga
826da72d4b Adjust image paths 2018-07-11 13:07:58 -04:00
BasioMeusPuga
b5231cd383 Merge pull request #71 from psikoz/master
logo add
2018-07-11 13:02:24 -04:00
BasioMeusPuga
5ac843e48c Merge pull request #72 from guoyunhe/patch-1
Update README: openSUSE package is now official
2018-07-11 12:55:22 -04:00
Guo Yunhe
74417319be Update README: openSUSE package is now official
Lector is now included in openSUSE Tumbleweed and will be included in future Leap releases.
2018-07-11 13:15:37 +03:00
psikoz
f8555a6ed5 Update README.md 2018-07-11 12:33:03 +03:00
psikoz
4346c27adc Add files via upload 2018-07-11 12:21:09 +03:00
BasioMeusPuga
16adf57dae Add option to include TOC with bookmarks
Shift dock positions
2018-07-08 20:20:57 -04:00
BasioMeusPuga
8534088f4a Update README.md 2018-07-06 16:19:24 -04:00
BasioMeusPuga
a81ed537a6 Improve bookmark addition and deletion
Fix toolbar button checking
2018-07-06 15:38:56 -04:00
BasioMeusPuga
ed5bc0b2b9 Shift to tree view for bookmarks
Cleanup
2018-07-06 09:01:40 -04:00
BasioMeusPuga
8cb8904e58 Exception handling for improperly formatted fb2 books 2018-06-17 10:40:37 -04:00
BasioMeusPuga
aa093b8cc2 Image display in fb2 parser 2018-06-17 10:29:55 -04:00
BasioMeusPuga
42b4d0317d Update README.md 2018-06-14 16:14:11 -04:00
BasioMeusPuga
a0e463bc58 Speed up file addition
Improve fb2 parser
Fix extension checking
2018-06-14 16:10:27 -04:00
BasioMeusPuga
4a2da61b51 Merge branch 'master' of https://github.com/basiomeuspuga/lector 2018-06-13 16:33:57 -04:00
BasioMeusPuga
5c481ccafe Begin fb2 support
Fix Chinese translation
2018-06-13 16:33:30 -04:00
BasioMeusPuga
30760b879e Merge pull request #67 from guoyunhe/guoyunhe-patch-1
Add openSUSE package
2018-06-07 18:17:41 -04:00
Guo Yunhe
af7868f62a Add openSUSE package 2018-06-07 19:03:45 +03:00
BasioMeusPuga
62c44730d8 Start search functionality
Multiple fixes
2018-05-24 15:14:56 -04:00
BasioMeusPuga
045d8a3e52 Update README.md 2018-05-15 00:45:40 -04:00
BasioMeusPuga
55bee210c6 Update version for release
Update translations
2018-05-13 18:40:24 -04:00
BasioMeusPuga
d3746c8e98 Merge branch 'master' of https://github.com/basiomeuspuga/lector 2018-05-13 18:28:52 -04:00
BasioMeusPuga
ffcf07414f Implement file drag drop 2018-05-13 18:16:17 -04:00
BasioMeusPuga
ffaace2eaa Uniform tab sizes
Path search
PDF parser exception handling
2018-05-13 15:54:17 -04:00
BasioMeusPuga
32455dd859 Merge pull request #52 from jaccsr/master
Language code
2018-05-05 09:38:19 -04:00
BasioMeusPuga
3e54340694 Account for older versions of Qt 2018-05-03 08:37:22 -04:00
jaccsr
bc6c7d1c36 I forgot the language code 2018-05-02 10:23:41 +08:00
jaccsr
ebd746b7b2 Merge pull request #1 from BasioMeusPuga/master
a
2018-05-02 09:11:23 +08:00
BasioMeusPuga
ebc3ef9f1b Update README.md 2018-05-01 18:33:44 -04:00
BasioMeusPuga
7238605441 Update translations: Chinese (simplified) 2018-05-01 18:29:59 -04:00
BasioMeusPuga
ea86737970 Merge pull request #50 from jaccsr/master
Chinese (simplified) translation
2018-05-01 18:22:19 -04:00
jaccsr
ab4c586c06 add chinese(simp) language 2018-05-02 06:06:23 +08:00
BasioMeusPuga
7977bde410 Multiple fixes 2018-04-29 08:11:46 -04:00
BasioMeusPuga
626472dd04 Comic view drag and drop
Menu icons
Polish for docks
2018-04-20 10:00:12 +05:30
BasioMeusPuga
d9efe2da3c Annotation notes 2018-04-19 20:35:22 +05:30
BasioMeusPuga
ec197f0829 Annotation saving, loading, and deletion 2018-04-19 15:19:20 +05:30
BasioMeusPuga
335479bcfb Small fixes 2018-04-17 11:24:16 +05:30
BasioMeusPuga
cbf01c6d16 Annotation placement 2018-04-16 13:00:49 +05:30
BasioMeusPuga
98ca118a60 Remove unnecessary shebangs
How this isn't a Ricky Martin song, we'll never know
2018-04-12 10:15:20 +05:30
BasioMeusPuga
c7aa0e28ee Web search for selection
Bugfixes
2018-04-11 01:43:21 +05:30
BasioMeusPuga
528c2e387c Improve spacebar navigation
Refactor variables
2018-04-10 12:39:52 +05:30
BasioMeusPuga
bc54d6b686 Complete annotation editor
Annotation saving and loading
2018-04-08 14:41:31 +05:30
BasioMeusPuga
8f298de58e Cleanup optional imports
Disable the multiprocessing module on Windows
Update translations
2018-04-02 19:57:46 +05:30
BasioMeusPuga
366859ebe0 Fix incorrect function argument 2018-04-02 01:06:53 +05:30
BasioMeusPuga
8c51cc047e Split content display widgets into new module 2018-03-31 10:43:13 +05:30
BasioMeusPuga
5081a31f1a Fine tune progress display
Option: Set consider read at percentage
Small fixes
2018-03-31 10:31:57 +05:30
BasioMeusPuga
aff69d95c1 Make progress work with block count
Break database thoroughly
Fix pdf year bug
2018-03-31 03:03:49 +05:30
BasioMeusPuga
0b8427c864 Flatpak manifest: No pdf support but otherwise functional 2018-03-30 21:03:37 +05:30
BasioMeusPuga
43dd6a34d9 Implement basic annotation editor / preview 2018-03-30 20:48:13 +05:30
BasioMeusPuga
2f4adfc183 Small fixes 2018-03-30 11:06:48 +05:30
BasioMeusPuga
0d015ad72e Auto hide Tab-bar and Statusbar 2018-03-29 02:50:01 +05:30
BasioMeusPuga
406ca0485f Position setting should work all the time now
Learn not to swear so much at the screen
Cover icons in the tab bar
Shift Scan Library button from the Library tab to the Library toolbar
2018-03-29 01:45:58 +05:30
BasioMeusPuga
ab6760226e Search position seeking fix for multiple tabs
Space navigation tries its best to not cut lines off
2018-03-28 20:17:00 +05:30
BasioMeusPuga
66c8626d43 Significant improvements to bookmark dock display 2018-03-28 00:46:12 +05:30
BasioMeusPuga
5fa724ae69 Implement scroll speed slider 2018-03-27 21:58:35 +05:30
BasioMeusPuga
d417a94829 Fix fullscreen toggle affecting reading position bug
Bookmark navigation much more reliable
Start annotations UI
2018-03-27 08:23:07 +05:30
BasioMeusPuga
9c85a1075e Fix context menu behavior 2018-03-24 01:34:42 +05:30
BasioMeusPuga
dd4b502861 Move contentView profile modification functions to guifunctions module 2018-03-24 01:15:33 +05:30
BasioMeusPuga
0f963b20f9 Move cover loading and culling to guifunctions module 2018-03-24 00:30:58 +05:30
BasioMeusPuga
f63b6627b2 Update README.md 2018-03-24 00:00:12 +05:30
BasioMeusPuga
00db5d5e0f Update translations 2018-03-23 23:57:49 +05:30
BasioMeusPuga
5e53d40e68 Redesign settings dialog
Remove dependency on requests
2018-03-23 23:56:01 +05:30
BasioMeusPuga
6ffa6934ed Fix splitting for a repeated anchor 2018-03-23 17:48:58 +05:30
108 changed files with 29827 additions and 8575 deletions

View File

@@ -1,5 +1,5 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2018 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -15,19 +15,26 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
SOURCES += lector/__main__.py \
lector/annotations.py \
lector/contentwidgets.py \
lector/definitionsdialog.py \
lector/dockwidgets.py \
lector/library.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
lector/toolbars.py \
lector/widgets.py \
lector/resources/definitions.py \
lector/resources/settingswindow.py \
lector/resources/metadata.py \
lector/resources/mainwindow.py
TRANSLATIONS += resources/translations/Lector_es.ts \
resources/translations/Lector_fr.ts \
resources/translations/Lector_de.ts \
resources/translations/SAMPLE.ts
TRANSLATIONS += lector/resources/translations/Lector_cs.ts \
lector/resources/translations/Lector_es.ts \
lector/resources/translations/Lector_fr.ts \
lector/resources/translations/Lector_de.ts \
lector/resources/translations/Lector_zh.ts \
lector/resources/translations/Lector_ja.ts \
lector/resources/translations/Lector_pt.ts \
lector/resources/translations/SAMPLE.ts

View File

@@ -1,27 +1,47 @@
# Lector
<p align="center"><img src="lector/resources/raw/logo/logotype_horizontal.png" alt="Lector" height="90px"></p>
Qt based ebook reader
Currently supports:
* pdf
* epub
* djvu
* fb2
* mobi
* azw / azw3 / azw4
* cbr / cbz
* md
Support for a bunch of other formats is coming. Please see the TODO for additional information.
## Contribute
[Paypal](https://www.paypal.me/supportlector)
Bitcoin: 17jaxj26vFJNqQ2hEVerbBV5fpTusfqFro
## Requirements
### Needed
| Package | Version tested |
| --- | --- |
| Qt5 | 5.10.1 |
| Python | 3.6 |
| PyQt5 | 5.10.1 |
| python-requests | 2.18.4 |
| python-lxml | 4.3.0 |
| python-beautifulsoup4 | 4.6.0 |
| poppler-qt5 | 0.61.1 |
| python-poppler-qt5 | 0.24.2 |
| python-xmltodict | 0.11.0 |
poppler-qt5 and python-poppler-qt5 are optional.
### Optional
| Package | Version tested | Required for |
| --- | --- | --- |
| python-pymupdf | 1.14.5 | PDF support |
| python-djvulibre | 0.8.4 | DjVu support |
| python-markdown | 3.0.1 | Markdown support |
| textile | 3.0.4 | TXT support |
## Support
When reporting issues:
* Make sure you're at the latest commit.
* Run with `$EXECUTABLEPATH debug`.
* Include the log `~/.local/share/Lector/Lector.log` AND terminal output.
* 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.
## Installation
### Manual
@@ -34,49 +54,55 @@ poppler-qt5 and python-poppler-qt5 are optional.
3. OR launch with `lector/__main__.py`
### Available packages
* [AUR](https://aur.archlinux.org/packages/lector-git/)
* [AUR - Releases](https://aur.archlinux.org/packages/lector/)
* [AUR - Git](https://aur.archlinux.org/packages/lector-git/)
* [Gentoo (unofficial)](https://bitbucket.org/szymonsz/gen2-overlay/src/master/app-text/lector/)
* [Fedora (unofficial)](https://copr.fedorainfracloud.org/coprs/bugzy/lector/)
* [openSUSE](https://software.opensuse.org/package/lector)
## Translations
1. There is a `SAMPLE.ts` file in `resources/translations`. Open it in `Qt Linguist`.
1. There is a `SAMPLE.ts` file [here](https://github.com/BasioMeusPuga/Lector/tree/master/lector/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.
Please keep the translations short. There's only so much space for UI elements.
## Screenshots
### Main window
![alt tag](https://i.imgur.com/yrv2c0a.png)
![alt tag](https://i.imgur.com/516hRkS.png)
### Table view
![alt tag](https://i.imgur.com/b1XdXqP.png)
![alt tag](https://i.imgur.com/o9An7AR.png)
### Book reading view
![alt tag](https://i.imgur.com/Tei6TqF.png)
![alt tag](https://i.imgur.com/ITG63Fc.png)
### Distraction free view
![alt tag](https://i.imgur.com/g8Ltupy.png)
### Annotation support
![alt tag](https://i.imgur.com/gLK29F4.png)
### Comic reading view
![alt tag](https://i.imgur.com/U5JR35g.png)
![alt tag](https://i.imgur.com/rvvTQCM.png)
### Bookmark support
![alt tag](https://i.imgur.com/RZkmCzG.png)
![alt tag](https://i.imgur.com/Y7qoU8m.png)
### View profiles
![alt tag](https://i.imgur.com/gkJ88pi.png)
![alt tag](https://i.imgur.com/awE2q2K.png)
### Metadata editor
![alt tag](https://i.imgur.com/AqQREBf.png)
![alt tag](https://i.imgur.com/0CDpNO8.png)
### In program dictionary
![alt tag](https://i.imgur.com/Vh9xQUC.png)
![alt tag](https://i.imgur.com/RF72m2h.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.
### Settings window
![alt tag](https://i.imgur.com/l6zJXaH.png)
## Attributions
* [KindleUnpack](https://github.com/kevinhendricks/KindleUnpack)

71
TODO
View File

@@ -1,8 +1,10 @@
TODO
General:
✓ Internationalization
Application icon
.desktop file
Application icon
.desktop file
✓ Shift to logging instead of print statements
Flatpak and AppImage support
Options:
✓ Automatic library management
✓ Recursive file addition
@@ -29,9 +31,15 @@ TODO
✓ Information dialog widget
✓ Allow editing of database data through the UI + for Bookmarks
✓ Include (action) icons with the applications
✓ Drag and drop support for the library
✓ Tab reordering
Additional Settings:
✓ Create covers for books without them - VERY SLOW
Set focus to newly added file
Reading:
✓ Navbar
✓ Drop down for TOC
✓ Treeview navigation for TOC
✓ Override the keypress event of the textedit
✓ Use format* icons for toolbar buttons
✓ Implement book view settings with a(nother) toolbar
@@ -57,43 +65,78 @@ TODO
✓ Paragraph indentation
✓ Comic view keyboard shortcuts
✓ Comic view context menu
✓ Image rotation
✓ Make the bookmark dock float over the reading area
✓ Spacebar should not cut off lines at the top
✓ Track open bookmark windows so they can be closed quickly at exit
✓ Search document using QTextCursor
✓ Double page / column view
✓ For comics
Caching is currently non functional
Annotations
✓ Text
✓ Disable buttons for annotations, search in images
Adjust key navigation according to viewport dimensions
Search document using QTextCursor?
Filetypes:
✓ pdf support
Parse TOC
Parse TOC
✓ epub support
✓ Homegrown solution please
✓ cbz, cbr support
✓ Keep font settings enabled but only for background color
✓ Double page view
✓ Manga mode
✓ mobi, azw support
Limit the extra files produced by KindleUnpack
Have them save to memory
✓ fb2 support
✓ Images need to show up in their placeholders
✓ djvu support
✓ markdown support
Other:
✓ Define every widget in code
Bugs:
Slider position change might be acting up
Deselecting all directories in the settings dialog also filters out manually added books
Bookmark name for a page that's not on the TOC and has nothing before
Screen position still keeps jumping when inside a paragraph
Better recursion needed for fb2 toc
Search results should ignore punctuation
Keep text size for annotations
Drag and drop is acting out
Search and annotation buttons become visible when font settings are hidden in comics
Secondary:
Annotations
Auto switch between flow and single page mode
Text to speech
Definitions dialog needs to respond to escape
Zoom slider for comics / library
Tab tooltip
Additional Settings:
Find definitions on Google
Disable progressbar - 20% book addition speed improvement
Disable cover loading when reading - Saves ~2M / book
Special formatting for each chapter's title
Signal end of chapter with some text
Graphical themes
Change focus rectangle dimensions
Tab reordering
Universal Ctrl + Tab
Allow tabs to detach and form their own windows
Goodreads API: Ratings, Read, Recommendations
Get ISBN using python-isbnlib
Pagination
Use embedded fonts + CSS
Scrolling: Smooth / By Line
Spacebar should not cut off lines at the top
Shift to logging instead of print statements
txt, doc, chm, djvu, fb2 support
txt, doc, chm support
Include icons for filetype emblems
Drag and drop support for the library
Comic view modes
Continuous paging
Double pages
Ignore a / the / numbers for sorting purposes
? Add only one file type if multiple are present
? Create emblem per filetype
In application notifications
Notification in case the filter is filtering out all files with no option in place
Option to fit images to viewport
Need help with:
Double page view for books
Scrolling: Smooth / By Line
Annotation preview in listView
Pagination

View File

@@ -0,0 +1,143 @@
{
"app-id":"com.basiomeuspuga.Lector",
"runtime":"org.kde.Platform",
"runtime-version":"5.12",
"sdk":"org.kde.Sdk",
"command":"lector",
"rename-icon":"Lector",
"rename-desktop-file":"lector.desktop",
"rename-appdata-file":"lector.appdata.xml",
"finish-args":[
"--filesystem=host",
"--socket=x11",
"--socket=wayland",
"--device=dri",
"--share=ipc",
"--share=network"
],
"build-options":{
"cflags":"-O2",
"cxxflags":"-O2"
},
"modules":[
{
"name": "PyQt5",
"buildsystem": "simple",
"build-commands": [
"pip3 install --prefix=/app PyQt5-5.12-5.12.1-cp35.cp36.cp37.cp38-abi3-manylinux1_x86_64.whl"
],
"modules":[
{
"name":"PyQt5-sip",
"sources":[
{
"type":"file",
"url":"https://files.pythonhosted.org/packages/ae/9c/74fba0b62a0756d214f9aded5b0184130f7866def7532fa68823f34feefa/PyQt5_sip-4.19.14-cp37-cp37m-manylinux1_x86_64.whl",
"sha256":"04bd0bb8b6f8fa03c2dfbdfff0c8c9bfb3f46a21dd4cac73983dae93bf949523"
}
],
"buildsystem":"simple",
"build-commands":[
"pip3 install --prefix=/app PyQt5_sip-4.19.14-cp37-cp37m-manylinux1_x86_64.whl"
]
}
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/5e/91/9ac8827d0af428e756f461a3aa7bcbc53d6450edfe026e27569f5ff3689e/PyQt5-5.12-5.12.1-cp35.cp36.cp37.cp38-abi3-manylinux1_x86_64.whl",
"sha256": "fd5946795b39922f971cf92dec799aadc7544b7fa993a79b9f6059f13d817e6e"
}
]
},
{
"name":"beautifulsoup4",
"buildsystem":"simple",
"sources":[
{
"type":"file",
"url":"https://files.pythonhosted.org/packages/1d/5d/3260694a59df0ec52f8b4883f5d23b130bc237602a1411fa670eae12351e/beautifulsoup4-4.7.1-py3-none-any.whl",
"sha256":"034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858"
}
],
"modules":[
{
"name": "soupsieve",
"sources":[
{
"type":"file",
"url":"https://files.pythonhosted.org/packages/bf/b3/2473abf05c4950c6a829ed5dcbc40d8b56d4351d15d6939c8ffb7c6b1a14/soupsieve-1.7.3-py2.py3-none-any.whl",
"sha256":"466910df7561796a60748826781ebe9a888f7a1668a636ae86783f44d10aae73"
}
],
"buildsystem":"simple",
"build-commands":[
"pip3 install --prefix=/app soupsieve-1.7.3-py2.py3-none-any.whl"
]
}
],
"build-commands":[
"pip3 install --prefix=/app beautifulsoup4-4.7.1-py3-none-any.whl"
]
},
{
"name":"xmltodict",
"buildsystem":"simple",
"sources":[
{
"type": "file",
"url":"https://files.pythonhosted.org/packages/28/fd/30d5c1d3ac29ce229f6bdc40bbc20b28f716e8b363140c26eff19122d8a5/xmltodict-0.12.0-py2.py3-none-any.whl",
"sha256":"8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051"
}
],
"build-commands":[
"pip3 install --prefix=/app xmltodict-0.12.0-py2.py3-none-any.whl"
]
},
{
"name":"PyMuPDF",
"buildsystem":"simple",
"build-commands": [
"pip3 install --prefix=/app PyMuPDF-1.14.8-cp37-cp37m-manylinux1_x86_64.whl"
],
"sources":[
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/3c/df/4bfaee2631b505d502c2ba64aa437799f0a64125edb1d4c4c38044ad1ecc/PyMuPDF-1.14.8-cp37-cp37m-manylinux1_x86_64.whl",
"sha256": "a49798b58cce00e09b8a4431a5f64a400b11a0959f29507187c471208ce040a5"
}
]
},
{
"name":"lxml",
"buildsystem":"simple",
"sources":[
{
"type":"file",
"url":"https://files.pythonhosted.org/packages/08/f2/04bf04e42c070f65b64dbde02d2c94851251f19f5e9f803cc8f8bc61ac77/lxml-4.3.1-cp37-cp37m-manylinux1_x86_64.whl",
"sha256":"c0a7751ba1a4bfbe7831920d98cee3ce748007eab8dfda74593d44079568219a"
}
],
"build-commands":[
"pip3 install --prefix=/app lxml-4.3.1-cp37-cp37m-manylinux1_x86_64.whl"
]
},
{
"name":"lector",
"buildsystem":"simple",
"ensure-writable":[
"/lib/python*/site-packages/easy-install.pth"
],
"sources":[
{
"type":"git",
"url":"https://github.com/BasioMeusPuga/Lector.git"
}
],
"build-commands":[
"python3 setup.py build",
"python3 setup.py install --prefix=/app"
]
}
]
}

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals, division, absolute_import, print_functi
import os
__path__ = ["lib", os.path.dirname(__file__), "kindleunpack"]
__path__ = ["lib", os.path.dirname(os.path.realpath(__file__)), "kindleunpack"]
import sys
import codecs
@@ -140,6 +140,8 @@ if PY2:
# 0.76 pre-release version only fix name related issues in opf by not using original file name in mobi7
# 0.77 bug fix for unpacking HDImages with included Fonts
# 0.80 converted to work with both python 2.7 and Python 3.3 and later
# 0.81 various fixes
# 0.82 Handle calibre-generated mobis that can have skeletons with no fragments
DUMP = False
""" Set to True to dump all possible information. """
@@ -847,7 +849,7 @@ def process_all_mobi_headers(files, apnxfile, sect, mhlst, K8Boundary, k8only=Fa
return
def unpackBook(infile, outdir, apnxfile=None, epubver='2', use_hd=True, dodump=False, dowriteraw=False, dosplitcombos=False):
def unpackBook(infile, outdir, apnxfile=None, epubver='2', use_hd=False, dodump=False, dowriteraw=False, dosplitcombos=False):
global DUMP
global WRITE_RAW_DATA
global SPLIT_COMBO_MOBIS
@@ -949,7 +951,7 @@ def main(argv=unicode_argv()):
global WRITE_RAW_DATA
global SPLIT_COMBO_MOBIS
print("KindleUnpack v0.80")
print("KindleUnpack v0.82")
print(" Based on initial mobipocket version Copyright © 2009 Charles M. Hannum <root@ihack.net>")
print(" Extensive Extensions and Improvements Copyright © 2009-2014 ")
print(" by: P. Durrant, K. Hendricks, S. Siebert, fandrieu, DiapDealer, nickredding, tkeo.")

View File

@@ -180,9 +180,11 @@ class K8Processor:
fragptr = 0
baseptr = 0
cnt = 0
filename = 'part%04d.xhtml' % cnt
for [skelnum, skelname, fragcnt, skelpos, skellen] in self.skeltbl:
baseptr = skelpos + skellen
skeleton = text[skelpos: baseptr]
aidtext = "0"
for i in range(fragcnt):
[insertpos, idtext, filenum, seqnum, startpos, length] = self.fragtbl[fragptr]
aidtext = idtext[12:-2]

View File

@@ -273,7 +273,7 @@ class OPFProcessor(object):
del metadata['CoverOffset']
handleMetaPairs(data, metadata, 'Codec', 'output encoding')
# handle kindlegen specifc tags
# handle kindlegen specific tags
handleTag(data, metadata, 'DictInLanguage', 'DictionaryInLanguage')
handleTag(data, metadata, 'DictOutLanguage', 'DictionaryOutLanguage')
handleMetaPairs(data, metadata, 'RegionMagnification', 'RegionMagnification')

View File

@@ -1,525 +0,0 @@
#! /usr/bin/python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
# this program works in concert with the output from KindleUnpack
'''
Convert from Mobi ML to XHTML
'''
import os
import sys
import re
SPECIAL_HANDLING_TAGS = {
'?xml' : ('xmlheader', -1),
'!--' : ('comment', -3),
'!DOCTYPE' : ('doctype', -1),
}
SPECIAL_HANDLING_TYPES = ['xmlheader', 'doctype', 'comment']
SELF_CLOSING_TAGS = ['br' , 'hr', 'input', 'img', 'image', 'meta', 'spacer', 'link', 'frame', 'base', 'col', 'reference']
class MobiMLConverter(object):
PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE)
IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex')
def __init__(self, filename):
self.base_css_rules = 'blockquote { margin: 0em 0em 0em 1.25em }\n'
self.base_css_rules += 'p { margin: 0em }\n'
self.base_css_rules += '.bold { font-weight: bold }\n'
self.base_css_rules += '.italic { font-style: italic }\n'
self.base_css_rules += '.mbp_pagebreak { page-break-after: always; margin: 0; display: block }\n'
self.tag_css_rules = {}
self.tag_css_rule_cnt = 0
self.path = []
self.filename = filename
self.wipml = open(self.filename, 'rb').read()
self.pos = 0
self.opfname = self.filename.rsplit('.',1)[0] + '.opf'
self.opos = 0
self.meta = ''
self.cssname = os.path.join(os.path.dirname(self.filename),'styles.css')
self.current_font_size = 3
self.font_history = []
def cleanup_html(self):
self.wipml = re.sub(r'<div height="0(pt|px|ex|em|%){0,1}"></div>', '', self.wipml)
self.wipml = self.wipml.replace('\r\n', '\n')
self.wipml = self.wipml.replace('> <', '>\n<')
self.wipml = self.wipml.replace('<mbp: ', '<mbp:')
# self.wipml = re.sub(r'<?xml[^>]*>', '', self.wipml)
self.wipml = self.wipml.replace('<br></br>','<br/>')
def replace_page_breaks(self):
self.wipml = self.PAGE_BREAK_PAT.sub(
'<div class="mbp_pagebreak" />',
self.wipml)
# parse leading text of ml and tag
def parseml(self):
p = self.pos
if p >= len(self.wipml):
return None
if self.wipml[p] != '<':
res = self.wipml.find('<',p)
if res == -1 :
res = len(self.wipml)
self.pos = res
return self.wipml[p:res], None
# handle comment as a special case to deal with multi-line comments
if self.wipml[p:p+4] == '<!--':
te = self.wipml.find('-->',p+1)
if te != -1:
te = te+2
else :
te = self.wipml.find('>',p+1)
ntb = self.wipml.find('<',p+1)
if ntb != -1 and ntb < te:
self.pos = ntb
return self.wipml[p:ntb], None
self.pos = te + 1
return None, self.wipml[p:te+1]
# parses string version of tag to identify its name,
# its type 'begin', 'end' or 'single',
# plus build a hashtable of its attributes
# code is written to handle the possiblity of very poor formating
def parsetag(self, s):
p = 1
# get the tag name
tname = None
ttype = None
tattr = {}
while s[p:p+1] == ' ' :
p += 1
if s[p:p+1] == '/':
ttype = 'end'
p += 1
while s[p:p+1] == ' ' :
p += 1
b = p
while s[p:p+1] not in ('>', '/', ' ', '"', "'", "\r", "\n") :
p += 1
tname=s[b:p].lower()
if tname == '!doctype':
tname = '!DOCTYPE'
# special cases
if tname in SPECIAL_HANDLING_TAGS.keys():
ttype, backstep = SPECIAL_HANDLING_TAGS[tname]
tattr['special'] = s[p:backstep]
if ttype is None:
# parse any attributes
while s.find('=',p) != -1 :
while s[p:p+1] == ' ' :
p += 1
b = p
while s[p:p+1] != '=' :
p += 1
aname = s[b:p].lower()
aname = aname.rstrip(' ')
p += 1
while s[p:p+1] == ' ' :
p += 1
if s[p:p+1] in ('"', "'") :
p = p + 1
b = p
while s[p:p+1] not in ('"', "'") :
p += 1
val = s[b:p]
p += 1
else :
b = p
while s[p:p+1] not in ('>', '/', ' ') :
p += 1
val = s[b:p]
tattr[aname] = val
# label beginning and single tags
if ttype is None:
ttype = 'begin'
if s.find(' /',p) >= 0:
ttype = 'single_ext'
elif s.find('/',p) >= 0:
ttype = 'single'
return ttype, tname, tattr
# main routine to convert from mobi markup language to html
def processml(self):
# are these really needed
html_done = False
head_done = False
body_done = False
skip = False
htmlstr = ''
self.replace_page_breaks()
self.cleanup_html()
# now parse the cleaned up ml into standard xhtml
while True:
r = self.parseml()
if not r:
break
text, tag = r
if text:
if not skip:
htmlstr += text
if tag:
ttype, tname, tattr = self.parsetag(tag)
# If we run into a DTD or xml declarations inside the body ... bail.
if tname in SPECIAL_HANDLING_TAGS.keys() and tname != 'comment' and body_done:
htmlstr += '\n</body></html>'
break
# make sure self-closing tags actually self-close
if ttype == 'begin' and tname in SELF_CLOSING_TAGS:
ttype = 'single'
# make sure any end tags of self-closing tags are discarded
if ttype == 'end' and tname in SELF_CLOSING_TAGS:
continue
# remove embedded guide and refernces from old mobis
if tname in ('guide', 'ncx', 'reference') and ttype in ('begin', 'single', 'single_ext'):
tname = 'removeme:{0}'.format(tname)
tattr = None
if tname in ('guide', 'ncx', 'reference', 'font', 'span') and ttype == 'end':
if self.path[-1] == 'removeme:{0}'.format(tname):
tname = 'removeme:{0}'.format(tname)
tattr = None
# Get rid of font tags that only have a color attribute.
if tname == 'font' and ttype in ('begin', 'single', 'single_ext'):
if 'color' in tattr.keys() and len(tattr.keys()) == 1:
tname = 'removeme:{0}'.format(tname)
tattr = None
# Get rid of empty spans in the markup.
if tname == 'span' and ttype in ('begin', 'single', 'single_ext') and not len(tattr):
tname = 'removeme:{0}'.format(tname)
# need to handle fonts outside of the normal methods
# so fonts tags won't be added to the self.path since we keep track
# of font tags separately with self.font_history
if tname == 'font' and ttype == 'begin':
# check for nested font start tags
if len(self.font_history) > 0 :
# inject a font end tag
taginfo = ('end', 'font', None)
htmlstr += self.processtag(taginfo)
self.font_history.append((ttype, tname, tattr))
# handle the current font start tag
taginfo = (ttype, tname, tattr)
htmlstr += self.processtag(taginfo)
continue
# check for nested font tags and unnest them
if tname == 'font' and ttype == 'end':
self.font_history.pop()
# handle this font end tag
taginfo = ('end', 'font', None)
htmlstr += self.processtag(taginfo)
# check if we were nested
if len(self.font_history) > 0:
# inject a copy of the most recent font start tag from history
taginfo = self.font_history[-1]
htmlstr += self.processtag(taginfo)
continue
# keep track of nesting path
if ttype == 'begin':
self.path.append(tname)
elif ttype == 'end':
if tname != self.path[-1]:
print ('improper nesting: ', self.path, tname, ttype)
if tname not in self.path:
# 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)
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])
self.path.pop()
self.path.pop()
if tname == 'removeme:{0}'.format(tname):
if ttype in ('begin', 'single', 'single_ext'):
skip = True
else:
skip = False
else:
taginfo = (ttype, tname, tattr)
htmlstr += self.processtag(taginfo)
# handle potential issue of multiple html, head, and body sections
if tname == 'html' and ttype == 'begin' and not html_done:
htmlstr += '\n'
html_done = True
if tname == 'head' and ttype == 'begin' and not head_done:
htmlstr += '\n'
# also add in metadata and style link tags
htmlstr += self.meta
htmlstr += '<link href="styles.css" rel="stylesheet" type="text/css" />\n'
head_done = True
if tname == 'body' and ttype == 'begin' and not body_done:
htmlstr += '\n'
body_done = True
# handle issue of possibly missing html, head, and body tags
# I have not seen this but the original did something like this so ...
if not body_done:
htmlstr = '<body>\n' + htmlstr + '</body>\n'
if not head_done:
headstr = '<head>\n'
headstr += self.meta
headstr += '<link href="styles.css" rel="stylesheet" type="text/css" />\n'
headstr += '</head>\n'
htmlstr = headstr + htmlstr
if not html_done:
htmlstr = '<html>\n' + htmlstr + '</html>\n'
# finally add DOCTYPE info
htmlstr = '<?xml version="1.0"?>\n<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' + htmlstr
css = self.base_css_rules
for cls, rule in self.tag_css_rules.items():
css += '.%s { %s }\n' % (cls, rule)
return (htmlstr, css, self.cssname)
def ensure_unit(self, raw, unit='px'):
if re.search(r'\d+$', raw) is not None:
raw += unit
return raw
# flatten possibly modified tag back to string
def taginfo_tostring(self, taginfo):
(ttype, tname, tattr) = taginfo
if ttype is None or tname is None:
return ''
if ttype == 'end':
return '</%s>' % tname
if ttype in SPECIAL_HANDLING_TYPES and tattr is not None and 'special' in tattr.keys():
info = tattr['special']
if ttype == 'comment':
return '<%s %s-->' % tname, info
else:
return '<%s %s>' % tname, info
res = []
res.append('<%s' % tname)
if tattr is not None:
for key in tattr.keys():
res.append(' %s="%s"' % (key, tattr[key]))
if ttype == 'single':
res.append('/>')
elif ttype == 'single_ext':
res.append(' />')
else :
res.append('>')
return "".join(res)
# routines to convert from mobi ml tags atributes to xhtml attributes and styles
def processtag(self, taginfo):
# Converting mobi font sizes to numerics
size_map = {
'xx-small': '1',
'x-small': '2',
'small': '3',
'medium': '4',
'large': '5',
'x-large': '6',
'xx-large': '7',
}
size_to_em_map = {
'1': '.65em',
'2': '.75em',
'3': '1em',
'4': '1.125em',
'5': '1.25em',
'6': '1.5em',
'7': '2em',
}
# current tag to work on
(ttype, tname, tattr) = taginfo
if not tattr:
tattr = {}
styles = []
if tname is None or tname.startswith('removeme'):
return ''
# have not seen an example of this yet so keep it here to be safe
# until this is better understood
if tname in ('country-region', 'place', 'placetype', 'placename',
'state', 'city', 'street', 'address', 'content'):
tname = 'div' if tname == 'content' else 'span'
for key in tattr.keys():
tattr.pop(key)
# handle general case of style, height, width, bgcolor in any tag
if 'style' in tattr.keys():
style = tattr.pop('style').strip()
if style:
styles.append(style)
if 'align' in tattr.keys():
align = tattr.pop('align').strip()
if align:
if tname in ('table', 'td', 'tr'):
pass
else:
styles.append('text-align: %s' % align)
if 'height' in tattr.keys():
height = tattr.pop('height').strip()
if height and '<' not in height and '>' not in height and re.search(r'\d+', height):
if tname in ('table', 'td', 'tr'):
pass
elif tname == 'img':
tattr['height'] = height
else:
styles.append('margin-top: %s' % self.ensure_unit(height))
if 'width' in tattr.keys():
width = tattr.pop('width').strip()
if width and re.search(r'\d+', width):
if tname in ('table', 'td', 'tr'):
pass
elif tname == 'img':
tattr['width'] = width
else:
styles.append('text-indent: %s' % self.ensure_unit(width))
if width.startswith('-'):
styles.append('margin-left: %s' % self.ensure_unit(width[1:]))
if 'bgcolor' in tattr.keys():
# no proprietary html allowed
if tname == 'div':
del tattr['bgcolor']
elif tname == 'font':
# Change font tags to span tags
tname = 'span'
if ttype in ('begin', 'single', 'single_ext'):
# move the face attribute to css font-family
if 'face' in tattr.keys():
face = tattr.pop('face').strip()
styles.append('font-family: "%s"' % face)
# Monitor the constantly changing font sizes, change them to ems and move
# them to css. The following will work for 'flat' font tags, but nested font tags
# will cause things to go wonky. Need to revert to the parent font tag's size
# when a closing tag is encountered.
if 'size' in tattr.keys():
sz = tattr.pop('size').strip().lower()
try:
float(sz)
except ValueError:
if sz in size_map.keys():
sz = size_map[sz]
else:
if sz.startswith('-') or sz.startswith('+'):
sz = self.current_font_size + float(sz)
if sz > 7:
sz = 7
elif sz < 1:
sz = 1
sz = str(int(sz))
styles.append('font-size: %s' % size_to_em_map[sz])
self.current_font_size = int(sz)
elif tname == 'img':
for attr in ('width', 'height'):
if attr in tattr:
val = tattr[attr]
if val.lower().endswith('em'):
try:
nval = float(val[:-2])
nval *= 16 * (168.451/72) # Assume this was set using the Kindle profile
tattr[attr] = "%dpx"%int(nval)
except:
del tattr[attr]
elif val.lower().endswith('%'):
del tattr[attr]
# convert the anchor tags
if 'filepos-id' in tattr:
tattr['id'] = tattr.pop('filepos-id')
if 'name' in tattr and tattr['name'] != tattr['id']:
tattr['name'] = tattr['id']
if 'filepos' in tattr:
filepos = tattr.pop('filepos')
try:
tattr['href'] = "#filepos%d" % int(filepos)
except ValueError:
pass
if styles:
ncls = None
rule = '; '.join(styles)
for sel, srule in self.tag_css_rules.items():
if srule == rule:
ncls = sel
break
if ncls is None:
self.tag_css_rule_cnt += 1
ncls = 'rule_%d' % self.tag_css_rule_cnt
self.tag_css_rules[ncls] = rule
cls = tattr.get('class', '')
cls = cls + (' ' if cls else '') + ncls
tattr['class'] = cls
# convert updated tag back to string representation
if len(tattr) == 0:
tattr = None
taginfo = (ttype, tname, tattr)
return self.taginfo_tostring(taginfo)
''' main only left in for testing outside of plugin '''
def main(argv=sys.argv):
if len(argv) != 2:
return 1
else:
infile = argv[1]
try:
print('Converting Mobi Markup Language to XHTML')
mlc = MobiMLConverter(infile)
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)
except ValueError as e:
print("Error: %s" % e)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

File diff suppressed because it is too large Load Diff

317
lector/annotations.py Normal file
View File

@@ -0,0 +1,317 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 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 logging
from PyQt5 import QtWidgets, QtCore, QtGui
from lector.resources import annotationswindow
logger = logging.getLogger(__name__)
class AnnotationsUI(QtWidgets.QDialog, annotationswindow.Ui_Dialog):
def __init__(self, parent=None):
super(AnnotationsUI, self).__init__()
self.setupUi(self)
self.parent = parent
self._translate = QtCore.QCoreApplication.translate
# Current annotation
self.modelIndex = None # The index of the annotations list model in the parent dialog
self.current_annotation = {}
# Populate annotation type
textmarkup_string = self._translate('AnnotationsUI', 'Text markup')
all_types = [textmarkup_string]
for i in all_types:
self.typeBox.addItem(i)
# Init defaults
self.default_stylesheet = self.foregroundCheck.styleSheet()
self.foregroundColor = QtGui.QColor.fromRgb(0, 0, 0)
self.underlineColor = QtGui.QColor.fromRgb(255, 0, 0)
self.highlightColor = QtGui.QColor.fromRgb(66, 209, 56)
self.underline_styles = {
'Solid': QtGui.QTextCharFormat.SingleUnderline,
'Dashes': QtGui.QTextCharFormat.DashUnderline,
'Dots': QtGui.QTextCharFormat.DotLine,
'Wavy': QtGui.QTextCharFormat.WaveUnderline}
# Push buttons
self.foregroundColorButton.clicked.connect(self.modify_annotation)
self.highlightColorButton.clicked.connect(self.modify_annotation)
self.underlineColorButton.clicked.connect(self.modify_annotation)
self.okButton.clicked.connect(self.ok_pressed)
self.cancelButton.clicked.connect(self.hide)
# Underline combo box
underline_items = ['Solid', 'Dashes', 'Dots', 'Wavy']
self.underlineType.addItems(underline_items)
self.underlineType.currentIndexChanged.connect(self.modify_annotation)
# Text markup related checkboxes
self.foregroundCheck.clicked.connect(self.modify_annotation)
self.highlightCheck.clicked.connect(self.modify_annotation)
self.boldCheck.clicked.connect(self.modify_annotation)
self.italicCheck.clicked.connect(self.modify_annotation)
self.underlineCheck.clicked.connect(self.modify_annotation)
def show_dialog(self, mode, index=None):
# TODO
# Account for annotation type here
# and point to a relevant set of widgets accordingly
if mode == 'edit' or mode == 'preview':
self.modelIndex = index
this_annotation = self.parent.annotationModel.data(
index, QtCore.Qt.UserRole)
annotation_name = this_annotation['name']
self.nameEdit.setText(annotation_name)
annotation_components = this_annotation['components']
if 'foregroundColor' in annotation_components:
self.foregroundCheck.setChecked(True)
self.foregroundColor = annotation_components['foregroundColor']
self.set_button_background_color(
self.foregroundColorButton, annotation_components['foregroundColor'])
else:
self.foregroundCheck.setChecked(False)
if 'highlightColor' in annotation_components:
self.highlightCheck.setChecked(True)
self.highlightColor = annotation_components['highlightColor']
self.set_button_background_color(
self.highlightColorButton, annotation_components['highlightColor'])
else:
self.highlightCheck.setChecked(False)
if 'bold' in annotation_components:
self.boldCheck.setChecked(True)
else:
self.boldCheck.setChecked(False)
if 'italic' in annotation_components:
self.italicCheck.setChecked(True)
else:
self.italicCheck.setChecked(False)
if 'underline' in annotation_components:
self.underlineCheck.setChecked(True)
underline_params = annotation_components['underline']
self.underlineType.setCurrentText(underline_params[0])
self.set_button_background_color(
self.underlineColorButton, underline_params[1])
else:
self.underlineCheck.setChecked(False)
elif mode == 'add':
new_annotation_string = self._translate('AnnotationsUI', 'New annotation')
self.nameEdit.setText(new_annotation_string)
all_checkboxes = (
self.foregroundCheck, self.highlightCheck,
self.boldCheck, self.italicCheck, self.underlineCheck)
for i in all_checkboxes:
i.setChecked(False)
self.modelIndex = None
self.set_button_background_color(
self.foregroundColorButton, self.foregroundColor)
self.set_button_background_color(
self.highlightColorButton, self.highlightColor)
self.set_button_background_color(
self.underlineColorButton, self.underlineColor)
self.update_preview()
if mode != 'preview':
self.show()
def set_button_background_color(self, button, color):
button.setStyleSheet(
"QPushButton {{background-color: {0}}}".format(color.name()))
def update_preview(self):
cursor = self.parent.previewView.textCursor()
cursor.setPosition(0)
cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor)
# TODO
# Other kinds of text markup
previewCharFormat = QtGui.QTextCharFormat()
if self.foregroundCheck.isChecked():
previewCharFormat.setForeground(self.foregroundColor)
highlight = QtCore.Qt.transparent
if self.highlightCheck.isChecked():
highlight = self.highlightColor
previewCharFormat.setBackground(highlight)
font_weight = QtGui.QFont.Normal
if self.boldCheck.isChecked():
font_weight = QtGui.QFont.Bold
previewCharFormat.setFontWeight(font_weight)
if self.italicCheck.isChecked():
previewCharFormat.setFontItalic(True)
if self.underlineCheck.isChecked():
previewCharFormat.setFontUnderline(True)
previewCharFormat.setUnderlineColor(self.underlineColor)
previewCharFormat.setUnderlineStyle(
self.underline_styles[self.underlineType.currentText()])
previewCharFormat.setFontStyleStrategy(
QtGui.QFont.PreferAntialias)
cursor.setCharFormat(previewCharFormat)
cursor.clearSelection()
self.parent.previewView.setTextCursor(cursor)
def modify_annotation(self):
sender = self.sender()
if isinstance(sender, QtWidgets.QCheckBox):
if not sender.isChecked():
self.update_preview()
return
new_color = None
if sender == self.foregroundColorButton:
new_color = self.get_color(self.foregroundColor)
self.foregroundColor = new_color
if sender == self.highlightColorButton:
new_color = self.get_color(self.highlightColor)
self.highlightColor = new_color
if sender == self.underlineColorButton:
new_color = self.get_color(self.underlineColor)
self.underlineColor = new_color
if new_color:
self.set_button_background_color(sender, new_color)
self.update_preview()
def get_color(self, current_color):
color_dialog = QtWidgets.QColorDialog()
new_color = color_dialog.getColor(current_color)
if new_color.isValid(): # Returned in case cancel is pressed
return new_color
else:
return current_color
def ok_pressed(self):
annotation_name = self.nameEdit.text()
if annotation_name == '':
self.nameEdit.setText('Why do you like bugs? WHY?')
return
annotation_components = {}
if self.foregroundCheck.isChecked():
annotation_components['foregroundColor'] = self.foregroundColor
if self.highlightCheck.isChecked():
annotation_components['highlightColor'] = self.highlightColor
if self.boldCheck.isChecked():
annotation_components['bold'] = True
if self.italicCheck.isChecked():
annotation_components['italic'] = True
if self.underlineCheck.isChecked():
annotation_components['underline'] = (
self.underlineType.currentText(), self.underlineColor)
self.current_annotation = {
'name': annotation_name,
'applicable_to': 'text',
'type': 'text_markup',
'components': annotation_components}
if self.modelIndex:
self.parent.annotationModel.setData(
self.modelIndex, annotation_name, QtCore.Qt.DisplayRole)
self.parent.annotationModel.setData(
self.modelIndex, self.current_annotation, QtCore.Qt.UserRole)
else: # New annotation
new_annotation_item = QtGui.QStandardItem()
new_annotation_item.setText(annotation_name)
new_annotation_item.setData(self.current_annotation, QtCore.Qt.UserRole)
self.parent.annotationModel.appendRow(new_annotation_item)
self.hide()
class AnnotationPlacement:
def __init__(self):
self.annotation_type = None
self.annotation_components = None
self.underline_styles = {
'Solid': QtGui.QTextCharFormat.SingleUnderline,
'Dashes': QtGui.QTextCharFormat.DashUnderline,
'Dots': QtGui.QTextCharFormat.DotLine,
'Wavy': QtGui.QTextCharFormat.WaveUnderline}
def set_current_annotation(self, annotation_type, annotation_components):
# Components expected to be a dictionary
self.annotation_type = annotation_type # This is currently unused
self.annotation_components = annotation_components
def format_text(self, cursor, start_here, end_here):
# This is applicable only to the PliantQTextBrowser
# for the text_markup style of annotation
# The cursor is the textCursor of the QTextEdit
# containing the text that has to be modified
if not self.annotation_components:
return
cursor.setPosition(start_here)
cursor.setPosition(end_here, QtGui.QTextCursor.KeepAnchor)
newCharFormat = QtGui.QTextCharFormat()
if 'foregroundColor' in self.annotation_components:
newCharFormat.setForeground(
self.annotation_components['foregroundColor'])
if 'highlightColor' in self.annotation_components:
newCharFormat.setBackground(
self.annotation_components['highlightColor'])
if 'bold' in self.annotation_components:
newCharFormat.setFontWeight(QtGui.QFont.Bold)
if 'italic' in self.annotation_components:
newCharFormat.setFontItalic(True)
if 'underline' in self.annotation_components:
newCharFormat.setFontUnderline(True)
newCharFormat.setUnderlineStyle(
self.underline_styles[self.annotation_components['underline'][0]])
newCharFormat.setUnderlineColor(
self.annotation_components['underline'][1])
newCharFormat.setFontStyleStrategy(
QtGui.QFont.PreferAntialias)
cursor.setCharFormat(newCharFormat)
cursor.clearSelection()
return cursor

1158
lector/contentwidgets.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -19,12 +17,15 @@
import os
import pickle
import sqlite3
import logging
from PyQt5 import QtCore
logger = logging.getLogger(__name__)
class DatabaseInit:
def __init__(self, location_prefix):
os.makedirs(location_prefix, exist_ok=True)
self.database_path = os.path.join(location_prefix, 'Lector.db')
self.books_table_columns = {
@@ -41,7 +42,8 @@ class DatabaseInit:
'LastAccessed': 'BLOB',
'Bookmarks': 'BLOB',
'CoverImage': 'BLOB',
'Addition': 'TEXT'}
'Addition': 'TEXT',
'Annotations': 'BLOB'}
self.directories_table_columns = {
'id': 'INTEGER PRIMARY KEY',
@@ -51,7 +53,7 @@ class DatabaseInit:
'CheckState': 'INTEGER'}
if os.path.exists(self.database_path):
self.check_database()
self.check_columns()
else:
self.create_database()
@@ -70,7 +72,7 @@ class DatabaseInit:
self.database.commit()
self.database.close()
def check_database(self):
def check_columns(self):
self.database = sqlite3.connect(self.database_path)
database_return = self.database.execute("PRAGMA table_info(books)").fetchall()
@@ -81,13 +83,13 @@ class DatabaseInit:
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]}')
info_string = f'Database: Adding column "{i[0]}"'
logger.info(info_string)
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:
@@ -208,8 +210,9 @@ class DatabaseFunctions:
else:
return None
except (KeyError, sqlite3.OperationalError):
print('SQLite is in wretched rebellion @ data fetching handling')
except Exception as e:
error_string = 'SQLite is in wretched rebellion @ data fetching handling'
logger.critical(error_string + f' {type(e).__name__} Arguments: {e.args}')
def fetch_covers_only(self, hash_list):
parameter_marks = ','.join(['?' for i in hash_list])
@@ -220,7 +223,7 @@ class DatabaseFunctions:
def modify_metadata(self, metadata_dict, book_hash):
def generate_binary(column, data):
if column in ('Position', 'LastAccessed', 'Bookmarks'):
if column in ('Position', 'LastAccessed', 'Bookmarks', 'Annotations'):
return sqlite3.Binary(pickle.dumps(data))
elif column == 'CoverImage':
return sqlite3.Binary(data)
@@ -241,8 +244,9 @@ class DatabaseFunctions:
try:
self.database.execute(
sql_command, update_data)
except sqlite3.OperationalError:
print('SQLite is in wretched rebellion @ metadata handling')
except sqlite3.OperationalError as e:
error_string = 'SQLite is in wretched rebellion @ metadata handling'
logger.critical(error_string + f' {type(e).__name__} Arguments: {e.args}')
self.database.commit()
self.database.close()

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -16,8 +14,22 @@
# 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 requests
from PyQt5 import QtWidgets, QtCore, QtGui, QtMultimedia
import json
import logging
import urllib.request
from PyQt5 import QtWidgets, QtCore, QtGui
logger = logging.getLogger(__name__)
try:
from PyQt5 import QtMultimedia
multimedia_available = True
except ImportError:
error_string = 'QtMultimedia not found. Sounds will not play.'
logger.error(error_string)
multimedia_available = False
from lector.resources import definitions
@@ -37,8 +49,12 @@ class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
radius = 15
path = QtGui.QPainterPath()
path.addRoundedRect(QtCore.QRectF(self.rect()), radius, radius)
mask = QtGui.QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
try:
mask = QtGui.QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
except TypeError: # Required for older versions of Qt
pass
self.definitionView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
@@ -51,25 +67,35 @@ class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
self.pronunciation_mp3 = None
self.okButton.clicked.connect(self.hide)
self.pronounceButton.clicked.connect(self.play_pronunciation)
self.dialogBackground.clicked.connect(self.color_background)
if multimedia_available:
self.pronounceButton.clicked.connect(self.play_pronunciation)
else:
self.pronounceButton.setEnabled(False)
def api_call(self, url, word):
language = self.parent.settings['dictionary_language']
url = url + language + '/' + word.lower()
r = requests.get(
url,
headers={'app_id': self.app_id, 'app_key': self.app_key})
req = urllib.request.Request(url)
req.add_header('app_id', self.app_id)
req.add_header('app_key', self.app_key)
if r.status_code != 200:
print('A firm nope on the dictionary finding thing')
try:
response = urllib.request.urlopen(req)
if response.getcode() == 200:
return_json = json.loads(response.read())
return return_json
except Exception as e:
this_error = f'API access error'
logger.exception(this_error + f' {type(e).__name__} Arguments: {e.args}')
self.parent.display_error_notification(None)
return None
return r.json()
def find_definition(self, word):
word_root_json = self.api_call(self.root_url, word)
if not word_root_json:
logger.error('Word root json noped out: ' + word)
self.set_text(word, None, None, True)
return
@@ -78,6 +104,8 @@ class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
definition_json = self.api_call(self.define_url, word_root)
if not definition_json:
logger.error('Definition json noped out: ' + word_root)
self.set_text(word, None, None, True)
return
definitions = {}
@@ -95,7 +123,7 @@ class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
this_definition = j['definitions'][0].capitalize()
except KeyError:
# The API also reports crossReferenceMarkers here
pass
this_definition = '<Not found>'
try:
definitions[category].add(this_definition)
@@ -137,18 +165,30 @@ class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog):
background = self.parent.settings['dialog_background']
else:
self.previous_position = self.pos()
background = self.parent.get_color()
self.parent.get_color()
background = self.parent.settings['dialog_background']
# Calculate inverse color for the background so that
# the text doesn't look blank
r, g, b, alpha = background.getRgb()
inv_average = 255 - (r + g + b) // 3
if 100 < inv_average < 150:
inv_average = 255
foreground = QtGui.QColor(
inv_average, inv_average, inv_average, alpha)
self.setStyleSheet(
"QDialog {{background-color: {0}}}".format(background.name()))
self.definitionView.setStyleSheet(
"QTextBrowser {{background-color: {0}}}".format(background.name()))
"QTextBrowser {{color:{0}; background-color: {1}}}".format(
foreground.name(), background.name()))
if not set_initial:
self.show()
def play_pronunciation(self):
if not self.pronunciation_mp3:
if not self.pronunciation_mp3 or not multimedia_available:
return
media_content = QtMultimedia.QMediaContent(

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -16,9 +14,14 @@
# 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 logging
from PyQt5 import QtWidgets, QtGui, QtCore
from lector.resources import pie_chart
logger = logging.getLogger(__name__)
class LibraryDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, temp_dir, parent=None):
@@ -34,11 +37,7 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate):
option = option.__class__(option)
file_exists = index.data(QtCore.Qt.UserRole + 5)
metadata = index.data(QtCore.Qt.UserRole + 3)
position = metadata['position']
if position:
is_read = position['is_read']
position_percent = index.data(QtCore.Qt.UserRole + 7)
# The shadow pixmap currently is set to 420 x 600
# Only draw the cover shadow in case the setting is enabled
@@ -55,55 +54,20 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate):
if not file_exists:
painter.setOpacity(.7)
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
read_icon = pie_chart.pixmapper(-1, None, None, 36)
painter.setOpacity(1)
read_icon = pie_chart.pixmapper(
-1, None, self.parent.settings['consider_read_at'], 36)
x_draw = option.rect.bottomRight().x() - 30
y_draw = option.rect.bottomRight().y() - 35
painter.drawPixmap(x_draw, y_draw, read_icon)
painter.setOpacity(1)
return
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
if position:
if is_read:
current_chapter = total_chapters = 100
else:
try:
current_chapter = position['current_chapter']
total_chapters = position['total_chapters']
except KeyError:
return
if position_percent:
read_icon = pie_chart.pixmapper(
current_chapter, total_chapters, self.temp_dir, 36)
position_percent, self.temp_dir, self.parent.settings['consider_read_at'], 36)
x_draw = option.rect.bottomRight().x() - 30
y_draw = option.rect.bottomRight().y() - 35
if current_chapter != 1:
painter.drawPixmap(x_draw, y_draw, read_icon)
class BookmarkDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(BookmarkDelegate, self).__init__(parent)
self.parent = parent
def sizeHint(self, *args):
dockwidget_width = self.parent.width() - 20
return QtCore.QSize(dockwidget_width, 50)
def paint(self, painter, option, index):
# TODO
# Alignment of the painted item
option = option.__class__(option)
chapter_index = index.data(QtCore.Qt.UserRole)
chapter_name = self.parent.window().bookToolBar.tocBox.itemText(chapter_index - 1)
if len(chapter_name) > 25:
chapter_name = chapter_name[:25] + '...'
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
painter.drawText(
option.rect,
QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight | QtCore.Qt.TextWordWrap,
' ' + chapter_name)
painter.drawPixmap(x_draw, y_draw, read_icon)

661
lector/dockwidgets.py Normal file
View File

@@ -0,0 +1,661 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 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 uuid
from PyQt5 import QtWidgets, QtGui, QtCore
from lector.models import BookmarkProxyModel
from lector.threaded import BackGroundTextSearch
class PliantDockWidget(QtWidgets.QDockWidget):
def __init__(self, main_window, notes_only, contentView, parent=None):
super(PliantDockWidget, self).__init__(parent)
self.main_window = main_window
self.notes_only = notes_only
self.contentView = contentView
self.current_annotation = None
self.parent = parent
# Models
# The following models belong to the sideDock
# bookmarkModel, bookmarkProxyModel
# annotationModel
# searchResultsModel
self.bookmarkModel = None
self.bookmarkProxyModel = None
self.annotationModel = None
self.searchResultsModel = None
# References
# All widgets belong to these
self.bookmarks = None
self.annotations = None
self.search = None
# Widgets
# Except this one
self.sideDockTabWidget = None
# Animate appearance
self.animation = QtCore.QPropertyAnimation(self, b'windowOpacity')
self.animation.setStartValue(0)
self.animation.setEndValue(1)
self.animation.setDuration(200)
def showEvent(self, event=None):
viewport_topRight = self.contentView.mapToGlobal(
self.contentView.viewport().rect().topRight())
desktop_size = QtWidgets.QDesktopWidget().screenGeometry()
dock_y = viewport_topRight.y()
dock_height = self.contentView.viewport().size().height()
if self.notes_only:
dock_width = dock_height = desktop_size.width() // 5.5
dock_x = QtGui.QCursor.pos().x()
dock_y = QtGui.QCursor.pos().y()
else:
dock_width = desktop_size.width() // 5
dock_x = viewport_topRight.x() - dock_width + 1
self.parent.navBar.hide()
self.main_window.active_docks.append(self)
self.setGeometry(dock_x, dock_y, dock_width, dock_height)
self.animation.start()
def hideEvent(self, event=None):
if self.notes_only:
annotationNoteEdit = self.findChild(QtWidgets.QTextEdit)
if self.current_annotation:
self.current_annotation['note'] = annotationNoteEdit.toPlainText()
try:
self.main_window.active_docks.remove(self)
except ValueError:
pass
def set_annotation(self, annotation):
self.current_annotation = annotation
def populate(self):
self.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable)
self.setTitleBarWidget(QtWidgets.QWidget(self)) # Removes titlebar
self.sideDockTabWidget = QtWidgets.QTabWidget(self)
self.setWidget(self.sideDockTabWidget)
# This order is important
self.bookmarkModel = QtGui.QStandardItemModel(self)
self.bookmarkProxyModel = BookmarkProxyModel(self)
self.bookmarks = Bookmarks(self)
self.bookmarks.generate_bookmark_model()
if not self.parent.are_we_doing_images_only:
self.annotationModel = QtGui.QStandardItemModel(self)
self.annotations = Annotations(self)
self.annotations.generate_annotation_model()
self.searchResultsModel = QtGui.QStandardItemModel(self)
self.search = Search(self)
def closeEvent(self, event):
self.hide()
# Ignoring this event prevents application closure
# when everything is fullscreened
event.ignore()
# For the following classes, the parent is the sideDock
# The parentTab is the parent... tab. So self.parent.parent
class Bookmarks:
def __init__(self, parent):
self.parent = parent
self.parentTab = self.parent.parent
self.bookmarkTreeView = QtWidgets.QTreeView(self.parent)
self._translate = QtCore.QCoreApplication.translate
self.bookmarks_string = self._translate('SideDock', 'Bookmarks')
self.bookmark_default = self._translate('SideDock', 'New bookmark')
self.create_widgets()
def create_widgets(self):
self.bookmarkTreeView.setHeaderHidden(True)
self.bookmarkTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.bookmarkTreeView.customContextMenuRequested.connect(
self.generate_bookmark_context_menu)
self.bookmarkTreeView.clicked.connect(self.navigate_to_bookmark)
# Add widget to side dock
self.parent.sideDockTabWidget.addTab(
self.bookmarkTreeView, self.bookmarks_string)
def add_bookmark(self, position=None):
identifier = uuid.uuid4().hex[:10]
if self.parentTab.are_we_doing_images_only:
chapter = self.parentTab.metadata['position']['current_chapter']
cursor_position = 0
else:
chapter, cursor_position = self.parent.contentView.record_position(True)
if position: # Should be the case when called from the context menu
cursor_position = position
self.parentTab.metadata['bookmarks'][identifier] = {
'chapter': chapter,
'cursor_position': cursor_position,
'description': self.bookmark_default}
self.parent.setVisible(True)
self.parent.sideDockTabWidget.setCurrentIndex(0)
self.add_bookmark_to_model(
self.bookmark_default, chapter, cursor_position, identifier, True)
def add_bookmark_to_model(
self, description, chapter_number, cursor_position,
identifier, new_bookmark=False):
def edit_new_bookmark(parent_item):
new_child = parent_item.child(parent_item.rowCount() - 1, 0)
source_index = self.parent.bookmarkModel.indexFromItem(new_child)
edit_index = self.bookmarkTreeView.model().mapFromSource(source_index)
self.parent.activateWindow()
self.bookmarkTreeView.setFocus()
self.bookmarkTreeView.setCurrentIndex(edit_index)
self.bookmarkTreeView.edit(edit_index)
def get_chapter_name(chapter_number):
for i in reversed(self.parentTab.metadata['toc']):
if i[2] <= chapter_number:
return i[1]
return 'Unknown'
bookmark = QtGui.QStandardItem()
bookmark.setData(False, QtCore.Qt.UserRole + 10) # Is Parent
bookmark.setData(chapter_number, QtCore.Qt.UserRole) # Chapter number
bookmark.setData(cursor_position, QtCore.Qt.UserRole + 1) # Cursor Position
bookmark.setData(identifier, QtCore.Qt.UserRole + 2) # Identifier
bookmark.setData(description, QtCore.Qt.DisplayRole) # Description
bookmark_chapter_name = get_chapter_name(chapter_number)
for i in range(self.parent.bookmarkModel.rowCount()):
parentIndex = self.parent.bookmarkModel.index(i, 0)
parent_chapter_number = parentIndex.data(QtCore.Qt.UserRole)
parent_chapter_name = parentIndex.data(QtCore.Qt.DisplayRole)
# This prevents duplication of the bookmark in the new
# navigation model
if ((parent_chapter_number <= chapter_number) and
(parent_chapter_name == bookmark_chapter_name)):
bookmarkParent = self.parent.bookmarkModel.itemFromIndex(parentIndex)
bookmarkParent.appendRow(bookmark)
if new_bookmark:
edit_new_bookmark(bookmarkParent)
return
# In case no parent item exists
bookmarkParent = QtGui.QStandardItem()
bookmarkParent.setData(True, QtCore.Qt.UserRole + 10) # Is Parent
bookmarkParent.setFlags(bookmarkParent.flags() & ~QtCore.Qt.ItemIsEditable) # Is Editable
bookmarkParent.setData(get_chapter_name(chapter_number), QtCore.Qt.DisplayRole)
bookmarkParent.setData(chapter_number, QtCore.Qt.UserRole)
bookmarkParent.appendRow(bookmark)
self.parent.bookmarkModel.appendRow(bookmarkParent)
if new_bookmark:
edit_new_bookmark(bookmarkParent)
def navigate_to_bookmark(self, index):
if not index.isValid():
return
is_parent = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole + 10)
if is_parent:
chapter_number = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole)
self.parentTab.set_content(chapter_number, True, True)
return
chapter = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole)
cursor_position = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole + 1)
self.parentTab.set_content(chapter, True, True)
if not self.parentTab.are_we_doing_images_only:
self.parentTab.set_cursor_position(cursor_position)
def generate_bookmark_model(self):
for i in self.parentTab.metadata['bookmarks'].items():
description = i[1]['description']
chapter = i[1]['chapter']
cursor_position = i[1]['cursor_position']
identifier = i[0]
self.add_bookmark_to_model(
description, chapter, cursor_position, identifier)
self.generate_bookmark_proxy_model()
def generate_bookmark_proxy_model(self):
self.parent.bookmarkProxyModel.setSourceModel(self.parent.bookmarkModel)
self.parent.bookmarkProxyModel.setSortCaseSensitivity(False)
self.parent.bookmarkProxyModel.setSortRole(QtCore.Qt.UserRole)
self.parent.bookmarkProxyModel.sort(0)
self.bookmarkTreeView.setModel(self.parent.bookmarkProxyModel)
def generate_bookmark_context_menu(self, position):
index = self.bookmarkTreeView.indexAt(position)
if not index.isValid():
return
is_parent = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole + 10)
if is_parent:
return
bookmarkMenu = QtWidgets.QMenu()
editAction = bookmarkMenu.addAction(
self.parentTab.main_window.QImageFactory.get_image('edit-rename'),
self._translate('Tab', 'Edit'))
deleteAction = bookmarkMenu.addAction(
self.parentTab.main_window.QImageFactory.get_image('trash-empty'),
self._translate('Tab', 'Delete'))
action = bookmarkMenu.exec_(
self.bookmarkTreeView.mapToGlobal(position))
if action == editAction:
self.bookmarkTreeView.edit(index)
if action == deleteAction:
child_index = self.parent.bookmarkProxyModel.mapToSource(index)
parent_index = child_index.parent()
child_rows = self.parent.bookmarkModel.itemFromIndex(
parent_index).rowCount()
delete_uuid = self.parent.bookmarkModel.data(
child_index, QtCore.Qt.UserRole + 2)
self.parentTab.metadata['bookmarks'].pop(delete_uuid)
self.parent.bookmarkModel.removeRow(
child_index.row(), child_index.parent())
if child_rows == 1:
self.parent.bookmarkModel.removeRow(parent_index.row())
class Annotations:
def __init__(self, parent):
self.parent = parent
self.parentTab = self.parent.parent
self.annotationListView = QtWidgets.QListView(self.parent)
self._translate = QtCore.QCoreApplication.translate
self.annotations_string = self._translate('SideDock', 'Annotations')
self.create_widgets()
def create_widgets(self):
self.annotationListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers)
self.annotationListView.doubleClicked.connect(
self.parent.contentView.toggle_annotation_mode)
# Add widget to side dock
self.parent.sideDockTabWidget.addTab(
self.annotationListView, self.annotations_string)
def generate_annotation_model(self):
# TODO
# Annotation previews will require creation of a
# QStyledItemDelegate
saved_annotations = self.parent.main_window.settings['annotations']
if not saved_annotations:
return
# Create annotation model
for i in saved_annotations:
item = QtGui.QStandardItem()
item.setText(i['name'])
item.setData(i, QtCore.Qt.UserRole)
self.parent.annotationModel.appendRow(item)
self.annotationListView.setModel(self.parent.annotationModel)
class Search:
def __init__(self, parent):
self.parent = parent
self.parentTab = self.parent.parent
self.searchThread = BackGroundTextSearch()
self.searchOptionsLayout = QtWidgets.QHBoxLayout()
self.searchTabLayout = QtWidgets.QVBoxLayout()
self.searchTimer = QtCore.QTimer(self.parent)
self.searchLineEdit = QtWidgets.QLineEdit(self.parent)
self.searchBookButton = QtWidgets.QToolButton(self.parent)
self.caseSensitiveSearchButton = QtWidgets.QToolButton(self.parent)
self.matchWholeWordButton = QtWidgets.QToolButton(self.parent)
self.searchResultsTreeView = QtWidgets.QTreeView(self.parent)
self._translate = QtCore.QCoreApplication.translate
self.search_string = self._translate('SideDock', 'Search')
self.search_book_string = self._translate('SideDock', 'Search entire book')
self.case_sensitive_string = self._translate('SideDock', 'Match case')
self.match_word_string = self._translate('SideDock', 'Match word')
self.create_widgets()
def create_widgets(self):
self.searchThread.finished.connect(self.generate_search_result_model)
self.searchTimer.setSingleShot(True)
self.searchTimer.timeout.connect(self.set_search_options)
self.searchLineEdit.textChanged.connect(
lambda: self.searchLineEdit.setStyleSheet(
QtWidgets.QLineEdit.styleSheet(self.parent)))
self.searchLineEdit.textChanged.connect(
lambda: self.searchTimer.start(500))
self.searchBookButton.clicked.connect(
lambda: self.searchTimer.start(100))
self.caseSensitiveSearchButton.clicked.connect(
lambda: self.searchTimer.start(100))
self.matchWholeWordButton.clicked.connect(
lambda: self.searchTimer.start(100))
self.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus)
self.searchLineEdit.setClearButtonEnabled(True)
self.searchLineEdit.setPlaceholderText(self.search_string)
self.searchBookButton.setIcon(
self.parent.main_window.QImageFactory.get_image('view-readermode'))
self.searchBookButton.setToolTip(self.search_book_string)
self.searchBookButton.setCheckable(True)
self.searchBookButton.setAutoRaise(True)
self.searchBookButton.setIconSize(QtCore.QSize(20, 20))
self.caseSensitiveSearchButton.setIcon(
self.parent.main_window.QImageFactory.get_image('search-case'))
self.caseSensitiveSearchButton.setToolTip(self.case_sensitive_string)
self.caseSensitiveSearchButton.setCheckable(True)
self.caseSensitiveSearchButton.setAutoRaise(True)
self.caseSensitiveSearchButton.setIconSize(QtCore.QSize(20, 20))
self.matchWholeWordButton.setIcon(
self.parent.main_window.QImageFactory.get_image('search-word'))
self.matchWholeWordButton.setToolTip(self.match_word_string)
self.matchWholeWordButton.setCheckable(True)
self.matchWholeWordButton.setAutoRaise(True)
self.matchWholeWordButton.setIconSize(QtCore.QSize(20, 20))
self.searchOptionsLayout.setContentsMargins(0, 3, 0, 0)
self.searchOptionsLayout.addWidget(self.searchLineEdit)
self.searchOptionsLayout.addWidget(self.searchBookButton)
self.searchOptionsLayout.addWidget(self.caseSensitiveSearchButton)
self.searchOptionsLayout.addWidget(self.matchWholeWordButton)
self.searchResultsTreeView.setHeaderHidden(True)
self.searchResultsTreeView.setEditTriggers(
QtWidgets.QTreeView.NoEditTriggers)
self.searchResultsTreeView.clicked.connect(
self.navigate_to_search_result)
self.searchTabLayout.addLayout(self.searchOptionsLayout)
self.searchTabLayout.addWidget(self.searchResultsTreeView)
self.searchTabLayout.setContentsMargins(0, 0, 0, 0)
self.searchTabWidget = QtWidgets.QWidget(self.parent)
self.searchTabWidget.setLayout(self.searchTabLayout)
# Add widget to side dock
self.parent.sideDockTabWidget.addTab(
self.searchTabWidget, self.search_string)
def set_search_options(self):
def generate_title_content_pair(required_chapters):
title_content_list = []
for i in self.parentTab.metadata['toc']:
if i[2] in required_chapters:
title_content_list.append(
(i[1], self.parentTab.metadata['content'][i[2] - 1], i[2]))
return title_content_list
# Select either the current chapter or all chapters
# Function name is descriptive
chapter_numbers = (self.parentTab.metadata['position']['current_chapter'],)
if self.searchBookButton.isChecked():
chapter_numbers = [i + 1 for i in range(len(self.parentTab.metadata['content']))]
search_content = generate_title_content_pair(chapter_numbers)
self.searchThread.set_search_options(
search_content,
self.searchLineEdit.text(),
self.caseSensitiveSearchButton.isChecked(),
self.matchWholeWordButton.isChecked())
self.searchThread.start()
def generate_search_result_model(self):
self.parent.searchResultsModel.clear()
search_results = self.searchThread.search_results
for i in search_results:
parentItem = QtGui.QStandardItem()
parentItem.setData(True, QtCore.Qt.UserRole) # Is parent?
parentItem.setData(i, QtCore.Qt.UserRole + 3) # Display text for label
for j in search_results[i]:
childItem = QtGui.QStandardItem(parentItem)
childItem.setData(False, QtCore.Qt.UserRole) # Is parent?
childItem.setData(j[3], QtCore.Qt.UserRole + 1) # Chapter index
childItem.setData(j[0], QtCore.Qt.UserRole + 2) # Cursor Position
childItem.setData(j[1], QtCore.Qt.UserRole + 3) # Display text for label
childItem.setData(j[2], QtCore.Qt.UserRole + 4) # Search term
parentItem.appendRow(childItem)
self.parent.searchResultsModel.appendRow(parentItem)
self.searchResultsTreeView.setModel(self.parent.searchResultsModel)
self.searchResultsTreeView.expandToDepth(1)
# Reset stylesheet in case something is found
if search_results:
self.searchLineEdit.setStyleSheet(
QtWidgets.QLineEdit.styleSheet(self.parent))
# Or set to Red in case nothing is found
if not search_results and len(self.searchLineEdit.text()) > 2:
self.searchLineEdit.setStyleSheet("QLineEdit {color: red;}")
# We'll be putting in labels instead of making a delegate
# QLabels can understand RTF, and they also have the somewhat
# distinct advantage of being a lot less work than a delegate
def generate_label(index):
label_text = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole + 3)
labelWidget = PliantLabelWidget(index, self.navigate_to_search_result)
labelWidget.setText(label_text)
self.searchResultsTreeView.setIndexWidget(index, labelWidget)
for parent_iter in range(self.parent.searchResultsModel.rowCount()):
parentItem = self.parent.searchResultsModel.item(parent_iter)
parentIndex = self.parent.searchResultsModel.index(parent_iter, 0)
generate_label(parentIndex)
for child_iter in range(parentItem.rowCount()):
childIndex = self.parent.searchResultsModel.index(child_iter, 0, parentIndex)
generate_label(childIndex)
def navigate_to_search_result(self, index):
if not index.isValid():
return
is_parent = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole)
if is_parent:
return
chapter_number = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole + 1)
cursor_position = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole + 2)
search_term = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole + 4)
self.parentTab.set_content(chapter_number, True, True)
if not self.parentTab.are_we_doing_images_only:
self.parentTab.set_cursor_position(
cursor_position, len(search_term))
class PliantLabelWidget(QtWidgets.QLabel):
# This is a hack to get clickable / editable appearance
# search results in the tree view.
def __init__(self, index, navigate_to_search_result):
super(PliantLabelWidget, self).__init__()
self.index = index
self.navigate_to_search_result = navigate_to_search_result
def mousePressEvent(self, QMouseEvent):
self.navigate_to_search_result(self.index)
QtWidgets.QLabel.mousePressEvent(self, QMouseEvent)
class PliantNavBarWidget(QtWidgets.QDockWidget):
def __init__(self, main_window, contentView, parent):
super(PliantNavBarWidget, self).__init__(parent)
self.main_window = main_window
self.contentView = contentView
self.parent = parent
self.setWindowTitle('Navigation')
# Animate appearance
self.animation = QtCore.QPropertyAnimation(self, b'windowOpacity')
self.animation.setDuration(200)
self.animation.setStartValue(0)
self.animation.setEndValue(.8)
background = self.main_window.settings['dialog_background']
self.setStyleSheet(
"QDockWidget {{background-color: {0}}}".format(background.name()))
self.backButton = QtWidgets.QPushButton()
self.backButton.setFlat(True)
icon = QtGui.QIcon()
icon.addPixmap(
QtGui.QPixmap(":/images/previous.png"),
QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.backButton.setIcon(icon)
self.backButton.setIconSize(QtCore.QSize(24, 24))
self.nextButton = QtWidgets.QPushButton()
self.nextButton.setFlat(True)
icon = QtGui.QIcon()
icon.addPixmap(
QtGui.QPixmap(":/images/next.png"),
QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.nextButton.setIcon(icon)
self.nextButton.setIconSize(QtCore.QSize(24, 24))
self.backButton.clicked.connect(lambda: self.button_click(-1))
self.nextButton.clicked.connect(lambda: self.button_click(1))
self.tocComboBox = FixedComboBox(self)
self.populate_combo_box()
self.navLayout = QtWidgets.QHBoxLayout()
self.navLayout.addWidget(self.backButton)
self.navLayout.addWidget(self.tocComboBox)
self.navLayout.addWidget(self.nextButton)
self.navWidget = QtWidgets.QWidget()
self.navWidget.setLayout(self.navLayout)
self.setWidget(self.navWidget)
def showEvent(self, event=None):
# TODO
# See what happens when the size of the viewport is smaller
# than the size of the dock
viewport_bottomRight = self.contentView.mapToGlobal(
self.contentView.viewport().rect().bottomRight())
# Dock dimensions
desktop_size = QtWidgets.QDesktopWidget().screenGeometry()
dock_width = desktop_size.width() // 4.5
dock_height = 30
dock_x = viewport_bottomRight.x() - dock_width - 30
dock_y = viewport_bottomRight.y() - 70
self.main_window.active_docks.append(self)
self.setGeometry(dock_x, dock_y, dock_width, dock_height)
# Rounded
radius = 20
path = QtGui.QPainterPath()
path.addRoundedRect(QtCore.QRectF(self.rect()), radius, radius)
try:
mask = QtGui.QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
except TypeError: # Required for older versions of Qt
pass
self.animation.start()
def populate_combo_box(self):
def set_toc_position(tocTree):
currentIndex = tocTree.currentIndex()
required_position = currentIndex.data(QtCore.Qt.UserRole)
self.return_focus()
self.parent.set_content(required_position, True, True)
# Create the Combobox / Treeview combination
tocTree = QtWidgets.QTreeView()
self.tocComboBox.setView(tocTree)
self.tocComboBox.setModel(self.parent.tocModel)
tocTree.setRootIsDecorated(False)
tocTree.setItemsExpandable(False)
tocTree.expandAll()
# Set the position of the QComboBox
self.parent.set_tocBox_index(None, self.tocComboBox)
# Make clicking do something
self.tocComboBox.currentIndexChanged.connect(
lambda: set_toc_position(tocTree))
def button_click(self, change):
self.contentView.common_functions.change_chapter(change)
self.return_focus()
def return_focus(self):
# The NavBar needs to be hidden after clicking
self.parent.activateWindow()
self.parent.contentView.setFocus()
self.parent.mouseHideTimer.start()
class FixedComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None):
super(FixedComboBox, self).__init__(parent)
screen_width = QtWidgets.QDesktopWidget().screenGeometry().width()
self.adjusted_size = screen_width // 6
def sizeHint(self):
return self.minimumSizeHint()
def minimumSizeHint(self):
return QtCore.QSize(self.adjusted_size, 32)
def wheelEvent(self, QWheelEvent):
# Disable mouse wheel scrolling in the ComboBox
return

View File

@@ -1,324 +0,0 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import zipfile
from urllib.parse import unquote
from bs4 import BeautifulSoup
class EPUB:
def __init__(self, filename):
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
# case the module cannot process the file
self.load_zip()
contents_path = self.get_file_path(
None, True)
if not contents_path:
return False # No (valid) opf was found so processing cannot continue
self.generate_book_metadata(contents_path)
self.parse_toc()
return True
def load_zip(self):
try:
self.zip_file = zipfile.ZipFile(
self.filename, mode='r', allowZip64=True)
except (KeyError, AttributeError, zipfile.BadZipFile):
print('Cannot parse ' + self.filename)
return
def parse_xml(self, filename, parser):
try:
this_xml = self.zip_file.read(filename).decode()
except KeyError:
short_filename = os.path.basename(self.filename)
print(f'{str(filename)} not found in {short_filename}')
return
root = BeautifulSoup(this_xml, parser)
return root
def get_file_path(self, filename, is_content_file=False):
# Use this to get the location of the content.opf file
# And maybe some other file that has a more well formatted
# We're going to all this trouble because there really is
# no going forward without a toc
if is_content_file:
container_location = self.get_file_path('container.xml')
xml = self.parse_xml(container_location, 'xml')
if xml:
root_item = xml.find('rootfile')
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):
return i.filename
return None
def read_from_zip(self, filename):
filename = unquote(filename)
try:
file_data = self.zip_file.read(filename)
return file_data
except KeyError:
file_path_actual = self.get_file_path(filename)
if file_path_actual:
return self.zip_file.read(file_path_actual)
else:
print('ePub module can\'t find ' + filename)
#______________________________________________________
def generate_book_metadata(self, contents_path):
self.book['title'] = os.path.splitext(
os.path.basename(self.filename))[0]
self.book['author'] = 'Unknown'
self.book['isbn'] = None
self.book['tags'] = None
self.book['cover'] = None
self.book['toc_file'] = 'toc.ncx' # Overwritten if another one exists
# Parse XML
xml = self.parse_xml(contents_path, 'xml')
# Parse metadata
item_dict = {
'title': 'title',
'author': 'creator',
'year': 'date'}
for i in item_dict.items():
item = xml.find(i[1])
if item:
self.book[i[0]] = item.text
try:
self.book['year'] = int(self.book['year'][:4])
except (TypeError, KeyError, IndexError, ValueError):
self.book['year'] = 9999
# Get identifier
identifier_items = xml.find_all('identifier')
for i in identifier_items:
scheme = i.get('scheme')
try:
if scheme.lower() == 'isbn':
self.book['isbn'] = i.text
except AttributeError:
self.book['isbn'] = None
# Tags
tag_items = xml.find_all('subject')
tag_list = [i.text for i in tag_items]
self.book['tags'] = tag_list
# Get items
self.book['content_dict'] = {}
all_items = xml.find_all('item')
for i in all_items:
media_type = i.get('media-type')
this_id = i.get('id')
if media_type == 'application/xhtml+xml' or media_type == 'text/html':
self.book['content_dict'][this_id] = i.get('href')
if media_type == 'application/x-dtbncx+xml':
self.book['toc_file'] = i.get('href')
# Cover image
if 'cover' in this_id and media_type.split('/')[0] == 'image':
cover_href = i.get('href')
try:
self.book['cover'] = self.zip_file.read(cover_href)
except KeyError:
# The cover cannot be found according to the
# path specified in the content reference
self.book['cover'] = self.zip_file.read(
self.get_file_path(cover_href))
if not self.book['cover']:
# 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
for j in self.zip_file.filelist:
if os.path.splitext(j.filename)[1] in ['.jpg', '.jpeg', '.png', '.gif']:
if j.file_size > biggest_image_size:
biggest_image = j.filename
biggest_image_size = j.file_size
if biggest_image:
self.book['cover'] = self.read_from_zip(biggest_image)
else:
print('No cover found for: ' + self.filename)
# Parse spine and arrange chapter paths acquired from the opf
# according to the order IN THE SPINE
spine_items = xml.find_all('itemref')
spine_order = []
for i in spine_items:
spine_order.append(i.get('idref'))
self.book['chapters_in_order'] = []
for i in spine_order:
chapter_path = self.book['content_dict'][i]
self.book['chapters_in_order'].append(chapter_path)
def parse_toc(self):
# This has no bearing on the actual order
# We're just using this to get chapter names
self.book['navpoint_dict'] = {}
toc_file = self.book['toc_file']
if toc_file:
toc_file = self.get_file_path(toc_file)
xml = self.parse_xml(toc_file, 'xml')
if not xml:
return
navpoints = xml.find_all('navPoint')
for i in navpoints:
chapter_title = i.find('text').text
chapter_source = i.find('content').get('src')
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 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)
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 = []
pagebreaks = markup.find_all('pagebreak')
def next_element(elem):
while elem is not None:
elem = elem.next_sibling
if hasattr(elem, 'name'):
return elem
for pbreak in pagebreaks:
chapter = [str(pbreak)]
elem = next_element(pbreak)
while elem and elem.name != 'pagebreak':
chapter.append(str(elem))
elem = next_element(elem)
chapters.append('\n'.join(chapter))
for this_chapter in chapters:
fallback_title = str(no_title_chapter)
self.book['book_list'].append(
(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
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'])
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('"&gt;', '', 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('"&gt;', '', 1) + ('<br/>' * 8)
return_list.append(
(chapter_titles[-1], bs_obj_string))
return return_list

View File

@@ -1,7 +1,5 @@
#!usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2018 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -16,9 +14,16 @@
# 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
import logging
from PyQt5 import QtCore, QtGui, QtWidgets
from lector import database
from lector.settings import Settings
from lector.resources import resources
logger = logging.getLogger(__name__)
class QImageFactory:
def __init__(self, parent):
@@ -30,3 +35,317 @@ class QImageFactory:
this_qicon = QtGui.QIcon(icon_path)
return this_qicon
# For nearly all cases below, code remains unchanged from its
# state in the __main__ module. References to objects have been
# made in the respective __init__ functions of the classes here
class CoverLoadingAndCulling:
def __init__(self, main_window):
self.main_window = main_window
self.lib_ref = self.main_window.lib_ref
self.listView = self.main_window.listView
self.database_path = self.main_window.database_path
def cull_covers(self, event=None):
blank_pixmap = QtGui.QPixmap()
blank_pixmap.load(':/images/blank.png') # Keep this. Removing it causes the
# listView to go blank on a resize
all_indexes = set()
for i in range(self.lib_ref.itemProxyModel.rowCount()):
all_indexes.add(self.lib_ref.itemProxyModel.index(i, 0))
y_range = list(range(0, self.listView.viewport().height(), 100))
y_range.extend((-20, self.listView.viewport().height() + 20))
x_range = range(0, self.listView.viewport().width(), 80)
visible_indexes = set()
for i in y_range:
for j in x_range:
this_index = self.listView.indexAt(QtCore.QPoint(j, i))
visible_indexes.add(this_index)
invisible_indexes = all_indexes - visible_indexes
for i in invisible_indexes:
model_index = self.lib_ref.itemProxyModel.mapToSource(i)
this_item = self.lib_ref.libraryModel.item(model_index.row())
if this_item:
this_item.setIcon(QtGui.QIcon(blank_pixmap))
this_item.setData(False, QtCore.Qt.UserRole + 8)
hash_index_dict = {}
hash_list = []
for i in visible_indexes:
model_index = self.lib_ref.itemProxyModel.mapToSource(i)
book_hash = self.lib_ref.libraryModel.data(
model_index, QtCore.Qt.UserRole + 6)
cover_displayed = self.lib_ref.libraryModel.data(
model_index, QtCore.Qt.UserRole + 8)
if book_hash and not cover_displayed:
hash_list.append(book_hash)
hash_index_dict[book_hash] = model_index
all_covers = database.DatabaseFunctions(
self.database_path).fetch_covers_only(hash_list)
for i in all_covers:
book_hash = i[0]
cover = i[1]
model_index = hash_index_dict[book_hash]
book_item = self.lib_ref.libraryModel.item(model_index.row())
self.cover_loader(book_item, cover)
def load_all_covers(self):
all_covers_db = database.DatabaseFunctions(
self.database_path).fetch_data(
('Hash', 'CoverImage',),
'books',
{'Hash': ''},
'LIKE')
if not all_covers_db:
return
all_covers = {
i[0]: i[1] for i in all_covers_db}
for i in range(self.lib_ref.libraryModel.rowCount()):
this_item = self.lib_ref.libraryModel.item(i, 0)
is_cover_already_displayed = this_item.data(QtCore.Qt.UserRole + 8)
if is_cover_already_displayed:
continue
book_hash = this_item.data(QtCore.Qt.UserRole + 6)
cover = all_covers[book_hash]
self.cover_loader(this_item, cover)
def cover_loader(self, item, cover):
img_pixmap = QtGui.QPixmap()
if cover:
img_pixmap.loadFromData(cover)
else:
img_pixmap.load(':/images/NotFound.png')
img_pixmap = img_pixmap.scaled(
420, 600, QtCore.Qt.IgnoreAspectRatio)
item.setIcon(QtGui.QIcon(img_pixmap))
item.setData(True, QtCore.Qt.UserRole + 8)
class ViewProfileModification:
def __init__(self, main_window):
self.main_window = main_window
self.listView = self.main_window.listView
self.settings = self.main_window.settings
self.bookToolBar = self.main_window.bookToolBar
self.comic_profile = self.main_window.comic_profile
self.tabWidget = self.main_window.tabWidget
self.alignment_dict = self.main_window.alignment_dict
def get_color(self, signal_sender):
def open_color_dialog(current_color):
color_dialog = QtWidgets.QColorDialog()
new_color = color_dialog.getColor(current_color)
if new_color.isValid(): # Returned in case cancel is pressed
return new_color
else:
return current_color
# Special cases that don't affect (comic)book display
if signal_sender == 'libraryBackground':
current_color = self.settings['listview_background']
new_color = open_color_dialog(current_color)
self.listView.setStyleSheet("QListView {{background-color: {0}}}".format(
new_color.name()))
self.settings['listview_background'] = new_color
return
if signal_sender == 'dialogBackground':
current_color = self.settings['dialog_background']
new_color = open_color_dialog(current_color)
self.settings['dialog_background'] = new_color
return
profile_index = self.bookToolBar.profileBox.currentIndex()
current_profile = self.bookToolBar.profileBox.itemData(
profile_index, QtCore.Qt.UserRole)
# Retain current values on opening a new dialog
if signal_sender == 'fgColor':
current_color = current_profile['foreground']
new_color = open_color_dialog(current_color)
self.bookToolBar.colorBoxFG.setStyleSheet(
'background-color: %s' % new_color.name())
current_profile['foreground'] = new_color
elif signal_sender == 'bgColor':
current_color = current_profile['background']
new_color = open_color_dialog(current_color)
self.bookToolBar.colorBoxBG.setStyleSheet(
'background-color: %s' % new_color.name())
current_profile['background'] = new_color
elif signal_sender == 'comicBGColor':
current_color = self.comic_profile['background']
new_color = open_color_dialog(current_color)
self.bookToolBar.comicBGColor.setStyleSheet(
'background-color: %s' % new_color.name())
self.comic_profile['background'] = new_color
self.bookToolBar.profileBox.setItemData(
profile_index, current_profile, QtCore.Qt.UserRole)
self.format_contentView()
def modify_font(self, signal_sender):
profile_index = self.bookToolBar.profileBox.currentIndex()
current_profile = self.bookToolBar.profileBox.itemData(
profile_index, QtCore.Qt.UserRole)
if signal_sender == 'fontBox':
current_profile['font'] = self.bookToolBar.fontBox.currentFont().family()
if signal_sender == 'fontSizeBox':
old_size = current_profile['font_size']
new_size = self.bookToolBar.fontSizeBox.itemText(
self.bookToolBar.fontSizeBox.currentIndex())
if new_size.isdigit():
current_profile['font_size'] = new_size
else:
current_profile['font_size'] = old_size
if signal_sender == 'lineSpacingUp' and current_profile['line_spacing'] < 200:
current_profile['line_spacing'] += 5
if signal_sender == 'lineSpacingDown' and current_profile['line_spacing'] > 90:
current_profile['line_spacing'] -= 5
if signal_sender == 'paddingUp':
current_profile['padding'] += 5
if signal_sender == 'paddingDown':
current_profile['padding'] -= 5
alignment_dict = {
'alignLeft': 'left',
'alignRight': 'right',
'alignCenter': 'center',
'alignJustify': 'justify'}
if signal_sender in alignment_dict:
current_profile['text_alignment'] = alignment_dict[signal_sender]
self.bookToolBar.profileBox.setItemData(
profile_index, current_profile, QtCore.Qt.UserRole)
self.format_contentView()
def modify_comic_view(self, signal_sender, key_pressed):
comic_profile = self.main_window.comic_profile
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' or key_pressed == QtCore.Qt.Key_Minus:
comic_profile['zoom_mode'] = 'manualZoom'
comic_profile['padding'] += 50
# This prevents infinite zoom out
if comic_profile['padding'] * 2 > current_tab.contentView.viewport().width():
comic_profile['padding'] -= 50
if signal_sender == 'zoomIn' or key_pressed in (
QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal):
comic_profile['zoom_mode'] = 'manualZoom'
comic_profile['padding'] -= 50
# This prevents infinite zoom in
if comic_profile['padding'] < 0:
comic_profile['padding'] = 0
if signal_sender == 'fitWidth' or key_pressed == QtCore.Qt.Key_W:
comic_profile['zoom_mode'] = 'fitWidth'
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' or key_pressed == QtCore.Qt.Key_B:
comic_profile['zoom_mode'] = 'bestFit'
self.bookToolBar.bestFit.setChecked(True)
if signal_sender == 'originalSize' or key_pressed == QtCore.Qt.Key_O:
comic_profile['zoom_mode'] = 'originalSize'
self.bookToolBar.originalSize.setChecked(True)
self.format_contentView()
def format_contentView(self):
current_tab = self.tabWidget.currentWidget()
try:
current_metadata = current_tab.metadata
except AttributeError:
return
if current_metadata['images_only']:
background = self.comic_profile['background']
zoom_mode = self.comic_profile['zoom_mode']
if zoom_mode == 'fitWidth':
self.bookToolBar.fitWidth.setChecked(True)
if zoom_mode == 'bestFit':
self.bookToolBar.bestFit.setChecked(True)
if zoom_mode == 'originalSize':
self.bookToolBar.originalSize.setChecked(True)
self.bookToolBar.comicBGColor.setStyleSheet(
'background-color: %s' % background.name())
current_tab.format_view(
None, None, None, background, None, None, None)
else:
profile_index = self.bookToolBar.profileBox.currentIndex()
current_profile = self.bookToolBar.profileBox.itemData(
profile_index, QtCore.Qt.UserRole)
font = current_profile['font']
foreground = current_profile['foreground']
background = current_profile['background']
padding = current_profile['padding']
font_size = current_profile['font_size']
line_spacing = current_profile['line_spacing']
text_alignment = current_profile['text_alignment']
# Change toolbar widgets to match new settings
self.bookToolBar.fontBox.blockSignals(True)
self.bookToolBar.fontSizeBox.blockSignals(True)
self.bookToolBar.fontBox.setCurrentText(font)
current_index = self.bookToolBar.fontSizeBox.findText(
str(font_size), QtCore.Qt.MatchExactly)
self.bookToolBar.fontSizeBox.setCurrentIndex(current_index)
self.bookToolBar.fontBox.blockSignals(False)
self.bookToolBar.fontSizeBox.blockSignals(False)
self.alignment_dict[current_profile['text_alignment']].setChecked(True)
self.bookToolBar.colorBoxFG.setStyleSheet(
'background-color: %s' % foreground.name())
self.bookToolBar.colorBoxBG.setStyleSheet(
'background-color: %s' % background.name())
current_tab.format_view(
font, font_size, foreground,
background, padding, line_spacing,
text_alignment)
def reset_profile(self):
current_profile_index = self.bookToolBar.profileBox.currentIndex()
current_profile_default = Settings(self).default_profiles[current_profile_index]
self.bookToolBar.profileBox.setItemData(
current_profile_index, current_profile_default, QtCore.Qt.UserRole)
self.format_contentView()

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -18,27 +16,32 @@
import os
import pickle
import logging
import pathlib
from PyQt5 import QtGui, QtCore
from lector import database
from lector.models import TableProxyModel, ItemProxyModel
logger = logging.getLogger(__name__)
class Library:
def __init__(self, parent):
self.parent = parent
self.view_model = None
self.item_proxy_model = None
self.table_proxy_model = None
self.main_window = parent
self.libraryModel = None
self.itemProxyModel = None
self.tableProxyModel = None
self._translate = QtCore.QCoreApplication.translate
def generate_model(self, mode, parsed_books=None, is_database_ready=True):
if mode == 'build':
self.view_model = QtGui.QStandardItemModel()
self.view_model.setColumnCount(10)
self.libraryModel = QtGui.QStandardItemModel()
self.libraryModel.setColumnCount(10)
books = database.DatabaseFunctions(
self.parent.database_path).fetch_data(
self.main_window.database_path).fetch_data(
('Title', 'Author', 'Year', 'DateAdded', 'Path',
'Position', 'ISBN', 'Tags', 'Hash', 'LastAccessed',
'Addition'),
@@ -47,20 +50,24 @@ class Library:
'LIKE')
if not books:
print('Database returned nothing')
logger.warning('Database returned nothing')
return
elif mode == 'addition':
# Assumes self.view_model already exists and may be extended
# Assumes self.libraryModel already exists and may be extended
# Because any additional books have already been added to the
# database using background threads
books = []
current_qdatetime = QtCore.QDateTime().currentDateTime()
for i in parsed_books.items():
_tags = i[1]['tags']
if _tags:
_tags = ', '.join([j for j in _tags if j])
try:
_tags = i[1]['tags']
if _tags:
_tags = ', '.join([j for j in _tags if j])
except: # Continuing seems more important than being correct
_tags = []
logger.warning('Tag generation error for: ' + i[1]['path'])
books.append([
i[1]['title'], i[1]['author'], i[1]['year'], current_qdatetime,
@@ -98,19 +105,12 @@ class Library:
position = i[5]
if position:
position = pickle.loads(position)
if position['is_read']:
position_perc = 100
else:
try:
position_perc = (
position['current_chapter'] * 100 / position['total_chapters'])
except KeyError:
position_perc = None
position_perc = generate_position_percentage(position)
try:
file_exists = os.path.exists(path)
except UnicodeEncodeError:
print('Error with unicode encoding in the library module')
print('Library: Unicode encoding error')
all_metadata = {
'title': title,
@@ -145,6 +145,7 @@ class Library:
item.setToolTip(tooltip_string)
# Just keep the following order. It's way too much trouble otherwise
# User roles have to be correlated to sorting order below
item.setData(title, QtCore.Qt.UserRole)
item.setData(author, QtCore.Qt.UserRole + 1)
item.setData(year, QtCore.Qt.UserRole + 2)
@@ -156,57 +157,60 @@ class Library:
item.setData(False, QtCore.Qt.UserRole + 8) # Is the cover being displayed?
item.setData(date_added, QtCore.Qt.UserRole + 9)
item.setData(last_accessed, QtCore.Qt.UserRole + 12)
item.setData(path, QtCore.Qt.UserRole + 13)
item.setIcon(QtGui.QIcon(img_pixmap))
self.view_model.appendRow(item)
self.libraryModel.appendRow(item)
# The is_database_ready boolean is required when a new thread sends
# books here for model generation.
if not self.parent.settings['perform_culling'] and is_database_ready:
self.parent.load_all_covers()
if not self.main_window.settings['perform_culling'] and is_database_ready:
self.main_window.cover_functions.load_all_covers()
def generate_proxymodels(self):
self.item_proxy_model = ItemProxyModel()
self.item_proxy_model.setSourceModel(self.view_model)
self.item_proxy_model.setSortCaseSensitivity(False)
self.itemProxyModel = ItemProxyModel()
self.itemProxyModel.setSourceModel(self.libraryModel)
self.itemProxyModel.setSortCaseSensitivity(False)
s = QtCore.QSize(160, 250) # Set icon sizing here
self.parent.listView.setIconSize(s)
self.parent.listView.setModel(self.item_proxy_model)
self.main_window.listView.setIconSize(s)
self.main_window.listView.setModel(self.itemProxyModel)
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)
self.tableProxyModel = TableProxyModel(
self.main_window.temp_dir.path(),
self.main_window.tableView.horizontalHeader(),
self.main_window.settings['consider_read_at'])
self.tableProxyModel.setSourceModel(self.libraryModel)
self.tableProxyModel.setSortCaseSensitivity(False)
self.main_window.tableView.setModel(self.tableProxyModel)
self.update_proxymodels()
def update_proxymodels(self):
# Table proxy model
self.table_proxy_model.invalidateFilter()
self.table_proxy_model.setFilterParams(
self.parent.libraryToolBar.searchBar.text(),
self.parent.active_library_filters,
self.tableProxyModel.invalidateFilter()
self.tableProxyModel.setFilterParams(
self.main_window.libraryToolBar.searchBar.text(),
self.main_window.active_library_filters,
0) # This doesn't need to know the sorting box position
self.table_proxy_model.setFilterFixedString(
self.parent.libraryToolBar.searchBar.text())
self.tableProxyModel.setFilterFixedString(
self.main_window.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()
self.tableProxyModel.sort_table_columns(
self.main_window.tableView.horizontalHeader().sortIndicatorSection())
self.tableProxyModel.sort_table_columns()
# Item proxy model
self.item_proxy_model.invalidateFilter()
self.item_proxy_model.setFilterParams(
self.parent.libraryToolBar.searchBar.text(),
self.parent.active_library_filters,
self.parent.libraryToolBar.sortingBox.currentIndex())
self.item_proxy_model.setFilterFixedString(
self.parent.libraryToolBar.searchBar.text())
self.itemProxyModel.invalidateFilter()
self.itemProxyModel.setFilterParams(
self.main_window.libraryToolBar.searchBar.text(),
self.main_window.active_library_filters,
self.main_window.libraryToolBar.sortingBox.currentIndex())
self.itemProxyModel.setFilterFixedString(
self.main_window.libraryToolBar.searchBar.text())
self.parent.statusMessage.setText(
str(self.item_proxy_model.rowCount()) +
self.main_window.statusMessage.setText(
str(self.itemProxyModel.rowCount()) +
self._translate('Library', ' books'))
# TODO
@@ -221,23 +225,25 @@ class Library:
1: 1,
2: 2,
3: 9,
4: 12}
4: 12,
5: 7}
# Sorting according to roles and the drop down in the library toolbar
self.item_proxy_model.setSortRole(
QtCore.Qt.UserRole + sort_roles[self.parent.libraryToolBar.sortingBox.currentIndex()])
self.itemProxyModel.setSortRole(
QtCore.Qt.UserRole +
sort_roles[self.main_window.libraryToolBar.sortingBox.currentIndex()])
# This can be expanded to other fields by appending to the list
sort_order = QtCore.Qt.AscendingOrder
if self.parent.libraryToolBar.sortingBox.currentIndex() in [3, 4]:
if self.main_window.libraryToolBar.sortingBox.currentIndex() in [3, 4, 5]:
sort_order = QtCore.Qt.DescendingOrder
self.item_proxy_model.sort(0, sort_order)
self.parent.start_culling_timer()
self.itemProxyModel.sort(0, sort_order)
self.main_window.start_culling_timer()
def generate_library_tags(self):
db_library_directories = database.DatabaseFunctions(
self.parent.database_path).fetch_data(
self.main_window.database_path).fetch_data(
('Path', 'Name', 'Tags'),
'directories', # This checks the directories table NOT the book one
{'Path': ''},
@@ -249,7 +255,7 @@ class Library:
else:
db_library_directories = database.DatabaseFunctions(
self.parent.database_path).fetch_data(
self.main_window.database_path).fetch_data(
('Path',),
'books', # THIS CHECKS THE BOOKS TABLE
{'Path': ''},
@@ -285,8 +291,8 @@ class Library:
# 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)
for i in range(self.libraryModel.rowCount()):
this_item = self.libraryModel.item(i, 0)
all_metadata = this_item.data(QtCore.Qt.UserRole + 3)
directory_name, directory_tags = get_tags(all_metadata)
@@ -301,8 +307,8 @@ class Library:
invalid_paths = []
deletable_persistent_indexes = []
for i in range(self.view_model.rowCount()):
item = self.view_model.item(i)
for i in range(self.libraryModel.rowCount()):
item = self.libraryModel.item(i)
item_metadata = item.data(QtCore.Qt.UserRole + 3)
book_path = item_metadata['path']
@@ -310,7 +316,7 @@ class Library:
addition_mode = item_metadata['addition_mode']
except KeyError:
addition_mode = 'automatic'
print('Libary: Error setting addition mode for prune')
logger.error('Libary: Error setting addition mode for prune')
if (book_path not in valid_paths and
(addition_mode != 'manual' or addition_mode is None)):
@@ -321,8 +327,28 @@ class Library:
if deletable_persistent_indexes:
for i in deletable_persistent_indexes:
self.view_model.removeRow(i.row())
self.libraryModel.removeRow(i.row())
# Remove invalid paths from the database as well
database.DatabaseFunctions(
self.parent.database_path).delete_from_database('Path', invalid_paths)
self.main_window.database_path).delete_from_database('Path', invalid_paths)
def generate_position_percentage(position):
if not position:
return None
if position['is_read']:
position_perc = 1
else:
try:
position_perc = (
position['current_block'] / position['total_blocks'])
except (KeyError, ZeroDivisionError):
try:
position_perc = (
position['current_chapter'] / position['total_chapters'])
except KeyError:
position_perc = None
return position_perc

58
lector/logger.py Normal file
View File

@@ -0,0 +1,58 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 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/>.
VERSION = '0.5.GittyGittyBangBang'
import os
import logging
from PyQt5 import QtCore
location_prefix = os.path.join(
QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppDataLocation),
'Lector')
logger_filename = os.path.join(location_prefix, 'Lector.log')
def init_logging(cli_arguments):
# This needs a separate 'Lector' in the os.path.join because
# application name isn't explicitly set in this module
os.makedirs(location_prefix, exist_ok=True)
log_level = 30 # Warning and above
# Set log level according to command line arguments
try:
if cli_arguments[1] == 'debug':
log_level = 10 # Debug and above
print('Debug logging enabled')
try:
os.remove(logger_filename) # Remove old log for clarity
except FileNotFoundError:
pass
except IndexError:
pass
# Create logging object
logging.basicConfig(
filename=logger_filename,
filemode='a',
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
datefmt='%Y/%m/%d %H:%M:%S',
level=log_level)
logging.addLevelName(60, 'HAMMERTIME') ## Messages that MUST be logged
return logging.getLogger('lector.main')

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -16,12 +14,16 @@
# 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 logging
from PyQt5 import QtWidgets, QtCore, QtGui
from lector import database
from lector.widgets import PliantQGraphicsScene
from lector.resources import metadata
logger = logging.getLogger(__name__)
class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
def __init__(self, parent):
@@ -38,8 +40,12 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
radius = 15
path = QtGui.QPainterPath()
path.addRoundedRect(QtCore.QRectF(self.rect()), radius, radius)
mask = QtGui.QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
try:
mask = QtGui.QRegion(path.toFillPolygon().toPolygon())
self.setMask(mask)
except TypeError: # Required for older versions of Qt
pass
self.parent = parent
self.database_path = self.parent.database_path
@@ -86,7 +92,7 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
self.coverView.setScene(graphics_scene)
def ok_pressed(self, event=None):
book_item = self.parent.lib_ref.view_model.item(self.book_index.row())
book_item = self.parent.lib_ref.libraryModel.item(self.book_index.row())
title = self.titleLine.text()
author = self.authorLine.text()
@@ -116,7 +122,7 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
if self.cover_for_database:
database_dict['CoverImage'] = self.cover_for_database
self.parent.cover_loader(
self.parent.cover_functions.cover_loader(
book_item, self.cover_for_database)
self.parent.lib_ref.update_proxymodels()
@@ -148,7 +154,8 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog):
background = self.parent.settings['dialog_background']
else:
self.previous_position = self.pos()
background = self.parent.get_color()
self.parent.get_color()
background = self.parent.settings['dialog_background']
self.setStyleSheet(
"QDialog {{background-color: {0}}}".format(background.name()))

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -16,33 +14,32 @@
# 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 logging
import pathlib
from PyQt5 import QtCore, QtWidgets
from lector.resources import pie_chart
logger = logging.getLogger(__name__)
class BookmarkProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super(BookmarkProxyModel, self).__init__(parent)
self.parent = parent
self.parentTab = self.parent.parent
self.filter_text = None
def setFilterParams(self, filter_text):
self.filter_text = filter_text
def filterAcceptsRow(self, row, parent):
# TODO
# Connect this to the search bar
return True
def setData(self, index, value, role):
if role == QtCore.Qt.EditRole:
source_index = self.mapToSource(index)
identifier = self.sourceModel().data(source_index, QtCore.Qt.UserRole + 2)
self.sourceModel().setData(source_index, value, QtCore.Qt.DisplayRole)
self.parent.metadata['bookmarks'][identifier]['description'] = value
self.parentTab.metadata['bookmarks'][identifier]['description'] = value
return True
@@ -65,9 +62,10 @@ class ItemProxyModel(QtCore.QSortFilterProxyModel):
class TableProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, temp_dir, tableViewHeader, parent=None):
def __init__(self, temp_dir, tableViewHeader, consider_read_at, parent=None):
super(TableProxyModel, self).__init__(parent)
self.tableViewHeader = tableViewHeader
self.consider_read_at = consider_read_at
self._translate = QtCore.QCoreApplication.translate
title_string = self._translate('TableProxyModel', 'Title')
@@ -100,7 +98,9 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
try:
return self.header_data[column]
except IndexError:
print('Table proxy model: Can\'t find header for column', column)
logger.error(
'Table proxy model: Can\'t find header for column' + str(column))
# The column will be called IndexError. Not a typo.
return 'IndexError'
def flags(self, index):
@@ -121,38 +121,22 @@ class TableProxyModel(QtCore.QSortFilterProxyModel):
return_pixmap = None
file_exists = item.data(QtCore.Qt.UserRole + 5)
metadata = item.data(QtCore.Qt.UserRole + 3)
position = metadata['position']
if position:
is_read = position['is_read']
position_percent = item.data(QtCore.Qt.UserRole + 7)
if not file_exists:
return pie_chart.pixmapper(
-1, None, None, QtCore.Qt.SizeHintRole + 10)
if position:
if is_read:
current_chapter = total_chapters = 100
else:
try:
current_chapter = position['current_chapter']
total_chapters = position['total_chapters']
# TODO
# See if there's any rationale for this
if current_chapter == 1:
raise KeyError
except KeyError:
return
if position_percent:
return_pixmap = pie_chart.pixmapper(
current_chapter, total_chapters, self.temp_dir,
position_percent, self.temp_dir,
self.consider_read_at,
QtCore.Qt.SizeHintRole + 10)
return return_pixmap
elif role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
if index.column() in (0, 5): # Cover and Status
if index.column() in (0, 5): # Cover and Status
return QtCore.QVariant()
if index.column() == 4:
@@ -214,14 +198,20 @@ class ProxyModelsCommonFunctions:
title = model.data(this_index, QtCore.Qt.UserRole)
author = model.data(this_index, QtCore.Qt.UserRole + 1)
tags = model.data(this_index, QtCore.Qt.UserRole + 4)
progress = model.data(this_index, QtCore.Qt.UserRole + 7)
directory_name = model.data(this_index, QtCore.Qt.UserRole + 10)
directory_tags = model.data(this_index, QtCore.Qt.UserRole + 11)
last_accessed = model.data(this_index, QtCore.Qt.UserRole + 12)
file_path = model.data(this_index, QtCore.Qt.UserRole + 13)
# Hide untouched files when sorting by last accessed
if self.parent_model.sorting_box_position == 4 and not last_accessed:
return False
# Hide untouched files when sorting by progress
if self.parent_model.sorting_box_position == 5 and not progress:
return False
if self.parent_model.active_library_filters:
if directory_name not in self.parent_model.active_library_filters:
return False
@@ -233,7 +223,9 @@ class ProxyModelsCommonFunctions:
else:
valid_data = [
i.lower() for i in (
title, author, tags, directory_name, directory_tags) if i is not None]
title, author, tags, directory_name,
directory_tags, file_path)
if i is not None]
for i in valid_data:
if self.parent_model.filter_text.lower() in i:
return True
@@ -351,32 +343,3 @@ class MostExcellentFileSystemModel(QtWidgets.QFileSystemModel):
for i in deletable:
del self.tag_data[i]
# TODO
# Unbork this
class FileSystemProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super(FileSystemProxyModel, self).__init__(parent)
def filterAcceptsRow(self, row_num, parent):
model = self.sourceModel()
filter_out = [
'boot', 'dev', 'etc', 'lost+found', 'opt', 'pdb',
'proc', 'root', 'run', 'srv', 'sys', 'tmp', 'twonky',
'usr', 'var', 'bin', 'kdeinit5__0', 'lib', 'lib64', 'sbin']
name_index = model.index(row_num, 0)
valid_data = model.data(name_index)
print(valid_data)
return True
try:
if valid_data in filter_out:
return False
except AttributeError:
pass
return True

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-18 BasioMeusPuga
# Copyright (C) 2017-19 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
@@ -21,9 +19,14 @@
import os
import time
import logging
import zipfile
import collections
from lector.rarfile import rarfile
logger = logging.getLogger(__name__)
class ParseCOMIC:
def __init__(self, filename, *args):
@@ -33,47 +36,45 @@ class ParseCOMIC:
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()]
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() and is_image(i.filename)]
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()]
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() and is_image(i.filename)]
self.image_list.sort()
except: # Specifying no exception here is warranted
print('Cannot parse ' + self.filename)
return
self.image_list.sort()
def get_title(self):
def generate_metadata(self):
title = os.path.basename(self.book_extension[0]).strip(' ')
return title
author = '<Unknown>'
isbn = None
tags = []
cover = self.book.read(self.image_list[0])
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
year = creation_time.split()[-1]
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])
Metadata = collections.namedtuple(
'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover'])
return Metadata(title, author, year, isbn, tags, cover)
def get_isbn(self):
return None
def generate_content(self):
image_number = len(self.image_list)
toc = [(1, f'Page {i + 1}', i + 1) for i in range(image_number)]
def get_tags(self):
return None
# Return toc, content, images_only
return toc, self.image_list, True
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
def is_image(filename):
valid_image_extensions = ['.png', '.jpg', '.bmp']
if os.path.splitext(filename)[1].lower() in valid_image_extensions:
return True
else:
return False

98
lector/parsers/djvu.py Normal file
View File

@@ -0,0 +1,98 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 BasioMeusPuga
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import collections
import djvu.decode
from PyQt5 import QtGui
class ParseDJVU:
def __init__(self, filename, *args):
self.book = None
self.filename = filename
def read_book(self):
self.book = djvu.decode.Context().new_document(
djvu.decode.FileURI(self.filename))
self.book.decoding_job.wait()
def generate_metadata(self):
# TODO
# What even is this?
title = os.path.basename(self.filename)
author = 'Unknown'
year = 9999
isbn = None
tags = []
cover_page = self.book.pages[0]
cover = render_djvu_page(cover_page, True)
Metadata = collections.namedtuple(
'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover'])
return Metadata(title, author, year, isbn, tags, cover)
def generate_content(self):
# TODO
# See if it's possible to generate a more involved ToC
content = list(range(len(self.book.pages)))
toc = [(1, f'Page {i + 1}', i + 1) for i in content]
# Return toc, content, images_only
return toc, content, True
def render_djvu_page(page, for_cover=False):
# TODO
# Figure out how to calculate image stride
# and if it impacts row_alignment in the render
# method below
# bytes_per_line = 13200
djvu_pixel_format = djvu.decode.PixelFormatRgbMask(
0xFF0000, 0xFF00, 0xFF, bpp=32)
djvu_pixel_format.rows_top_to_bottom = 1
djvu_pixel_format.y_top_to_bottom = 0
# ¯\_(ツ)_/¯
mode = 0
page_job = page.decode(wait=True)
width, height = page_job.size
rect = (0, 0, width, height)
output = page_job.render(
mode, rect, rect, djvu_pixel_format)
# row_alignment=bytes_per_line)
imageFormat = QtGui.QImage.Format_RGB32
pageQImage = QtGui.QImage(output, width, height, imageFormat)
# Format conversion not only keeps the damn thing from
# segfaulting when converting from QImage to QPixmap,
# but it also allows for the double page mode to keep
# working properly. We like format conversion.
pageQImage = pageQImage.convertToFormat(
QtGui.QImage.Format_ARGB32_Premultiplied)
if for_cover:
return pageQImage
pixmap = QtGui.QPixmap()
pixmap.convertFromImage(pageQImage)
return pixmap

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -16,51 +14,43 @@
# 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
# Maybe also include book description
import os
import zipfile
import logging
from lector.ePub.read_epub import EPUB
from lector.readers.read_epub import EPUB
logger = logging.getLogger(__name__)
class ParseEPUB:
def __init__(self, filename, temp_dir, file_md5):
# TODO
# Maybe also include book description
self.book_ref = None
self.book = None
self.filename = filename
self.temp_dir = temp_dir
self.extract_path = os.path.join(temp_dir, file_md5)
def read_book(self):
self.book_ref = EPUB(self.filename)
contents_found = self.book_ref.read_epub()
if not contents_found:
print('Cannot process: ' + self.filename)
return
self.book = self.book_ref.book
self.book = EPUB(self.filename, self.temp_dir)
def get_title(self):
return self.book['title']
def generate_metadata(self):
self.book.generate_metadata()
return self.book.metadata
def get_author(self):
return self.book['author']
def get_year(self):
return self.book['year']
def get_cover_image(self):
return self.book['cover']
def get_isbn(self):
return self.book['isbn']
def get_tags(self):
return self.book['tags']
def get_contents(self):
def generate_content(self):
zipfile.ZipFile(self.filename).extractall(self.extract_path)
self.book_ref.parse_chapters(temp_dir=self.extract_path)
file_settings = {
'images_only': False}
return self.book['book_list'], file_settings
self.book.generate_toc()
self.book.generate_content()
toc = []
content = []
for count, i in enumerate(self.book.content):
toc.append((i[0], i[1], count + 1))
content.append(i[2])
# Return toc, content, images_only
return toc, content, False

52
lector/parsers/fb2.py Normal file
View File

@@ -0,0 +1,52 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 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
# Maybe also include book description
import os
import logging
from lector.readers.read_fb2 import FB2
logger = logging.getLogger(__name__)
class ParseFB2:
def __init__(self, filename, temp_dir, file_md5):
self.book = None
self.filename = filename
self.extract_path = os.path.join(temp_dir, file_md5)
def read_book(self):
self.book = FB2(self.filename)
def generate_metadata(self):
self.book.generate_metadata()
return self.book.metadata
def generate_content(self):
os.makedirs(self.extract_path, exist_ok=True) # Manual creation is required here
self.book.generate_content(temp_dir=self.extract_path)
toc = []
content = []
for count, i in enumerate(self.book.content):
toc.append((i[0], i[1], count + 1))
content.append(i[2])
# Return toc, content, images_only
return toc, content, False

View File

@@ -0,0 +1,54 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 BasioMeusPuga
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import collections
import markdown
logger = logging.getLogger(__name__)
class ParseMD:
def __init__(self, filename, *args):
self.book = None
self.filename = filename
def read_book(self):
self.book = None
def generate_metadata(self):
title = os.path.basename(self.filename)
author = 'Unknown'
year = 9999
isbn = None
tags = []
cover = None
Metadata = collections.namedtuple(
'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover'])
return Metadata(title, author, year, isbn, tags, cover)
def generate_content(self):
with open(self.filename, 'r') as book:
text = book.read()
content = [markdown.markdown(text)]
toc = [(1, 'Markdown', 1)]
# Return toc, content, images_only
return toc, content, False

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -16,78 +14,69 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This module parses Amazon ebooks using KindleUnpack to first create an
# epub that is then read the usual way
# TODO
# See if it's possible to just feed the
# unzipped mobi7 file into the EPUB parser module
import os
import sys
import shutil
import zipfile
import logging
from lector.ePub.read_epub import EPUB
from lector.readers.read_epub import EPUB
import lector.KindleUnpack.kindleunpack as KindleUnpack
logger = logging.getLogger(__name__)
class ParseMOBI:
# This module parses Amazon ebooks using KindleUnpack to first create an
# epub and then read the usual way
def __init__(self, filename, temp_dir, file_md5):
self.book_ref = None
self.book = None
self.filename = filename
self.epub_filepath = None
self.split_large_xml = False
self.temp_dir = temp_dir
self.extract_dir = os.path.join(temp_dir, file_md5)
self.extract_path = os.path.join(temp_dir, file_md5)
def read_book(self):
with HidePrinting():
KindleUnpack.unpackBook(self.filename, self.extract_dir)
KindleUnpack.unpackBook(self.filename, self.extract_path)
epub_filename = os.path.splitext(
os.path.basename(self.filename))[0] + '.epub'
self.epub_filepath = os.path.join(
self.extract_dir, 'mobi8', epub_filename)
self.extract_path, 'mobi8', epub_filename)
if not os.path.exists(self.epub_filepath):
zip_dir = os.path.join(self.extract_dir, 'mobi7')
zip_dir = os.path.join(self.extract_path, 'mobi7')
zip_file = os.path.join(
self.extract_dir, epub_filename)
self.extract_path, epub_filename)
self.epub_filepath = shutil.make_archive(zip_file, 'zip', zip_dir)
self.split_large_xml = True
self.book_ref = EPUB(self.epub_filepath)
contents_found = self.book_ref.read_epub()
if not contents_found:
print('Cannot process: ' + self.filename)
return
self.book = self.book_ref.book
self.book = EPUB(self.epub_filepath, self.temp_dir)
def get_title(self):
return self.book['title']
def generate_metadata(self):
self.book.generate_metadata()
return self.book.metadata
def get_author(self):
return self.book['author']
def generate_content(self):
zipfile.ZipFile(self.epub_filepath).extractall(self.extract_path)
def get_year(self):
return self.book['year']
self.book.generate_toc()
self.book.generate_content()
def get_cover_image(self):
return self.book['cover']
toc = []
content = []
for count, i in enumerate(self.book.content):
toc.append((1, i[1], count + 1))
content.append(i[2])
def get_isbn(self):
return self.book['isbn']
# Return toc, content, images_only
return toc, content, False
def get_tags(self):
return self.book['tags']
def get_contents(self):
extract_path = os.path.join(self.extract_dir)
zipfile.ZipFile(self.epub_filepath).extractall(extract_path)
self.book_ref.parse_chapters(
temp_dir=self.temp_dir, split_large_xml=self.split_large_xml)
file_settings = {
'images_only': False}
return self.book['book_list'], file_settings
class HidePrinting:
def __enter__(self):

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env python3
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2018 BasioMeusPuga
# Copyright (C) 2017-2019 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
@@ -16,90 +14,87 @@
# 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
import collections
import fitz
from PyQt5 import QtGui
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 = fitz.open(self.filename)
self.book = popplerqt5.Poppler.Document.load(self.filename)
if not self.book:
return
def generate_metadata(self):
title = self.book.metadata['title']
if not title:
title = os.path.splitext(os.path.basename(self.filename))[0]
self.metadata = BeautifulSoup(self.book.metadata(), 'xml')
author = self.book.metadata['author']
if not author:
author = 'Unknown'
def get_title(self):
creation_date = self.book.metadata['creationDate']
try:
title = self.metadata.find('title').text
return title.replace('\n', '')
except AttributeError:
return os.path.splitext(os.path.basename(self.filename))[0]
year = creation_date.split(':')[1][:4]
except (ValueError, AttributeError):
year = 9999
def get_author(self):
try:
author = self.metadata.find('creator').text
return author.replace('\n', '')
except AttributeError:
return 'Unknown'
isbn = None
def get_year(self):
try:
year = self.metadata.find('MetadataDate').text
return year.replace('\n', '')
except AttributeError:
return 9999
tags = self.book.metadata['keywords']
if not tags:
tags = []
def get_cover_image(self):
self.book.setRenderHint(
popplerqt5.Poppler.Document.Antialiasing
and popplerqt5.Poppler.Document.TextAntialiasing)
# This is a little roundabout for the cover
# and I'm sure it's taking a performance hit
# But it is simple. So there's that.
cover_page = self.book.loadPage(0)
# Disabling scaling gets the covers much faster
cover = render_pdf_page(cover_page, True)
cover_page = self.book.page(0)
cover_image = cover_page.renderToImage(300, 300)
return resize_image(cover_image)
Metadata = collections.namedtuple(
'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover'])
return Metadata(title, author, year, isbn, tags, cover)
def get_isbn(self):
return None
def generate_content(self):
content = list(range(self.book.pageCount))
toc = self.book.getToC()
if not toc:
toc = [(1, f'Page {i + 1}', i + 1) for i in range(self.book.pageCount)]
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
# Return toc, content, images_only
return toc, content, True
def resize_image(cover_image):
cover_image = cover_image.scaled(
420, 600, QtCore.Qt.IgnoreAspectRatio)
def render_pdf_page(page_data, for_cover=False):
# Draw page contents on to a pixmap
# and then return that pixmap
byte_array = QtCore.QByteArray()
buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly)
cover_image.save(buffer, 'jpg', 75)
# Render quality is set by the following
zoom_matrix = fitz.Matrix(4, 4)
if for_cover:
zoom_matrix = fitz.Matrix(1, 1)
cover_image_final = io.BytesIO(byte_array)
cover_image_final.seek(0)
return cover_image_final.getvalue()
pagePixmap = page_data.getPixmap(
matrix=zoom_matrix,
alpha=False) # Sets background to White
imageFormat = QtGui.QImage.Format_RGB888 # Set to Format_RGB888 if alpha
pageQImage = QtGui.QImage(
pagePixmap.samples,
pagePixmap.width,
pagePixmap.height,
pagePixmap.stride,
imageFormat)
# The cover page doesn't require conversion into a Pixmap
if for_cover:
return pageQImage
pixmap = QtGui.QPixmap()
pixmap.convertFromImage(pageQImage)
return pixmap

55
lector/parsers/txt.py Normal file
View File

@@ -0,0 +1,55 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 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 collections
import os
import textile
class ParseTXT:
"""Parser for TXT files."""
def __init__(self, filename, *args):
"""Initialize new instance of the TXT parser."""
self.filename = filename
def read_book(self):
"""Prepare the parser to read book."""
pass
def generate_metadata(self):
"""Generate metadata for the book."""
title = os.path.basename(self.filename)
author = 'Unknown'
year = 9999
isbn = None
tags = []
cover = None
Metadata = collections.namedtuple(
'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover'])
return Metadata(title, author, year, isbn, tags, cover)
def generate_content(self):
"""Generate content of the book."""
with open(self.filename, 'rt') as txt:
text = txt.read()
content = [textile.textile(text)]
toc = [(1, 'Text', 1)]
return toc, content, False

478
lector/readers/read_epub.py Normal file
View File

@@ -0,0 +1,478 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 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
# See if inserting chapters not in the toc.ncx can be avoided
# Account for stylesheets... eventually
import os
import zipfile
import logging
import collections
from urllib.parse import unquote
import xmltodict
from PyQt5 import QtGui
from bs4 import BeautifulSoup
logger = logging.getLogger(__name__)
class EPUB:
def __init__(self, book_filename, temp_dir):
self.book_filename = book_filename
self.temp_dir = temp_dir
self.zip_file = None
self.file_list = None
self.opf_dict = None
self.cover_image_name = None
self.split_chapters = {}
self.metadata = None
self.content = []
self.generate_references()
def generate_references(self):
self.zip_file = zipfile.ZipFile(
self.book_filename, mode='r', allowZip64=True)
self.file_list = self.zip_file.namelist()
# Book structure relies on parsing the .opf file
# in the book. Now that might be the usual content.opf
# or package.opf or it might be named after your favorite
# eldritch abomination. The point is we have to check
# the container.xml
container = self.find_file('container.xml')
if container:
container_xml = self.zip_file.read(container)
container_dict = xmltodict.parse(container_xml)
packagefile = container_dict['container']['rootfiles']['rootfile']['@full-path']
else:
presumptive_names = ('content.opf', 'package.opf', 'volume.opf')
for i in presumptive_names:
packagefile = self.find_file(i)
if packagefile:
logger.info('Using presumptive package file: ' + self.book_filename)
break
packagefile_data = self.zip_file.read(packagefile)
self.opf_dict = xmltodict.parse(packagefile_data)
def find_file(self, filename):
# Get rid of special characters
filename = unquote(filename)
# First, look for the file in the root of the book
if filename in self.file_list:
return filename
# Then search for it elsewhere
else:
file_basename = os.path.basename(filename)
for i in self.file_list:
if os.path.basename(i) == file_basename:
return i
# If the file isn't found
logger.warning(filename + ' not found in ' + self.book_filename)
return False
def generate_toc(self):
def find_alternative_toc():
toc_filename = None
toc_filename_alternative = None
manifest = self.opf_dict['package']['manifest']['item']
for i in manifest:
# Behold the burning hoops we're jumping through
if i['@id'] == 'ncx':
toc_filename = i['@href']
if ('ncx' in i['@id']) or ('toc' in i['@id']):
toc_filename_alternative = i['@href']
if toc_filename and toc_filename_alternative:
break
if not toc_filename:
if not toc_filename_alternative:
logger.warning('No ToC found for: ' + self.book_filename)
else:
toc_filename = toc_filename_alternative
logger.info('Using alternate ToC for: ' + self.book_filename)
return toc_filename
# Find the toc.ncx file from the manifest
# EPUBs will name literally anything, anything so try
# a less stringent approach if the first one doesn't work
# The idea is to prioritize 'toc.ncx' since this should work
# for the vast majority of books
toc_filename = 'toc.ncx'
does_toc_exist = self.find_file(toc_filename)
if not does_toc_exist:
toc_filename = find_alternative_toc()
tocfile = self.find_file(toc_filename)
tocfile_data = self.zip_file.read(tocfile)
toc_dict = xmltodict.parse(tocfile_data)
def recursor(level, nav_node):
if isinstance(nav_node, list):
these_contents = [[
level + 1,
i['navLabel']['text'],
i['content']['@src']] for i in nav_node]
self.content.extend(these_contents)
return
if 'navPoint' in nav_node.keys():
recursor(level, nav_node['navPoint'])
else:
self.content.append([
level + 1,
nav_node['navLabel']['text'],
nav_node['content']['@src']])
navpoints = toc_dict['ncx']['navMap']['navPoint']
for top_level_nav in navpoints:
# Just one chapter
if isinstance(top_level_nav, str):
self.content.append([
1,
navpoints['navLabel']['text'],
navpoints['content']['@src']])
break
# Multiple chapters
self.content.append([
1,
top_level_nav['navLabel']['text'],
top_level_nav['content']['@src']])
if 'navPoint' in top_level_nav.keys():
recursor(1, top_level_nav)
def get_chapter_content(self, chapter_file):
this_file = self.find_file(chapter_file)
if this_file:
chapter_content = self.zip_file.read(this_file).decode()
# Generate a None return for a blank chapter
# These will be removed from the contents later
contentDocument = QtGui.QTextDocument(None)
contentDocument.setHtml(chapter_content)
contentText = contentDocument.toPlainText().replace('\n', '')
if contentText == '':
chapter_content = None
return chapter_content
else:
return 'Possible parse error: ' + chapter_file
def parse_split_chapters(self, chapters_with_split_content):
# For split chapters, get the whole chapter first, then split
# between ids using their anchors, then "heal" the resultant text
# by creating a BeautifulSoup object. Write its str to the content
for i in chapters_with_split_content.items():
chapter_file = i[0]
self.split_chapters[chapter_file] = {}
chapter_content = self.get_chapter_content(chapter_file)
soup = BeautifulSoup(chapter_content, 'lxml')
split_anchors = i[1]
for this_anchor in reversed(split_anchors):
this_tag = soup.find(
attrs={"id":lambda x: x == this_anchor})
markup_split = str(soup).split(str(this_tag))
soup = BeautifulSoup(markup_split[0], 'lxml')
# If the tag is None, it probably means the content is overlapping
# Skipping the insert is the way forward
if this_tag:
this_markup = BeautifulSoup(
str(this_tag).strip() + markup_split[1], 'lxml')
self.split_chapters[chapter_file][this_anchor] = str(this_markup)
# Remaining markup is assigned here
self.split_chapters[chapter_file]['top_level'] = str(soup)
def generate_content(self):
# Find all the chapters mentioned in the opf spine
# These are simply ids that correspond to the actual item
# as mentioned in the manifest - which is a comprehensive
# list of files
try:
# Multiple chapters
chapters_in_spine = [
i['@idref']
for i in self.opf_dict['package']['spine']['itemref']]
except TypeError:
# Single chapter - Large xml
chapters_in_spine = [
self.opf_dict['package']['spine']['itemref']['@idref']]
# Next, find items and ids from the manifest
# This might error out in case there's only one item in
# the manifest. Remember that for later.
chapters_from_manifest = {
i['@id']: i['@href']
for i in self.opf_dict['package']['manifest']['item']}
# Finally, check which items are supposed to be in the spine
# on the basis of the id and change the toc accordingly
spine_final = []
for i in chapters_in_spine:
try:
spine_final.append(chapters_from_manifest.pop(i))
except KeyError:
pass
toc_chapters = [
unquote(i[2].split('#')[0]) for i in self.content]
for i in spine_final:
if not i in toc_chapters:
spine_index = spine_final.index(i)
if spine_index == 0: # Or chapter insertion circles back to the end
previous_chapter_toc_index = -1
else:
previous_chapter = spine_final[spine_final.index(i) - 1]
previous_chapter_toc_index = toc_chapters.index(previous_chapter)
toc_chapters.insert(
previous_chapter_toc_index + 1, i)
self.content.insert(
previous_chapter_toc_index + 1, [1, None, i])
# Parse split chapters as below
# They can be picked up during the iteration through the toc
chapters_with_split_content = {}
for i in self.content:
if '#' in i[2]:
this_split = i[2].split('#')
chapter = this_split[0]
anchor = this_split[1]
try:
chapters_with_split_content[chapter].append(anchor)
except KeyError:
chapters_with_split_content[chapter] = []
chapters_with_split_content[chapter].append(anchor)
self.parse_split_chapters(chapters_with_split_content)
# Now we iterate over the ToC as presented in the toc.ncx
# and add chapters to the content list
# In case a split chapter is encountered, get its content
# from the split_chapters dictionary
# What could possibly go wrong?
toc_copy = self.content[:]
# Put the book into the book
for count, i in enumerate(toc_copy):
chapter_file = i[2]
# Get split content according to its corresponding id attribute
if '#' in chapter_file:
this_split = chapter_file.split('#')
chapter_file_proper = this_split[0]
this_anchor = this_split[1]
try:
chapter_content = (
self.split_chapters[chapter_file_proper][this_anchor])
except KeyError:
chapter_content = 'Parse Error'
error_string = (
f'Error parsing {self.book_filename}: {chapter_file_proper}')
logger.error(error_string)
# Get content that remained at the end of the pillaging above
elif chapter_file in self.split_chapters.keys():
try:
chapter_content = self.split_chapters[chapter_file]['top_level']
except KeyError:
chapter_content = 'Parse Error'
error_string = (
f'Error parsing {self.book_filename}: {chapter_file}')
logger.error(error_string)
# Vanilla non split chapters
else:
chapter_content = self.get_chapter_content(chapter_file)
self.content[count][2] = chapter_content
# Cleanup content by removing null chapters
unnamed_chapter_title = 1
content_copy = []
for i in self.content:
if i[2]:
chapter_title = i[1]
if not chapter_title:
chapter_title = unnamed_chapter_title
content_copy.append((
i[0], str(chapter_title), i[2]))
unnamed_chapter_title += 1
self.content = content_copy
# Get cover image and put it in its place
# I imagine this involves saying nasty things to it
# There's no point shifting this to the parser
# The performance increase is negligible
cover_image = self.generate_book_cover()
if cover_image:
cover_path = os.path.join(
self.temp_dir, os.path.basename(self.book_filename)) + ' - cover'
with open(cover_path, 'wb') as cover_temp:
cover_temp.write(cover_image)
# This is probably stupid, but I can't stand the idea of
# having to look at two book covers
cover_replacement_conditions = (
self.cover_image_name.lower() + '.jpg' in self.content[0][2].lower(),
self.cover_image_name.lower() + '.png' in self.content[0][2].lower(),
'cover' in self.content[0][1].lower())
if True in cover_replacement_conditions:
logger.info(
f'Replacing cover {cover_replacement_conditions}: {self.book_filename}')
self.content[0] = (
1, 'Cover',
f'<center><img src="{cover_path}" alt="Cover"></center>')
else:
logger.info('Adding cover: ' + self.book_filename)
self.content.insert(
0,
(1, 'Cover',
f'<center><img src="{cover_path}" alt="Cover"></center>'))
def generate_metadata(self):
book_metadata = self.opf_dict['package']['metadata']
def flattener(this_object):
if isinstance(this_object, collections.OrderedDict):
return this_object['#text']
if isinstance(this_object, list):
if isinstance(this_object[0], collections.OrderedDict):
return this_object[0]['#text']
else:
return this_object[0]
if isinstance(this_object, str):
return this_object
# There are no exception types specified below
# This is on purpose and makes me long for the days
# of simpler, happier things.
# Book title
try:
title = flattener(book_metadata['dc:title'])
except:
logger.warning('Title not found: ' + self.book_filename)
title = os.path.splitext(
os.path.basename(self.book_filename))[0]
# Book author
try:
author = flattener(book_metadata['dc:creator'])
except:
logger.warning('Author not found: ' + self.book_filename)
author = 'Unknown'
# Book year
try:
year = int(flattener(book_metadata['dc:date'])[:4])
except:
logger.warning('Year not found: ' + self.book_filename)
year = 9999
# Book isbn
# Both one and multiple schema
isbn = None
try:
scheme = book_metadata['dc:identifier']['@opf:scheme'].lower()
if scheme.lower() == 'isbn':
isbn = book_metadata['dc:identifier']['#text']
except (TypeError, KeyError):
try:
for i in book_metadata['dc:identifier']:
if i['@opf:scheme'].lower() == 'isbn':
isbn = i['#text']
break
except:
logger.warning('ISBN not found: ' + self.book_filename)
# Book tags
try:
tags = book_metadata['dc:subject']
if isinstance(tags, str):
tags = [tags]
except:
tags = []
# Book cover
cover = self.generate_book_cover()
# Named tuple? Named tuple.
Metadata = collections.namedtuple(
'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover'])
self.metadata = Metadata(title, author, year, isbn, tags, cover)
def generate_book_cover(self):
# This is separate because the book cover needs to
# be found and extracted both during addition / reading
book_cover = None
try:
cover_image = [
i['@href'] for i in self.opf_dict['package']['manifest']['item']
if i['@media-type'].split('/')[0] == 'image' and
'cover' in i['@id']][0]
book_cover = self.zip_file.read(self.find_file(cover_image))
except:
logger.warning('Cover not found in opf: ' + self.book_filename)
# Find book cover the hard way
if not book_cover:
biggest_image_size = 0
cover_image = None
for j in self.zip_file.filelist:
if os.path.splitext(j.filename)[1] in ['.jpg', '.jpeg', '.png', '.gif']:
if j.file_size > biggest_image_size:
cover_image = j.filename
biggest_image_size = j.file_size
if cover_image:
book_cover = self.zip_file.read(
self.find_file(cover_image))
if not book_cover:
self.cover_image_name = ''
logger.warning('Cover not found: ' + self.book_filename)
else:
self.cover_image_name = os.path.splitext(
os.path.basename(cover_image))[0]
return book_cover

176
lector/readers/read_fb2.py Normal file
View File

@@ -0,0 +1,176 @@
# This file is a part of Lector, a Qt based ebook reader
# Copyright (C) 2017-2019 BasioMeusPuga
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import base64
import zipfile
import logging
import collections
from bs4 import BeautifulSoup
logger = logging.getLogger(__name__)
class FB2:
def __init__(self, filename):
self.filename = filename
self.zip_file = None
self.xml = None
self.metadata = None
self.content = []
self.generate_references()
def generate_references(self):
if self.filename.endswith('.fb2.zip'):
this_book = zipfile.ZipFile(
self.filename, mode='r', allowZip64=True)
for i in this_book.filelist:
if os.path.splitext(i.filename)[1] == '.fb2':
book_text = this_book.read(i.filename)
break
else:
with open(self.filename, 'r') as book_file:
book_text = book_file.read()
self.xml = BeautifulSoup(book_text, 'lxml')
def generate_metadata(self):
# All metadata can be parsed in one pass
all_tags = self.xml.find('description')
title = all_tags.find('book-title').text
if title == '' or title is None:
title = os.path.splitext(
os.path.basename(self.filename))[0]
author = all_tags.find(
'author').getText(separator=' ').replace('\n', ' ')
if author == '' or author is None:
author = '<Unknown>'
else:
author = author.strip()
# TODO
# Account for other date formats
try:
year = int(all_tags.find('date').text)
except ValueError:
year = 9999
isbn = None
tags = None
cover = self.generate_book_cover()
Metadata = collections.namedtuple(
'Metadata', ['title', 'author', 'year', 'isbn', 'tags', 'cover'])
self.metadata = Metadata(title, author, year, isbn, tags, cover)
def generate_content(self, temp_dir):
# TODO
# Check what's up with recursion levels
# Why is the TypeError happening in get_title
def get_title(element):
this_title = '<No title>'
title_xml = '<No title xml>'
try:
for i in element:
if i.name == 'title':
this_title = i.getText(separator=' ')
this_title = this_title.replace('\n', '').strip()
title_xml = str(i.unwrap())
break
except TypeError:
return None, None
return this_title, title_xml
def recursor(level, element):
children = element.findChildren('section', recursive=False)
if not children and level != 1:
this_title, title_xml = get_title(element)
self.content.append(
[level, this_title, title_xml + str(element)])
else:
for i in children:
recursor(level + 1, i)
first_element = self.xml.find('section') # Recursive find
siblings = list(first_element.findNextSiblings('section', recursive=False))
siblings.insert(0, first_element)
for this_element in siblings:
this_title, title_xml = get_title(this_element)
# Do not add chapter content in case it has sections
# inside it. This prevents having large Book sections that
# have duplicated content
section_children = this_element.findChildren('section')
chapter_text = str(this_element)
if section_children:
chapter_text = this_title
self.content.append([1, this_title, chapter_text])
recursor(1, this_element)
# Extract all images to the temp_dir
for i in self.xml.find_all('binary'):
image_name = i.get('id')
image_path = os.path.join(temp_dir, image_name)
image_string = f'<image l:href="#{image_name}"'
replacement_string = f'<p></p><img src=\"{image_path}\"'
for j in self.content:
j[2] = j[2].replace(
image_string, replacement_string)
try:
image_data = base64.decodebytes(i.text.encode())
with open(image_path, 'wb') as outimage:
outimage.write(image_data)
except AttributeError:
pass
# Insert the book cover at the beginning
cover_image = self.generate_book_cover()
if cover_image:
cover_path = os.path.join(
temp_dir, os.path.basename(self.filename)) + ' - cover'
with open(cover_path, 'wb') as cover_temp:
cover_temp.write(cover_image)
self.content.insert(
0, (1, 'Cover', f'<center><img src="{cover_path}" alt="Cover"></center>'))
def generate_book_cover(self):
cover = None
try:
cover_image_xml = self.xml.find('coverpage')
for i in cover_image_xml:
cover_image_name = i.get('l:href')
cover_image_data = self.xml.find_all('binary')
for i in cover_image_data:
if cover_image_name.endswith(i.get('id')):
cover = base64.decodebytes(i.text.encode())
except (AttributeError, TypeError):
# Catch TypeError in case no images exist in the book
logger.warning('Cover not found: ' + self.filename)
return cover

View File

@@ -10,5 +10,7 @@
<p>Author: BasioMeusPuga <a href="mailto:disgruntled.mob@gmail.com">disgruntled.mob@gmail.com</a></p>
<p>Page:&nbsp;<a href="https://github.com/BasioMeusPuga/Lector">https://github.com/BasioMeusPuga/Lector</a></p>
<p>License: GPLv3&nbsp;<a href="https://www.gnu.org/licenses/gpl-3.0.en.html">https://www.gnu.org/licenses/gpl-3.0.en.html</a></p>
<p>Donate (Paypal): <a href="https://www.paypal.me/supportlector">https://www.paypal.me/supportlector</p>
<p>Donate (Bitcoin): 17jaxj26vFJNqQ2hEVerbBV5fpTusfqFro</p>
<p>&nbsp;</p></body>
</html>

View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'raw/annotations.ui'
#
# Created by: PyQt5 UI code generator 5.10.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(306, 387)
self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setObjectName("gridLayout")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.nameEdit = QtWidgets.QLineEdit(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.nameEdit.sizePolicy().hasHeightForWidth())
self.nameEdit.setSizePolicy(sizePolicy)
self.nameEdit.setObjectName("nameEdit")
self.horizontalLayout_2.addWidget(self.nameEdit)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.typeLabel = QtWidgets.QLabel(Dialog)
self.typeLabel.setObjectName("typeLabel")
self.horizontalLayout.addWidget(self.typeLabel)
self.typeBox = QtWidgets.QComboBox(Dialog)
self.typeBox.setObjectName("typeBox")
self.horizontalLayout.addWidget(self.typeBox)
self.verticalLayout.addLayout(self.horizontalLayout)
self.stackedWidget = QtWidgets.QStackedWidget(Dialog)
self.stackedWidget.setObjectName("stackedWidget")
self.page = QtWidgets.QWidget()
self.page.setObjectName("page")
self.gridLayout_2 = QtWidgets.QGridLayout(self.page)
self.gridLayout_2.setObjectName("gridLayout_2")
self.verticalLayout_12 = QtWidgets.QVBoxLayout()
self.verticalLayout_12.setObjectName("verticalLayout_12")
self.horizontalLayout_15 = QtWidgets.QHBoxLayout()
self.horizontalLayout_15.setObjectName("horizontalLayout_15")
self.foregroundCheck = QtWidgets.QCheckBox(self.page)
self.foregroundCheck.setObjectName("foregroundCheck")
self.horizontalLayout_15.addWidget(self.foregroundCheck)
self.foregroundColorButton = QtWidgets.QPushButton(self.page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.foregroundColorButton.sizePolicy().hasHeightForWidth())
self.foregroundColorButton.setSizePolicy(sizePolicy)
self.foregroundColorButton.setMinimumSize(QtCore.QSize(30, 0))
self.foregroundColorButton.setMaximumSize(QtCore.QSize(45, 40))
self.foregroundColorButton.setText("")
self.foregroundColorButton.setObjectName("foregroundColorButton")
self.horizontalLayout_15.addWidget(self.foregroundColorButton)
self.verticalLayout_12.addLayout(self.horizontalLayout_15)
self.horizontalLayout_16 = QtWidgets.QHBoxLayout()
self.horizontalLayout_16.setObjectName("horizontalLayout_16")
self.highlightCheck = QtWidgets.QCheckBox(self.page)
self.highlightCheck.setObjectName("highlightCheck")
self.horizontalLayout_16.addWidget(self.highlightCheck)
self.highlightColorButton = QtWidgets.QPushButton(self.page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.highlightColorButton.sizePolicy().hasHeightForWidth())
self.highlightColorButton.setSizePolicy(sizePolicy)
self.highlightColorButton.setMinimumSize(QtCore.QSize(30, 24))
self.highlightColorButton.setMaximumSize(QtCore.QSize(45, 40))
self.highlightColorButton.setText("")
self.highlightColorButton.setObjectName("highlightColorButton")
self.horizontalLayout_16.addWidget(self.highlightColorButton)
self.verticalLayout_12.addLayout(self.horizontalLayout_16)
self.horizontalLayout_17 = QtWidgets.QHBoxLayout()
self.horizontalLayout_17.setObjectName("horizontalLayout_17")
self.boldCheck = QtWidgets.QCheckBox(self.page)
self.boldCheck.setObjectName("boldCheck")
self.horizontalLayout_17.addWidget(self.boldCheck)
self.verticalLayout_12.addLayout(self.horizontalLayout_17)
self.horizontalLayout_18 = QtWidgets.QHBoxLayout()
self.horizontalLayout_18.setObjectName("horizontalLayout_18")
self.italicCheck = QtWidgets.QCheckBox(self.page)
self.italicCheck.setObjectName("italicCheck")
self.horizontalLayout_18.addWidget(self.italicCheck)
self.verticalLayout_12.addLayout(self.horizontalLayout_18)
self.horizontalLayout_19 = QtWidgets.QHBoxLayout()
self.horizontalLayout_19.setObjectName("horizontalLayout_19")
self.underlineCheck = QtWidgets.QCheckBox(self.page)
self.underlineCheck.setObjectName("underlineCheck")
self.horizontalLayout_19.addWidget(self.underlineCheck)
self.underlineType = QtWidgets.QComboBox(self.page)
self.underlineType.setObjectName("underlineType")
self.horizontalLayout_19.addWidget(self.underlineType)
self.underlineColorButton = QtWidgets.QPushButton(self.page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.underlineColorButton.sizePolicy().hasHeightForWidth())
self.underlineColorButton.setSizePolicy(sizePolicy)
self.underlineColorButton.setMinimumSize(QtCore.QSize(45, 24))
self.underlineColorButton.setMaximumSize(QtCore.QSize(45, 40))
self.underlineColorButton.setText("")
self.underlineColorButton.setObjectName("underlineColorButton")
self.horizontalLayout_19.addWidget(self.underlineColorButton)
self.verticalLayout_12.addLayout(self.horizontalLayout_19)
self.gridLayout_2.addLayout(self.verticalLayout_12, 0, 0, 1, 1)
self.stackedWidget.addWidget(self.page)
self.verticalLayout.addWidget(self.stackedWidget)
self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem)
self.okButton = QtWidgets.QPushButton(Dialog)
self.okButton.setObjectName("okButton")
self.horizontalLayout_3.addWidget(self.okButton)
self.cancelButton = QtWidgets.QPushButton(Dialog)
self.cancelButton.setObjectName("cancelButton")
self.horizontalLayout_3.addWidget(self.cancelButton)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem1)
self.gridLayout.addLayout(self.horizontalLayout_3, 1, 0, 1, 1)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Annotation Editor"))
self.nameEdit.setPlaceholderText(_translate("Dialog", "Annotation Name"))
self.typeLabel.setText(_translate("Dialog", "Type"))
self.foregroundCheck.setText(_translate("Dialog", "Foreground"))
self.highlightCheck.setText(_translate("Dialog", "Highlight"))
self.boldCheck.setText(_translate("Dialog", "Bold"))
self.italicCheck.setText(_translate("Dialog", "Italic"))
self.underlineCheck.setText(_translate("Dialog", "Underline"))
self.okButton.setText(_translate("Dialog", "OK"))
self.cancelButton.setText(_translate("Dialog", "Cancel"))

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'raw/main.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
# Created by: PyQt5 UI code generator 5.10.1
#
# WARNING! All changes made in this file will be lost!
@@ -35,20 +35,6 @@ class Ui_MainWindow(object):
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
self.gridLayout_4.setSpacing(0)
self.gridLayout_4.setObjectName("gridLayout_4")
self.listView = QtWidgets.QListView(self.listPage)
self.listView.setFrameShape(QtWidgets.QFrame.NoFrame)
self.listView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.listView.setProperty("showDropIndicator", False)
self.listView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.listView.setMovement(QtWidgets.QListView.Static)
self.listView.setProperty("isWrapping", True)
self.listView.setResizeMode(QtWidgets.QListView.Fixed)
self.listView.setLayoutMode(QtWidgets.QListView.SinglePass)
self.listView.setViewMode(QtWidgets.QListView.IconMode)
self.listView.setUniformItemSizes(True)
self.listView.setWordWrap(True)
self.listView.setObjectName("listView")
self.gridLayout_4.addWidget(self.listView, 0, 0, 1, 1)
self.stackedWidget.addWidget(self.listPage)
self.tablePage = QtWidgets.QWidget()
self.tablePage.setObjectName("tablePage")
@@ -56,20 +42,6 @@ class Ui_MainWindow(object):
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
self.gridLayout_3.setSpacing(0)
self.gridLayout_3.setObjectName("gridLayout_3")
self.tableView = QtWidgets.QTableView(self.tablePage)
self.tableView.setFrameShape(QtWidgets.QFrame.Box)
self.tableView.setFrameShadow(QtWidgets.QFrame.Plain)
self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed|QtWidgets.QAbstractItemView.SelectedClicked)
self.tableView.setAlternatingRowColors(True)
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.tableView.setGridStyle(QtCore.Qt.NoPen)
self.tableView.setSortingEnabled(True)
self.tableView.setWordWrap(False)
self.tableView.setObjectName("tableView")
self.tableView.horizontalHeader().setVisible(True)
self.tableView.verticalHeader().setVisible(False)
self.gridLayout_3.addWidget(self.tableView, 0, 0, 1, 1)
self.stackedWidget.addWidget(self.tablePage)
self.gridLayout_2.addWidget(self.stackedWidget, 0, 0, 1, 1)
self.tabWidget.addTab(self.tab, "")

View File

@@ -94,26 +94,26 @@ def generate_pie(progress_percent, temp_dir=None):
return lSvg
def pixmapper(current_chapter, total_chapters, temp_dir, size):
def pixmapper(position_percent, temp_dir, consider_read_at, size):
# A current_chapter of -1 implies the files does not exist
# A chapter number == Total chapters implies the file is unread
return_pixmap = None
# position_percent and consider_read_at are expected as a <1 decimal value
if current_chapter == -1:
return_pixmap = None
consider_read_at = consider_read_at / 100
if position_percent == -1:
return_pixmap = QtGui.QIcon(':/images/error.svg').pixmap(size)
return return_pixmap
if current_chapter == total_chapters:
if position_percent >= consider_read_at: # Consider book read @ this progress
return_pixmap = QtGui.QIcon(':/images/checkmark.svg').pixmap(size)
else:
# TODO
# See if saving the svg to disk can be avoided
# Shift to lines to track progress
# Maybe make the alignment a little more uniform across emblems
progress_percent = int(current_chapter * 100 / total_chapters)
generate_pie(progress_percent, temp_dir)
generate_pie(int(position_percent * 100), temp_dir)
svg_path = os.path.join(temp_dir, 'lector_progress.svg')
return_pixmap = QtGui.QIcon(svg_path).pixmap(size - 4) ## The -4 looks more proportional

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 8 1.0039062 C 4.134 1.0039062 1 4.1380063 1 8.0039062 C 1 11.869906 4.134 15.003906 8 15.003906 C 11.866 15.003906 15 11.869906 15 8.0039062 C 15 4.1380063 11.866 1.0039062 8 1.0039062 z M 8 3.7539062 C 8.69036 3.7539062 9.25 4.3135463 9.25 5.0039062 C 9.25 5.6942662 8.69036 6.2539062 8 6.2539062 C 7.30964 6.2539062 6.75 5.6942662 6.75 5.0039062 C 6.75 4.3135463 7.30964 3.7539062 8 3.7539062 z M 7 7.0039062 L 9 7.0039062 L 9 12.003906 L 7 12.003906 L 7 7.0039062 z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 815 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="m12.213 1c-0.213 0-0.425 0.083-0.59 0.248l-1.6308 1.6387 3.1208 3.1211 1.639-1.6308c0.33-0.33 0.33-0.8497 0-1.1797l-1.949-1.9493c-0.165-0.165-0.378-0.248-0.59-0.248zm-3.34 3.0078l-7.8808 7.8792-0.00001 3.121h3.1211l0.0078-0.008h10.879v-2h-8.8789l5.8709-5.873-3.119-3.1192z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 617 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 3 6 L 8 11 L 13 6 L 3 6 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 372 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 8 5 L 3 10 L 13 10 L 8 5 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 22 22"
version="1.1"
id="svg7"
sodipodi:docname="invert.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata11">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="855"
inkscape:window-height="480"
id="namedview9"
showgrid="false"
inkscape:zoom="10.727273"
inkscape:cx="11"
inkscape:cy="11"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#5294e2; }
</style>
</defs>
<path
style="fill:#5c616c;fill-opacity:1"
class="ColorScheme-Text"
d="M 2 1 L 2 15 L 9 15 L 9 13 A 5 5 0 0 1 4 8 A 5 5 0 0 1 9 3 L 9 1 L 2 1 z M 9 3 L 9 13 C 11.7614 13 14 10.7614 14 8 C 14 5.2386 11.7614 3 9 3 z"
transform="translate(3 3)"
id="path5" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
version="1.1"
viewBox="0 0 22 22"
id="svg7"
sodipodi:docname="manga.svg"
inkscape:version="0.92.2 2405546, 2018-03-11">
<metadata
id="metadata11">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1586"
inkscape:window-height="856"
id="namedview9"
showgrid="false"
inkscape:zoom="10.727273"
inkscape:cx="11"
inkscape:cy="11"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">
.ColorScheme-Text { color:#6e6e6e; } .ColorScheme-Highlight { color:#5294e2; }
</style>
</defs>
<path
style="fill:#5c616c;fill-opacity:1"
class="ColorScheme-Text"
d="M 19,11 14,6 V 8 H 8 V 6 l -5,5 5,5 v -2 h 6 v 2 z"
id="path5" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 22 22"
version="1.1"
id="svg7"
sodipodi:docname="page-double.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata11">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1043"
id="namedview9"
showgrid="false"
inkscape:zoom="10.727273"
inkscape:cx="11"
inkscape:cy="11"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; }
</style>
</defs>
<path
style="fill:#5c616c;fill-opacity:1"
class="ColorScheme-Text"
d="M 1 1 L 1 15 L 15 15 L 15 1 L 1 1 z M 3 3 L 7 3 L 7 13 L 3 13 L 3 3 z M 9 3 L 13 3 L 13 13 L 9 13 L 9 3 z"
transform="translate(3 3)"
id="path5" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 22 22"
version="1.1"
id="svg7"
sodipodi:docname="page-flow.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata11">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1043"
id="namedview9"
showgrid="false"
inkscape:zoom="15.170655"
inkscape:cx="-1.2825494"
inkscape:cy="5.330307"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">
.ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; }
</style>
</defs>
<path
style="fill:#5c616c;fill-opacity:1"
class="ColorScheme-Text"
d="M 2 1 L 2 15 L 14 15 L 14 1 L 2 1 z M 4 3 L 12 3 L 12 13 L 4 13 L 4 3 z"
transform="translate(3 3)"
id="path5" />
<g
transform="matrix(0.64844409,0,0,0.64844409,5.8379769,5.8670545)"
id="g7"
style="fill:#5c616c;fill-opacity:1">
<path
inkscape:connector-curvature="0"
class="ColorScheme-Text"
d="m 7,2 v 8 L 3.5,6.5 2,8 8,14 14,8 12.5,6.5 9,10 V 2 Z"
style="color:#dfdfdf;fill:#5c616c;fill-opacity:1"
id="path5-3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

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 2 1 L 2 15 L 14 15 L 14 1 L 2 1 z M 4 3 L 12 3 L 12 13 L 4 13 L 4 3 z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 416 B

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 22 22"
version="1.1"
id="svg7"
sodipodi:docname="rotate-left.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata11">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="855"
inkscape:window-height="480"
id="namedview9"
showgrid="false"
inkscape:zoom="10.727273"
inkscape:cx="11"
inkscape:cy="11"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; }
</style>
</defs>
<path
style="fill:#5c616c;fill-opacity:1"
class="ColorScheme-Text"
d="M 8.0292969 0.001953125 L 4.0292969 3.0019531 L 8.0292969 6.0019531 L 8.0292969 4.0019531 C 10.238397 4.0019531 12.029297 5.7928531 12.029297 8.0019531 C 12.029297 10.211053 10.238397 12.001953 8.0292969 12.001953 C 5.8201969 12.001953 4.0292969 10.211053 4.0292969 8.0019531 A 1 1 0 0 0 3.0292969 7.0019531 A 1 1 0 0 0 2.0292969 8.0019531 A 1 1 0 0 0 2.0351562 8.1015625 C 2.0889563 11.368762 4.7491969 14.001953 8.0292969 14.001953 C 11.342997 14.001953 14.029297 11.315653 14.029297 8.0019531 C 14.029297 4.6882531 11.342997 2.0019531 8.0292969 2.0019531 L 8.0292969 0.001953125 z"
transform="translate(3 3)"
id="path5" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 22 22"
version="1.1"
id="svg7"
sodipodi:docname="rotate-right.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata11">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="855"
inkscape:window-height="480"
id="namedview9"
showgrid="false"
inkscape:zoom="10.727273"
inkscape:cx="11"
inkscape:cy="11"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">
.ColorScheme-Text { color:#444444; } .ColorScheme-Highlight { color:#4285f4; }
</style>
</defs>
<path
style="fill:#5c616c;fill-opacity:1"
class="ColorScheme-Text"
d="M 7.9804688 0.001953125 L 7.9804688 2.0019531 C 4.6667688 2.0019531 1.9804688 4.6882531 1.9804688 8.0019531 C 1.9804688 11.315653 4.6667688 14.001953 7.9804688 14.001953 C 11.260569 14.001953 13.920809 11.368762 13.974609 8.1015625 A 1 1 0 0 0 13.980469 8.0019531 A 1 1 0 0 0 12.980469 7.0019531 A 1 1 0 0 0 11.980469 8.0019531 C 11.980469 10.211053 10.189569 12.001953 7.9804688 12.001953 C 5.7713688 12.001953 3.9804688 10.211053 3.9804688 8.0019531 C 3.9804688 5.7928531 5.7713688 4.0019531 7.9804688 4.0019531 L 7.9804688 6.0019531 L 11.980469 3.0019531 L 7.9804688 0.001953125 z"
transform="translate(3 3)"
id="path5" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
version="1.1"
id="svg4"
sodipodi:docname="search-case.svg"
inkscape:version="0.92.2 2405546, 2018-03-11">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1480"
inkscape:window-height="750"
id="namedview6"
showgrid="false"
inkscape:zoom="14.75"
inkscape:cx="-6.5084746"
inkscape:cy="8"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg4" />
<path
style="fill:#5c616c;fill-opacity:1"
d="M 4.7890625,2 1,13 h 1.90625 l 0.6796875,-2 h 3.8300781 l 0.6875,2 H 10 L 6.2109375,2 Z M 5.4980469,5.4375 6.6835938,9 H 4.3144531 Z M 11.5,8 v 1 h 3 C 14.715,9 15,9.305 15,9.5 V 10 h -2.5 c -0.46,0 -0.87,0.189375 -1.125,0.484375 C 11.12,10.774375 11,11.14 11,11.5 c 0,0.36 0.135625,0.725625 0.390625,1.015625 C 11.645625,12.805625 12.045,13 12.5,13 H 16 V 9.5 C 16,8.685 15.34,8 14.5,8 Z m 1,3 H 15 v 1 H 12.5 C 12.3,12 12.215625,11.944375 12.140625,11.859375 12.065625,11.774375 12,11.64 12,11.5 12,11.36 12.05,11.225625 12.125,11.140625 12.2,11.060625 12.29,11 12.5,11 Z"
id="path2" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
version="1.1"
id="svg4"
sodipodi:docname="search-word.svg"
inkscape:version="0.92.2 2405546, 2018-03-11">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1043"
id="namedview6"
showgrid="false"
inkscape:zoom="45.254834"
inkscape:cx="6.5123537"
inkscape:cy="6.8917559"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
style="fill:#5c616c;fill-opacity:1"
d="M 3,3 C 1.02036,3 -9.9999927e-7,4.1718311 0,6.6074219 V 8.5 A 1.5,1.5 0 0 0 1.5,10 1.5,1.5 0 0 0 3,8.5 1.5,1.5 0 0 0 1.5097656,7 l -0.00781,-0.4023438 c 0,-0.948207 0.1116447,-1.5979182 0.3378907,-1.9511718 C 2.0660618,4.29323 2.453243,4.0371834 3,4 Z M 7,3 C 5.02036,3 3.999999,4.1718311 4,6.6074219 V 8.5 A 1.5,1.5 0 0 0 5.5,10 1.5,1.5 0 0 0 7,8.5 1.5,1.5 0 0 0 5.5097656,7 l -0.00781,-0.4023438 c 0,-0.948207 0.1116447,-1.5979182 0.3378907,-1.9511718 C 6.0660608,4.29323 6.453243,4.0371834 7,4 Z M 9.5,5 A 1.5,1.5 0 0 0 8,6.5 1.5,1.5 0 0 0 9.490234,8 l 0.00781,0.4023438 c 0,0.948207 -0.111645,1.5979182 -0.337891,1.9511722 C 8.9339389,10.70677 8.5467573,10.962817 8,11 v 1 c 1.97964,0 3.000001,-1.171831 3,-3.6074219 V 6.5 A 1.5,1.5 0 0 0 9.5,5 Z m 4,0 A 1.5,1.5 0 0 0 12,6.5 1.5,1.5 0 0 0 13.490234,8 l 0.0078,0.4023438 c 0,0.948207 -0.111645,1.5979182 -0.337891,1.9511722 C 12.933938,10.70677 12.546757,10.962817 12,11 v 1 c 1.97964,0 3.000001,-1.171831 3,-3.6074219 V 6.5 A 1.5,1.5 0 0 0 13.5,5 Z"
id="path2" />
<path
style="fill:#5c616c;stroke:none;stroke-width:0.06779661;fill-opacity:1"
d="M 8.040678,11.489133 V 11.01394 l 0.2542373,-0.0411 C 8.6241141,10.91962 9.0049959,10.657098 9.1919904,10.354534 9.3774432,10.054466 9.5267422,9.2371719 9.5298381,8.5050867 9.5321781,7.9511596 9.5305475,7.9457647 9.3605343,7.9457647 8.6870054,7.9457647 8.0454876,7.2386514 8.0421669,6.4925935 8.0367069,5.2649491 9.4833138,4.5924333 10.437555,5.379 c 0.470567,0.3878808 0.518377,0.5947737 0.518377,2.2432227 0,0.9924588 -0.03143,1.6284257 -0.09756,1.9740504 -0.267079,1.3958959 -1.1175811,2.2244799 -2.3939662,2.3322709 l -0.4237288,0.03579 z"
id="path4524"
inkscape:connector-curvature="0" />
<path
style="fill:#5c616c;stroke:none;stroke-width:0.00390625;fill-opacity:1"
d="m 9.7117131,11.513295 c 0.00204,-0.0018 0.023926,-0.01759 0.048633,-0.03507 0.1798996,-0.127248 0.3444879,-0.28336 0.4858319,-0.460812 0.252616,-0.317149 0.444866,-0.723077 0.559095,-1.1805105 0.06615,-0.2648918 0.09348,-0.4533196 0.116987,-0.8066406 C 10.94809,8.6420545 10.9575,8.1721993 10.95441,7.4247931 10.952,6.840388 10.947836,6.672371 10.92997,6.438465 10.924141,6.3621478 10.9106,6.2310896 10.906461,6.2109259 l -0.0022,-0.010742 h 0.0313 c 0.0366,0 0.0324,-0.00607 0.0433,0.0625 0.01847,0.1161553 0.01782,0.070812 0.01782,1.25 0,1.1705155 -1.65e-4,1.1838055 -0.01779,1.4296875 -0.05607,0.7822203 -0.232739,1.4031726 -0.538111,1.8913516 -0.157745,0.252177 -0.36026,0.471333 -0.5979529,0.647089 l -0.048386,0.03578 h -0.043215 c -0.023798,0 -0.041548,-0.0015 -0.039504,-0.0033 z"
id="path4526"
inkscape:connector-curvature="0" />
<path
style="fill:#5c616c;stroke:none;stroke-width:0.00390625;fill-opacity:1"
d="m 8.0037065,11.500359 v -0.499846 l 0.038086,-0.0025 c 0.052532,-0.0035 0.1567951,-0.0193 0.2234149,-0.03389 C 8.640889,10.881833 8.9448125,10.678717 9.1476483,10.374368 9.3523256,10.067256 9.4641833,9.5521406 9.492672,8.7855008 L 9.497502,8.655618 H 9.512387 9.527272 L 9.524992,8.705423 C 9.5049185,9.1437939 9.4502274,9.5471986 9.3653315,9.8830268 9.3036893,10.126869 9.2445481,10.277176 9.1635619,10.395823 9.0101537,10.620571 8.748788,10.822299 8.4830209,10.92108 c -0.078653,0.02923 -0.1439508,0.04431 -0.30158,0.06961 l -0.140625,0.02258 -9.922e-4,0.475456 c -9.556e-4,0.457983 -7.301e-4,0.475456 0.00614,0.475456 0.012211,0 0.421374,-0.03493 0.483138,-0.04125 0.3949367,-0.04038 0.7679272,-0.159625 1.078125,-0.344663 0.023633,-0.0141 0.0556,-0.03409 0.071038,-0.04442 l 0.028069,-0.01879 0.04029,0.003 0.04029,0.003 -0.046244,0.03035 c -0.2869093,0.188322 -0.6383031,0.321037 -1.0300398,0.389028 -0.189836,0.03295 -0.3718111,0.04969 -0.6219586,0.0572 l -0.084961,0.0026 z"
id="path4528"
inkscape:connector-curvature="0" />
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

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 2 0 C 0.892 0 0 0.892 0 2 L 0 14 C 0 15.108 0.892 16 2 16 L 14 16 C 15.108 16 16 15.108 16 14 L 16 2 C 16 0.892 15.108 0 14 0 L 2 0 z M 3.7148438 2 L 12.285156 2 C 13.235156 2 14 2.7651437 14 3.7148438 L 14 12.285156 C 14 13.235156 13.235156 14 12.285156 14 L 3.7148438 14 C 2.7651438 14 2 13.235156 2 12.285156 L 2 3.7148438 C 2 2.7651438 2.7651437 2 3.7148438 2 z M 6.7402344 3 L 6.6289062 4.3164062 A 3.964 3.9286 0 0 0 5.4707031 4.9804688 L 4.2617188 4.4179688 L 3.0019531 6.5820312 L 4.0976562 7.3378906 A 3.964 3.9286 0 0 0 4.0371094 8 A 3.964 3.9286 0 0 0 4.0957031 8.6660156 L 3.0019531 9.4179688 L 4.2617188 11.582031 L 5.4667969 11.019531 A 3.964 3.9286 0 0 0 6.6289062 11.679688 L 6.7402344 13 L 9.2617188 13 L 9.3730469 11.683594 A 3.964 3.9286 0 0 0 10.53125 11.019531 L 11.740234 11.582031 L 13.001953 9.4179688 L 11.904297 8.6621094 A 3.964 3.9286 0 0 0 11.964844 8 A 3.964 3.9286 0 0 0 11.908203 7.3339844 L 13.001953 6.5820312 L 11.740234 4.4179688 L 10.535156 4.9804688 A 3.964 3.9286 0 0 0 9.3730469 4.3203125 L 9.2617188 3 L 6.7402344 3 z M 8.0019531 6.5722656 A 1.4414 1.4286 0 0 1 9.4433594 8 A 1.4414 1.4286 0 0 1 8.0019531 9.4277344 A 1.4414 1.4286 0 0 1 6.5605469 8 A 1.4414 1.4286 0 0 1 8.0019531 6.5722656 z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

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 8 0.99609375 C 4.134 0.99609375 1 4.1300937 1 7.9960938 C 1 11.862094 4.134 14.996094 8 14.996094 C 11.866 14.996094 15 11.862094 15 7.9960938 C 15 4.1300937 11.866 0.99609375 8 0.99609375 z M 7.5 2.9335938 C 7.5669 2.9265937 7.65125 2.9375937 7.71875 2.9335938 C 7.72675 2.9655938 7.67005 3.0794638 7.59375 3.2460938 C 7.10789 4.3074937 7.08033 5.5504437 7.53125 6.2148438 C 7.61285 6.3353038 7.6875 6.4499437 7.6875 6.4648438 C 7.6875 6.4797438 7.5995 6.4960938 7.5 6.4960938 C 7.26642 6.4960938 7.04538 6.3537238 6.59375 5.9960938 C 6.39312 5.8372237 6.1323 5.7037938 6.03125 5.6835938 C 5.87257 5.6518937 5.83028 5.6657938 5.625 5.8710938 C 5.43401 6.0620537 5.375 6.1650237 5.375 6.3398438 C 5.375 7.0027837 6.16208 7.5297437 7.625 7.8398438 C 9.6117 8.2609137 10.10145 8.6389138 10.15625 9.6835938 C 10.22505 10.993594 9.5276 11.981394 8 12.746094 C 7.81767 12.837394 7.7015 12.872844 7.625 12.902344 C 7.5911 12.899344 7.56505 12.905344 7.53125 12.902344 C 7.51825 12.861844 7.5 12.767884 7.5 12.589844 C 7.5 11.894064 7.22575 11.177844 6.8125 10.777344 C 6.70157 10.669824 6.39098 10.441994 6.125 10.277344 C 5.85903 10.112704 5.59105 9.9214438 5.53125 9.8398438 C 5.43215 9.7044337 5.42386 9.6212437 5.5 9.3710938 C 5.63876 8.9142237 5.80392 8.6597637 6.125 8.3710938 C 6.29333 8.2197537 6.46271 8.0928437 6.5 8.0898438 C 6.5373 8.0868438 6.28485 8.0110437 5.90625 7.9335938 C 5.52767 7.8559938 4.97383 7.6934738 4.6875 7.5898438 C 4.16392 7.4003938 3.457 7.0026837 3.1875 6.7148438 C 3.1761 6.7026437 3.16615 6.6943938 3.15625 6.6835938 C 3.54238 5.1454938 4.626 3.8848438 6.0625 3.2773438 C 6.36307 3.1502138 6.67292 3.0629938 7 2.9960938 C 7.16292 2.9627938 7.33178 2.9506937 7.5 2.9335938 z M 12.1875 5.2773438 C 12.30495 5.3499437 12.74841 6.3093438 12.875 6.7773438 C 13.03844 7.3815337 13.02661 8.4271437 12.875 9.0273438 C 12.8173 9.2557838 12.74335 9.4694937 12.71875 9.4960938 C 12.69415 9.5226938 12.60494 9.3695637 12.5 9.1835938 C 12.39505 8.9976538 12.05984 8.6025437 11.78125 8.3085938 C 10.97711 7.4600637 10.85066 7.0170437 11.1875 6.3398438 C 11.35737 5.9983538 12.0966 5.2212438 12.1875 5.2773438 z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

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 8 1.0039062 C 4.134 1.0039062 1 4.1380063 1 8.0039062 C 1 11.869906 4.134 15.003906 8 15.003906 C 11.866 15.003906 15 11.869906 15 8.0039062 C 15 4.1380063 11.866 1.0039062 8 1.0039062 z M 8 3.7539062 C 8.69036 3.7539062 9.25 4.3135463 9.25 5.0039062 C 9.25 5.6942662 8.69036 6.2539062 8 6.2539062 C 7.30964 6.2539062 6.75 5.6942662 6.75 5.0039062 C 6.75 4.3135463 7.30964 3.7539062 8 3.7539062 z M 7 7.0039062 L 9 7.0039062 L 9 12.003906 L 7 12.003906 L 7 7.0039062 z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 815 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="m12.213 1c-0.213 0-0.425 0.083-0.59 0.248l-1.6308 1.6387 3.1208 3.1211 1.639-1.6308c0.33-0.33 0.33-0.8497 0-1.1797l-1.949-1.9493c-0.165-0.165-0.378-0.248-0.59-0.248zm-3.34 3.0078l-7.8808 7.8792-0.00001 3.121h3.1211l0.0078-0.008h10.879v-2h-8.8789l5.8709-5.873-3.119-3.1192z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 617 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:#d3dae3; } .ColorScheme-Highlight { color:#5294e2; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 3 6 L 8 11 L 13 6 L 3 6 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 372 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:#d3dae3; } .ColorScheme-Highlight { color:#5294e2; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 8 5 L 3 10 L 13 10 L 8 5 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 373 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:#dfdfdf; } .ColorScheme-Highlight { color:#5294e2; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 2 1 L 2 15 L 9 15 L 9 13 A 5 5 0 0 1 4 8 A 5 5 0 0 1 9 3 L 9 1 L 2 1 z M 9 3 L 9 13 C 11.7614 13 14 10.7614 14 8 C 14 5.2386 11.7614 3 9 3 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 487 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" version="1.1" viewBox="0 0 22 22">
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#5294e2; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 19,11 14,6 V 8 H 8 V 6 l -5,5 5,5 v -2 h 6 v 2 z"/>
</svg>

After

Width:  |  Height:  |  Size: 382 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 1 1 L 1 15 L 15 15 L 15 1 L 1 1 z M 3 3 L 7 3 L 7 13 L 3 13 L 3 3 z M 9 3 L 13 3 L 13 13 L 9 13 L 9 3 z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 22 22"
version="1.1"
id="svg7"
sodipodi:docname="page-flow.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata11">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1043"
id="namedview9"
showgrid="false"
inkscape:zoom="15.170655"
inkscape:cx="-1.2825494"
inkscape:cy="5.330307"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">
.ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; }
</style>
</defs>
<path
style="fill:currentColor"
class="ColorScheme-Text"
d="M 2 1 L 2 15 L 14 15 L 14 1 L 2 1 z M 4 3 L 12 3 L 12 13 L 4 13 L 4 3 z"
transform="translate(3 3)"
id="path5" />
<g
transform="matrix(0.64844409,0,0,0.64844409,5.8379769,5.8670545)"
id="g7">
<path
inkscape:connector-curvature="0"
class="ColorScheme-Text"
d="m 7,2 v 8 L 3.5,6.5 2,8 8,14 14,8 12.5,6.5 9,10 V 2 Z"
style="color:#dfdfdf;fill:currentColor"
id="path5-3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

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:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 2 1 L 2 15 L 14 15 L 14 1 L 2 1 z M 4 3 L 12 3 L 12 13 L 4 13 L 4 3 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 416 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:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 8.0292969 0.001953125 L 4.0292969 3.0019531 L 8.0292969 6.0019531 L 8.0292969 4.0019531 C 10.238397 4.0019531 12.029297 5.7928531 12.029297 8.0019531 C 12.029297 10.211053 10.238397 12.001953 8.0292969 12.001953 C 5.8201969 12.001953 4.0292969 10.211053 4.0292969 8.0019531 A 1 1 0 0 0 3.0292969 7.0019531 A 1 1 0 0 0 2.0292969 8.0019531 A 1 1 0 0 0 2.0351562 8.1015625 C 2.0889563 11.368762 4.7491969 14.001953 8.0292969 14.001953 C 11.342997 14.001953 14.029297 11.315653 14.029297 8.0019531 C 14.029297 4.6882531 11.342997 2.0019531 8.0292969 2.0019531 L 8.0292969 0.001953125 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 928 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:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; }
</style>
</defs>
<path style="fill:currentColor" class="ColorScheme-Text" d="M 7.9804688 0.001953125 L 7.9804688 2.0019531 C 4.6667688 2.0019531 1.9804688 4.6882531 1.9804688 8.0019531 C 1.9804688 11.315653 4.6667688 14.001953 7.9804688 14.001953 C 11.260569 14.001953 13.920809 11.368762 13.974609 8.1015625 A 1 1 0 0 0 13.980469 8.0019531 A 1 1 0 0 0 12.980469 7.0019531 A 1 1 0 0 0 11.980469 8.0019531 C 11.980469 10.211053 10.189569 12.001953 7.9804688 12.001953 C 5.7713688 12.001953 3.9804688 10.211053 3.9804688 8.0019531 C 3.9804688 5.7928531 5.7713688 4.0019531 7.9804688 4.0019531 L 7.9804688 6.0019531 L 11.980469 3.0019531 L 7.9804688 0.001953125 z" transform="translate(3 3)"/>
</svg>

After

Width:  |  Height:  |  Size: 928 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<path style="fill:#dfdfdf" d="M 4.7890625,2 1,13 h 1.90625 l 0.6796875,-2 h 3.8300781 l 0.6875,2 H 10 L 6.2109375,2 Z M 5.4980469,5.4375 6.6835938,9 H 4.3144531 Z M 11.5,8 v 1 h 3 C 14.715,9 15,9.305 15,9.5 V 10 h -2.5 c -0.46,0 -0.87,0.189375 -1.125,0.484375 C 11.12,10.774375 11,11.14 11,11.5 c 0,0.36 0.135625,0.725625 0.390625,1.015625 C 11.645625,12.805625 12.045,13 12.5,13 H 16 V 9.5 C 16,8.685 15.34,8 14.5,8 Z m 1,3 H 15 v 1 H 12.5 C 12.3,12 12.215625,11.944375 12.140625,11.859375 12.065625,11.774375 12,11.64 12,11.5 12,11.36 12.05,11.225625 12.125,11.140625 12.2,11.060625 12.29,11 12.5,11 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 693 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<path style="fill:#dfdfdf" d="M 3,3 C 1.02036,3 -9.9999927e-7,4.1718311 0,6.6074219 V 8.5 A 1.5,1.5 0 0 0 1.5,10 1.5,1.5 0 0 0 3,8.5 1.5,1.5 0 0 0 1.5097656,7 l -0.00781,-0.4023438 c 0,-0.948207 0.1116447,-1.5979182 0.3378907,-1.9511718 C 2.0660618,4.29323 2.453243,4.0371834 3,4 Z M 7,3 C 5.02036,3 3.999999,4.1718311 4,6.6074219 V 8.5 A 1.5,1.5 0 0 0 5.5,10 1.5,1.5 0 0 0 7,8.5 1.5,1.5 0 0 0 5.5097656,7 l -0.00781,-0.4023438 c 0,-0.948207 0.1116447,-1.5979182 0.3378907,-1.9511718 C 6.0660608,4.29323 6.453243,4.0371834 7,4 Z M 9.5,5 A 1.5,1.5 0 0 0 8,6.5 1.5,1.5 0 0 0 9.490234,8 l 0.00781,0.4023438 c 0,0.948207 -0.111645,1.5979182 -0.337891,1.9511722 C 8.9339389,10.70677 8.5467573,10.962817 8,11 v 1 c 1.97964,0 3.000001,-1.171831 3,-3.6074219 V 6.5 A 1.5,1.5 0 0 0 9.5,5 Z m 4,0 A 1.5,1.5 0 0 0 12,6.5 1.5,1.5 0 0 0 13.490234,8 l 0.0078,0.4023438 c 0,0.948207 -0.111645,1.5979182 -0.337891,1.9511722 C 12.933938,10.70677 12.546757,10.962817 12,11 v 1 c 1.97964,0 3.000001,-1.171831 3,-3.6074219 V 6.5 A 1.5,1.5 0 0 0 13.5,5 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

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 2 0 C 0.892 0 0 0.892 0 2 L 0 14 C 0 15.108 0.892 16 2 16 L 14 16 C 15.108 16 16 15.108 16 14 L 16 2 C 16 0.892 15.108 0 14 0 L 2 0 z M 3.7148438 2 L 12.285156 2 C 13.235156 2 14 2.7651437 14 3.7148438 L 14 12.285156 C 14 13.235156 13.235156 14 12.285156 14 L 3.7148438 14 C 2.7651438 14 2 13.235156 2 12.285156 L 2 3.7148438 C 2 2.7651438 2.7651437 2 3.7148438 2 z M 6.7402344 3 L 6.6289062 4.3164062 A 3.964 3.9286 0 0 0 5.4707031 4.9804688 L 4.2617188 4.4179688 L 3.0019531 6.5820312 L 4.0976562 7.3378906 A 3.964 3.9286 0 0 0 4.0371094 8 A 3.964 3.9286 0 0 0 4.0957031 8.6660156 L 3.0019531 9.4179688 L 4.2617188 11.582031 L 5.4667969 11.019531 A 3.964 3.9286 0 0 0 6.6289062 11.679688 L 6.7402344 13 L 9.2617188 13 L 9.3730469 11.683594 A 3.964 3.9286 0 0 0 10.53125 11.019531 L 11.740234 11.582031 L 13.001953 9.4179688 L 11.904297 8.6621094 A 3.964 3.9286 0 0 0 11.964844 8 A 3.964 3.9286 0 0 0 11.908203 7.3339844 L 13.001953 6.5820312 L 11.740234 4.4179688 L 10.535156 4.9804688 A 3.964 3.9286 0 0 0 9.3730469 4.3203125 L 9.2617188 3 L 6.7402344 3 z M 8.0019531 6.5722656 A 1.4414 1.4286 0 0 1 9.4433594 8 A 1.4414 1.4286 0 0 1 8.0019531 9.4277344 A 1.4414 1.4286 0 0 1 6.5605469 8 A 1.4414 1.4286 0 0 1 8.0019531 6.5722656 z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

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 8 0.99609375 C 4.134 0.99609375 1 4.1300937 1 7.9960938 C 1 11.862094 4.134 14.996094 8 14.996094 C 11.866 14.996094 15 11.862094 15 7.9960938 C 15 4.1300937 11.866 0.99609375 8 0.99609375 z M 7.5 2.9335938 C 7.5669 2.9265937 7.65125 2.9375937 7.71875 2.9335938 C 7.72675 2.9655938 7.67005 3.0794638 7.59375 3.2460938 C 7.10789 4.3074937 7.08033 5.5504437 7.53125 6.2148438 C 7.61285 6.3353038 7.6875 6.4499437 7.6875 6.4648438 C 7.6875 6.4797438 7.5995 6.4960938 7.5 6.4960938 C 7.26642 6.4960938 7.04538 6.3537238 6.59375 5.9960938 C 6.39312 5.8372237 6.1323 5.7037938 6.03125 5.6835938 C 5.87257 5.6518937 5.83028 5.6657938 5.625 5.8710938 C 5.43401 6.0620537 5.375 6.1650237 5.375 6.3398438 C 5.375 7.0027837 6.16208 7.5297437 7.625 7.8398438 C 9.6117 8.2609137 10.10145 8.6389138 10.15625 9.6835938 C 10.22505 10.993594 9.5276 11.981394 8 12.746094 C 7.81767 12.837394 7.7015 12.872844 7.625 12.902344 C 7.5911 12.899344 7.56505 12.905344 7.53125 12.902344 C 7.51825 12.861844 7.5 12.767884 7.5 12.589844 C 7.5 11.894064 7.22575 11.177844 6.8125 10.777344 C 6.70157 10.669824 6.39098 10.441994 6.125 10.277344 C 5.85903 10.112704 5.59105 9.9214438 5.53125 9.8398438 C 5.43215 9.7044337 5.42386 9.6212437 5.5 9.3710938 C 5.63876 8.9142237 5.80392 8.6597637 6.125 8.3710938 C 6.29333 8.2197537 6.46271 8.0928437 6.5 8.0898438 C 6.5373 8.0868438 6.28485 8.0110437 5.90625 7.9335938 C 5.52767 7.8559938 4.97383 7.6934738 4.6875 7.5898438 C 4.16392 7.4003938 3.457 7.0026837 3.1875 6.7148438 C 3.1761 6.7026437 3.16615 6.6943938 3.15625 6.6835938 C 3.54238 5.1454938 4.626 3.8848438 6.0625 3.2773438 C 6.36307 3.1502138 6.67292 3.0629938 7 2.9960938 C 7.16292 2.9627938 7.33178 2.9506937 7.5 2.9335938 z M 12.1875 5.2773438 C 12.30495 5.3499437 12.74841 6.3093438 12.875 6.7773438 C 13.03844 7.3815337 13.02661 8.4271437 12.875 9.0273438 C 12.8173 9.2557838 12.74335 9.4694937 12.71875 9.4960938 C 12.69415 9.5226938 12.60494 9.3695637 12.5 9.1835938 C 12.39505 8.9976538 12.05984 8.6025437 11.78125 8.3085938 C 10.97711 7.4600637 10.85066 7.0170437 11.1875 6.3398438 C 11.35737 5.9983538 12.0966 5.2212438 12.1875 5.2773438 z" transform="translate(4 4)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>306</width>
<height>387</height>
</rect>
</property>
<property name="windowTitle">
<string>Annotation Editor</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="nameEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string>Annotation Name</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="typeLabel">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="typeBox"/>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_15">
<item>
<widget class="QCheckBox" name="foregroundCheck">
<property name="text">
<string>Foreground</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="foregroundColorButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>40</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QCheckBox" name="highlightCheck">
<property name="text">
<string>Highlight</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="highlightColorButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>40</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_17">
<item>
<widget class="QCheckBox" name="boldCheck">
<property name="text">
<string>Bold</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_18">
<item>
<widget class="QCheckBox" name="italicCheck">
<property name="text">
<string>Italic</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_19">
<item>
<widget class="QCheckBox" name="underlineCheck">
<property name="text">
<string>Underline</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="underlineType"/>
</item>
<item>
<widget class="QPushButton" name="underlineColorButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>45</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>40</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2019 BasioMeusPuga <https://github.com/BasioMeusPuga> -->
<!-- See <https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html> for an overview of the AppStream metainfo.xml spec. -->
<component type="desktop-application">
<id>io.github.BasioMeusPuga.Lector</id>
<metadata_license>FSFAP</metadata_license>
<project_license>GPL-3.0+</project_license>
<name>Lector</name>
<summary>Ebook reader and collection manager</summary>
<description>
<p>
Lector is an ebook reader and collection manager. It offers a
full-screen distraction-free view, document highlighting and
annotations, a built-in dictionary, bookmarks, and multiple profiles for
changing the way the books are presented. Lector can also edit metadata,
so you can correct information about the books, and add keywords to make
them easier to find.
</p>
<p>
It supports the following file formats:
</p>
<ul>
<li>PDF</li>
<li>EPUB</li>
<li>DjVu</li>
<li>FictionBook (.fb2)</li>
<li>Mobipocket (.mobi)</li>
<li>Amazon Kindle (.azw, .azw3, .azw4)</li>
<li>Comic book archives (.cbr, .cbz)</li>
<li>Markdown</li>
</ul>
</description>
<launchable type="desktop-id">io.github.BasioMeusPuga.Lector.desktop</launchable>
<screenshots>
<screenshot type="default">
<caption>Main window</caption>
<image>https://camo.githubusercontent.com/9e2f7346616dfdb67a77c2e6b5db148d9b4669ca/68747470733a2f2f692e696d6775722e636f6d2f35313668526b532e706e67</image>
</screenshot>
<screenshot>
<caption>Table view</caption>
<image>https://camo.githubusercontent.com/66cb4f09a81d164af50180eaa024f8e116c83815/68747470733a2f2f692e696d6775722e636f6d2f6f39416e3741522e706e67</image>
</screenshot>
<screenshot>
<caption>Book reading view, in optional Solarized display profile</caption>
<image>https://camo.githubusercontent.com/1b60a0c27383715e59ccfd9d2585544d303e08d9/68747470733a2f2f692e696d6775722e636f6d2f495447363346632e706e67</image>
</screenshot>
<screenshot>
<caption>Distraction-free view</caption>
<image>https://camo.githubusercontent.com/3289abadb668ad3e53b8ba779b52768b14b25bcf/68747470733a2f2f692e696d6775722e636f6d2f67384c747570792e706e67</image>
</screenshot>
<screenshot>
<caption>Annotation support</caption>
<image>https://camo.githubusercontent.com/80e7423b488e59ed2c05e556b4918c25522edfd1/68747470733a2f2f692e696d6775722e636f6d2f674c4b323946342e706e67</image>
</screenshot>
<screenshot>
<caption>Comic reading view</caption>
<image>https://camo.githubusercontent.com/81aaa963c6e9ed19a1605e2f2b017ce8c83ca656/68747470733a2f2f692e696d6775722e636f6d2f7276765451434d2e706e67</image>
</screenshot>
<screenshot>
<caption>Bookmark support</caption>
<image>https://camo.githubusercontent.com/ebbaffedcbd4f743ba9d67e5eaab2108407832cd/68747470733a2f2f692e696d6775722e636f6d2f5937716f55386d2e706e67</image>
</screenshot>
<screenshot>
<caption>Customizable view profiles</caption>
<image>https://camo.githubusercontent.com/0cea5e37e5c40abd4988544ba32b187db3cbe783/68747470733a2f2f692e696d6775722e636f6d2f6177453271324b2e706e67</image>
</screenshot>
<screenshot>
<caption>Metadata editor</caption>
<image>https://camo.githubusercontent.com/15362fb47e79b2d50eef507133cee8de6c1baa5c/68747470733a2f2f692e696d6775722e636f6d2f304344704e4f382e706e67</image>
</screenshot>
<screenshot>
<caption>In-app dictionary</caption>
<image>https://camo.githubusercontent.com/de3bfc5359db85da8ad94850d12fe563b5eefb87/68747470733a2f2f692e696d6775722e636f6d2f524637326d32682e706e67</image>
</screenshot>
<screenshot>
<caption>Settings window</caption>
<image>https://camo.githubusercontent.com/9ef376164f311cf518b4825684e7d5cc9d1f538c/68747470733a2f2f692e696d6775722e636f6d2f6c367a4a5861482e706e67</image>
</screenshot>
</screenshots>
<url type="homepage">https://github.com/BasioMeusPuga/Lector</url>
<url type="bugtracker">https://github.com/BasioMeusPuga/Lector/issues</url>
<provides>
<binary>lector</binary>
</provides>
<releases>
<release version="0.5.1" date="2019-03-09">
<description>
<p>Sharper icons on hiDPI screens. More reliable dependency checks, and slight tweaks to the GUI.</p>
</description>
</release>
</releases>
</component>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="178.117px" height="234.088px" viewBox="0 0 178.117 234.088" enable-background="new 0 0 178.117 234.088"
xml:space="preserve">
<g>
<path fill="#41CD52" d="M128.275,0H67.067H0v168.689h0.091c37.278,0.518,64.647,9.549,81.347,26.848
c2.557,2.652,4.862,5.513,6.894,8.564c2.034-3.052,4.338-5.912,6.893-8.564c16.925-17.534,44.797-26.573,82.843-26.87h0.05V49.842
L128.275,0z M162.231,156.781h-0.044c-31.258,0.243-54.156,7.67-68.062,22.081c-2.099,2.173-3.993,4.527-5.667,7.031
c-1.668-2.504-3.562-4.858-5.661-7.031c-13.721-14.213-36.207-21.636-66.835-22.058l-0.076-0.004V18.208h55.104l50.289-0.004
l40.951,40.951V156.781z"/>
<path fill="#41CD52" d="M100.03,200.175c-5.132,5.315-9.028,11.451-11.699,18.353c-2.67-6.901-6.566-13.037-11.696-18.353
C61.197,184.181,35.406,175.856,0,175.366v10.383c23.437,0.365,53.315,4.596,69.823,21.7c6.819,7.062,10.964,16.009,12.459,26.639
h12.098c1.497-10.63,5.643-19.577,12.461-26.639c16.851-17.459,47.633-21.503,71.275-21.716v-10.39
C141.959,175.621,115.676,183.96,100.03,200.175z"/>
<g>
<g>
<rect x="38.626" y="69.435" fill="#41CD52" width="100.864" height="9.664"/>
<rect x="38.626" y="97.219" fill="#41CD52" width="100.864" height="9.664"/>
<rect x="38.626" y="124.999" fill="#41CD52" width="100.864" height="9.663"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="854.721px" height="234.088px" viewBox="0 0 854.721 234.088" enable-background="new 0 0 854.721 234.088"
xml:space="preserve">
<path fill="#41CD52" d="M128.275,0H67.067H0v168.689h0.091c37.278,0.518,64.647,9.549,81.347,26.848
c2.557,2.652,4.862,5.513,6.894,8.564c2.034-3.052,4.339-5.912,6.894-8.564c16.924-17.534,44.796-26.573,82.842-26.87h0.049V49.842
L128.275,0z M162.231,156.781h-0.044c-31.257,0.243-54.156,7.67-68.062,22.081c-2.098,2.173-3.993,4.527-5.667,7.031
c-1.668-2.504-3.562-4.858-5.661-7.031c-13.721-14.213-36.207-21.636-66.835-22.058l-0.076-0.004V18.208h55.104l50.289-0.004
l40.951,40.951V156.781z"/>
<path fill="#41CD52" d="M100.03,200.175c-5.132,5.315-9.028,11.451-11.699,18.353c-2.67-6.901-6.566-13.037-11.696-18.353
C61.197,184.181,35.406,175.856,0,175.366v10.383c23.437,0.365,53.315,4.596,69.823,21.7c6.819,7.062,10.964,16.009,12.459,26.639
h12.098c1.497-10.63,5.642-19.577,12.461-26.639c16.85-17.459,47.632-21.503,71.274-21.716v-10.39
C141.959,175.621,115.676,183.96,100.03,200.175z"/>
<g>
<g>
<rect x="38.626" y="69.435" fill="#41CD52" width="100.864" height="9.664"/>
<rect x="38.626" y="97.219" fill="#41CD52" width="100.864" height="9.664"/>
<rect x="38.626" y="124.999" fill="#41CD52" width="100.864" height="9.663"/>
</g>
</g>
<g>
<polygon fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" points="238.749,49.355 226.182,49.355 226.182,185.003
299.459,185.003 299.459,174.092 238.749,174.092 "/>
<polygon fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" points="332.486,118.691 379.688,118.691 379.688,107.78
332.486,107.78 332.486,60.27 397.687,60.27 397.687,49.355 319.92,49.355 319.92,185.003 400.541,185.003 400.541,174.092
332.486,174.092 "/>
<path fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" d="M488.217,167.807c-5.688,5.718-13.057,8.583-22.107,8.583
c-12.167,0-21.647-5.246-28.427-15.735c-6.787-10.485-10.181-25.144-10.181-43.979c0-18.893,3.241-33.46,9.721-43.702
c6.482-10.238,15.689-15.362,27.604-15.362c8.191,0,15.059,2.507,20.59,7.521c5.539,5.014,10.018,12.779,13.441,23.294l11.649-2.75
c-3.676-13.205-9.192-22.99-16.562-29.348c-7.361-6.361-16.827-9.542-28.385-9.542c-16.39,0-29.024,5.98-37.927,17.931
c-8.895,11.957-13.342,29.001-13.342,51.135c0,22.865,4.447,40.495,13.342,52.871c8.902,12.384,21.537,18.573,37.927,18.573
c12.475,0,22.515-3.557,30.128-10.687c7.616-7.122,12.984-17.957,16.101-32.513l-11.098-2.474
C498.06,153.365,493.902,162.093,488.217,167.807z"/>
<polygon fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" points="521.967,60.27 562.142,60.27 562.142,185.003
574.803,185.003 574.803,60.27 614.97,60.27 614.97,49.355 521.967,49.355 "/>
<path fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" d="M681.103,47.156c-15.955,0-28.818,6.373-38.569,19.121
c-9.755,12.745-14.628,29.732-14.628,50.949c0,21.218,4.873,38.2,14.628,50.945c9.751,12.753,22.614,19.125,38.569,19.125
c15.956,0,28.762-6.372,38.429-19.125c9.663-12.745,14.487-29.728,14.487-50.945c0-21.217-4.824-38.204-14.487-50.949
C709.865,53.529,697.059,47.156,681.103,47.156z M710.36,160.563c-7.152,10.546-16.907,15.826-29.257,15.826
c-12.353,0-22.107-5.28-29.26-15.826c-7.156-10.546-10.725-24.992-10.725-43.337c0-18.402,3.568-32.867,10.725-43.382
c7.152-10.516,16.907-15.773,29.26-15.773c12.35,0,22.104,5.258,29.257,15.773c7.155,10.515,10.729,24.979,10.729,43.382
C721.088,135.571,717.515,150.018,710.36,160.563z"/>
<path fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" d="M821.244,121.537c8.99-2.568,15.743-6.799,20.267-12.703
c4.535-5.901,6.791-13.438,6.791-22.606c0-12.228-4.067-21.431-12.197-27.609c-8.134-6.175-20.239-9.264-36.321-9.264h-37.697
v135.648h12.573v-61.632h33.472l31.55,61.632h14.222L821.244,121.537z M805.285,112.733h-30.626V60.27h26.87
c11.372,0,19.826,2.142,25.357,6.418c5.539,4.28,8.301,10.857,8.301,19.718c0,8.442-2.587,14.936-7.749,19.49
C822.267,110.458,814.89,112.733,805.285,112.733z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="267.176px" height="313.485px" viewBox="0 0 267.176 313.485" enable-background="new 0 0 267.176 313.485"
xml:space="preserve">
<path fill="#41CD52" d="M172.645,0h-61.208H44.37v168.689h0.091c37.278,0.518,64.647,9.549,81.347,26.848
c2.557,2.652,4.862,5.513,6.894,8.564c2.034-3.052,4.339-5.912,6.894-8.564c16.924-17.534,44.796-26.573,82.842-26.87h0.049V49.843
L172.645,0z M206.601,156.781h-0.044c-31.257,0.243-54.156,7.67-68.062,22.081c-2.098,2.173-3.993,4.527-5.667,7.031
c-1.668-2.504-3.562-4.858-5.661-7.031c-13.721-14.213-36.207-21.636-66.835-22.058l-0.076-0.004V18.208h55.104l50.289-0.004
l40.951,40.951V156.781z"/>
<path fill="#41CD52" d="M144.4,200.175c-5.132,5.315-9.028,11.451-11.699,18.353c-2.67-6.901-6.566-13.037-11.696-18.353
c-15.438-15.994-41.229-24.318-76.635-24.809v10.383c23.437,0.365,53.315,4.596,69.823,21.7
c6.819,7.062,10.964,16.009,12.459,26.639h12.098c1.497-10.63,5.642-19.577,12.461-26.639c16.85-17.459,47.632-21.503,71.274-21.716
v-10.39C186.329,175.621,160.046,183.96,144.4,200.175z"/>
<g>
<g>
<rect x="82.996" y="69.435" fill="#41CD52" width="100.864" height="9.664"/>
<rect x="82.996" y="97.219" fill="#41CD52" width="100.864" height="9.663"/>
<rect x="82.996" y="124.999" fill="#41CD52" width="100.864" height="9.663"/>
</g>
</g>
<g>
<polygon fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" points="5.822,254.563 0.5,254.563 0.5,312.014 31.535,312.014
31.535,307.393 5.822,307.393 "/>
<polygon fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" points="45.522,283.929 65.515,283.929 65.515,279.309
45.522,279.309 45.522,259.187 73.137,259.187 73.137,254.563 40.2,254.563 40.2,312.014 74.345,312.014 74.345,307.393
45.522,307.393 "/>
<path fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" d="M111.479,304.73c-2.408,2.422-5.529,3.636-9.362,3.636
c-5.152,0-9.168-2.222-12.039-6.665c-2.875-4.439-4.312-10.648-4.312-18.626c0-8.001,1.372-14.171,4.116-18.509
c2.745-4.336,6.645-6.506,11.691-6.506c3.469,0,6.378,1.062,8.721,3.186c2.345,2.123,4.242,5.411,5.692,9.865l4.934-1.166
c-1.557-5.592-3.894-9.735-7.014-12.43c-3.118-2.693-7.127-4.04-12.022-4.04c-6.941,0-12.292,2.532-16.063,7.594
c-3.767,5.065-5.649,12.284-5.649,21.657c0,9.684,1.883,17.151,5.649,22.394c3.771,5.244,9.122,7.865,16.063,7.865
c5.284,0,9.536-1.507,12.76-4.526c3.226-3.016,5.5-7.604,6.819-13.77l-4.7-1.047C115.647,298.614,113.887,302.312,111.479,304.73z"
/>
<polygon fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" points="125.772,259.187 142.789,259.187 142.789,312.014
148.15,312.014 148.15,259.187 165.162,259.187 165.162,254.563 125.772,254.563 "/>
<path fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" d="M193.172,253.631c-6.758,0-12.206,2.701-16.335,8.1
c-4.133,5.398-6.195,12.593-6.195,21.578c0,8.987,2.063,16.179,6.195,21.576c4.129,5.402,9.577,8.101,16.335,8.101
s12.182-2.698,16.275-8.101c4.094-5.397,6.136-12.589,6.136-21.576c0-8.985-2.042-16.18-6.136-21.578
S199.93,253.631,193.172,253.631z M205.563,301.663c-3.028,4.466-7.161,6.703-12.391,6.703c-5.232,0-9.363-2.237-12.392-6.703
c-3.031-4.466-4.544-10.586-4.544-18.354c0-7.794,1.513-13.92,4.544-18.374c3.028-4.453,7.159-6.68,12.392-6.68
c5.229,0,9.362,2.227,12.391,6.68c3.031,4.454,4.544,10.58,4.544,18.374C210.106,291.077,208.594,297.197,205.563,301.663z"/>
<path fill="#4D4D4D" stroke="#4D4D4D" stroke-miterlimit="10" d="M252.525,285.135c3.808-1.088,6.667-2.88,8.583-5.381
c1.921-2.499,2.876-5.69,2.876-9.573c0-5.18-1.723-9.078-5.165-11.693c-3.445-2.615-8.572-3.924-15.384-3.924H227.47v57.45h5.325
V285.91h14.177l13.361,26.104h6.023L252.525,285.135z M245.766,281.406h-12.971v-22.22h11.38c4.816,0,8.397,0.907,10.739,2.717
c2.346,1.813,3.516,4.601,3.516,8.354c0,3.574-1.096,6.325-3.281,8.254C252.958,280.443,249.834,281.406,245.766,281.406z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -55,46 +55,6 @@
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QListView" name="listView">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="movement">
<enum>QListView::Static</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
<property name="resizeMode">
<enum>QListView::Fixed</enum>
</property>
<property name="layoutMode">
<enum>QListView::SinglePass</enum>
</property>
<property name="spacing">
<number>0</number>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tablePage">
@@ -114,43 +74,6 @@
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QTableView" name="tableView">
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -1,5 +1,39 @@
<RCC>
<qresource prefix="images">
<file>DarkIcons/rotate-left.svg</file>
<file>DarkIcons/rotate-right.svg</file>
<file>LightIcons/rotate-left.svg</file>
<file>LightIcons/rotate-right.svg</file>
<file>DarkIcons/invert.svg</file>
<file>LightIcons/invert.svg</file>
<file>DarkIcons/manga-mode.svg</file>
<file>LightIcons/manga-mode.svg</file>
<file>DarkIcons/search-word.svg</file>
<file>DarkIcons/search-word.svg</file>
<file>DarkIcons/search-case.svg</file>
<file>LightIcons/search-word.svg</file>
<file>LightIcons/search-case.svg</file>
<file>DarkIcons/page-double.svg</file>
<file>DarkIcons/page-single.svg</file>
<file>DarkIcons/page-flow.svg</file>
<file>LightIcons/page-double.svg</file>
<file>LightIcons/page-single.svg</file>
<file>LightIcons/page-flow.svg</file>
<file>DarkIcons/about.svg</file>
<file>DarkIcons/switches.svg</file>
<file>LightIcons/about.svg</file>
<file>LightIcons/switches.svg</file>
<file>LightIcons/annotate.svg</file>
<file>DarkIcons/annotate.svg</file>
<file>Google.png</file>
<file>Wikipedia.png</file>
<file>Youtube.png</file>
<file>DarkIcons/web-browser.svg</file>
<file>LightIcons/web-browser.svg</file>
<file>DarkIcons/arrow-down.svg</file>
<file>DarkIcons/arrow-up.svg</file>
<file>LightIcons/arrow-down.svg</file>
<file>LightIcons/arrow-up.svg</file>
<file>Lector.png</file>
<file>DarkIcons/tableofcontents.svg</file>
<file>LightIcons/tableofcontents.svg</file>
@@ -66,6 +100,8 @@
<file>LightIcons/zoom-in.svg</file>
<file>LightIcons/zoom-original.svg</file>
<file>LightIcons/zoom-out.svg</file>
<file>next.png</file>
<file>previous.png</file>
<file>QMPlay2.svg</file>
<file>color.svg</file>
<file>blank.png</file>
@@ -75,8 +111,12 @@
<file>error.svg</file>
</qresource>
<qresource prefix="translations">
<file>translations_bin/Lector_cs.qm</file>
<file>translations_bin/Lector_es.qm</file>
<file>translations_bin/Lector_de.qm</file>
<file>translations_bin/Lector_fr.qm</file>
<file>translations_bin/Lector_ja.qm</file>
<file>translations_bin/Lector_zh.qm</file>
<file>translations_bin/Lector_pt.qm</file>
</qresource>
</RCC>

View File

@@ -6,211 +6,799 @@
<rect>
<x>0</x>
<y>0</y>
<width>1216</width>
<height>658</height>
<width>1139</width>
<height>612</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Library</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QTreeView" name="treeView"/>
</item>
<item row="1" column="0">
<widget class="QTextBrowser" name="aboutBox">
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Switches</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QCheckBox" name="refreshLibrary">
<property name="text">
<string>Startup: Refresh library</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="fileRemember">
<property name="text">
<string>Remember open files</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="performCulling">
<property name="toolTip">
<string>Enabling reduces startup time and memory usage</string>
</property>
<property name="text">
<string>Load covers only when needed</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="coverShadows">
<property name="text">
<string>Cover shadows</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoTags">
<property name="text">
<string>Generate tags from files</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cachingEnabled">
<property name="toolTip">
<string>Greatly reduces page transition time at the cost of more memory</string>
</property>
<property name="text">
<string>Cache comic / pdf pages</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="languageLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Dictionary:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="languageBox"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="hideScrollBars">
<property name="toolTip">
<string>Horizontal scrolling with Alt + Scroll
Reopen book to see changes</string>
</property>
<property name="text">
<string>Hide scrollbars when reading</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label">
<property name="toolTip">
<string>Restart application to see changes</string>
</property>
<property name="text">
<string>Icon theme: </string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="darkIconsRadio">
<property name="toolTip">
<string>Restart application to see changes</string>
</property>
<property name="text">
<string>Dar&amp;k</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="lightIconsRadio">
<property name="toolTip">
<string>Restart application to see changes</string>
</property>
<property name="text">
<string>&amp;Light</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
<widget class="SaysHelloWhenClicked" name="listView">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>Scan Library</string>
</property>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="treeViewPage">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QTreeView" name="treeView"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="switchPage">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Library</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QLabel" name="readAtLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Consider book read at percent</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="readAtPercent">
<property name="minimum">
<number>90</number>
</property>
<property name="value">
<number>95</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label">
<property name="toolTip">
<string>Restart application to see changes</string>
</property>
<property name="text">
<string>Icon theme: </string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="darkIconsRadio">
<property name="toolTip">
<string>Restart application to see changes</string>
</property>
<property name="text">
<string>&amp;Dark</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="lightIconsRadio">
<property name="toolTip">
<string>Restart application to see changes</string>
</property>
<property name="text">
<string>L&amp;ight</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QCheckBox" name="refreshLibrary">
<property name="text">
<string>Startup: Refresh library</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="fileRemember">
<property name="text">
<string>Remember open files</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="coverShadows">
<property name="text">
<string>Cover shadows</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="performCulling">
<property name="toolTip">
<string>Enabling reduces startup time and memory usage</string>
</property>
<property name="text">
<string>Load covers only when needed</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QCheckBox" name="autoTags">
<property name="text">
<string>Generate tags from files</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="attenuateTitles">
<property name="text">
<string>Shrink long book titles</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QCheckBox" name="navBarVisible">
<property name="text">
<string>Show navigation bar</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoCover">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Attempt to download missing book covers from Google books - SLOW&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Download missing covers</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Reading</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="0">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QCheckBox" name="hideScrollBars">
<property name="toolTip">
<string>Horizontal scrolling with Alt + Scroll
Reopen book to see changes</string>
</property>
<property name="text">
<string>Hide scrollbars when reading</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cachingEnabled">
<property name="toolTip">
<string>Greatly reduces page transition time at the cost of more memory</string>
</property>
<property name="text">
<string>Cache comic / pdf pages</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_15">
<item>
<widget class="QLabel" name="smallIncrementLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UP/DOWN ARROW - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Small increment</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="smallIncrementBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UP/DOWN ARROW - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>4</number>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="value">
<number>4</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="largeIncrementLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;SPACEBAR - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Large increment</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="largeIncrementBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;SPACEBAR - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="languageLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Dictionary language</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QComboBox" name="languageBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="scrollSpeedLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Scroll speed</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSlider" name="scrollSpeedSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>3</number>
</property>
<property name="maximum">
<number>15</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="annotationsPage">
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="textTab">
<attribute name="title">
<string>Text</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="newAnnotation">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>New</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteAnnotation">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Delete</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="editAnnotation">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Edit</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveUp">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Move Up</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveDown">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Move Down</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QListView" name="annotationsList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="defaultDropAction">
<enum>Qt::IgnoreAction</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QTextBrowser" name="previewView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="imageTab">
<attribute name="title">
<string>Image</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="aboutPage">
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<widget class="QTabWidget" name="aboutTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="aboutTab">
<attribute name="title">
<string>About</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<widget class="QTextBrowser" name="aboutBox">
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="logTab">
<attribute name="title">
<string>Log</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="0">
<widget class="QPlainTextEdit" name="logBox"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="aboutButton">
<property name="text">
<string>About</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QPushButton" name="resetButton">
<property name="text">
<string>Reset Application</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearLogButton">
<property name="text">
<string>Clear Log</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>Scan Library</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SaysHelloWhenClicked</class>
<extends>QListView</extends>
<header>lector.widgets</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -2,39 +2,86 @@
# Form implementation generated from reading ui file 'raw/settings.ui'
#
# Created by: PyQt5 UI code generator 5.10.1
# Created by: PyQt5 UI code generator 5.12
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(1216, 658)
self.gridLayout_3 = QtWidgets.QGridLayout(Dialog)
self.gridLayout_3.setObjectName("gridLayout_3")
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.groupBox_2 = QtWidgets.QGroupBox(Dialog)
self.groupBox_2.setObjectName("groupBox_2")
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
self.gridLayout_2.setObjectName("gridLayout_2")
self.treeView = QtWidgets.QTreeView(self.groupBox_2)
self.treeView.setObjectName("treeView")
self.gridLayout_2.addWidget(self.treeView, 0, 0, 1, 1)
self.aboutBox = QtWidgets.QTextBrowser(self.groupBox_2)
self.aboutBox.setOpenExternalLinks(True)
self.aboutBox.setOpenLinks(False)
self.aboutBox.setObjectName("aboutBox")
self.gridLayout_2.addWidget(self.aboutBox, 1, 0, 1, 1)
self.verticalLayout_2.addWidget(self.groupBox_2)
self.groupBox = QtWidgets.QGroupBox(Dialog)
self.groupBox.setObjectName("groupBox")
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
Dialog.resize(1139, 612)
self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setObjectName("gridLayout")
self.listView = SaysHelloWhenClicked(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listView.sizePolicy().hasHeightForWidth())
self.listView.setSizePolicy(sizePolicy)
self.listView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.listView.setObjectName("listView")
self.gridLayout.addWidget(self.listView, 0, 0, 1, 1)
self.verticalLayout_4 = QtWidgets.QVBoxLayout()
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.stackedWidget = QtWidgets.QStackedWidget(Dialog)
self.stackedWidget.setObjectName("stackedWidget")
self.treeViewPage = QtWidgets.QWidget()
self.treeViewPage.setObjectName("treeViewPage")
self.gridLayout_5 = QtWidgets.QGridLayout(self.treeViewPage)
self.gridLayout_5.setObjectName("gridLayout_5")
self.treeView = QtWidgets.QTreeView(self.treeViewPage)
self.treeView.setObjectName("treeView")
self.gridLayout_5.addWidget(self.treeView, 0, 0, 1, 1)
self.stackedWidget.addWidget(self.treeViewPage)
self.switchPage = QtWidgets.QWidget()
self.switchPage.setObjectName("switchPage")
self.gridLayout_2 = QtWidgets.QGridLayout(self.switchPage)
self.gridLayout_2.setObjectName("gridLayout_2")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.groupBox = QtWidgets.QGroupBox(self.switchPage)
self.groupBox.setObjectName("groupBox")
self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout_4.setObjectName("gridLayout_4")
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout_13 = QtWidgets.QHBoxLayout()
self.horizontalLayout_13.setObjectName("horizontalLayout_13")
self.horizontalLayout_14 = QtWidgets.QHBoxLayout()
self.horizontalLayout_14.setObjectName("horizontalLayout_14")
self.readAtLabel = QtWidgets.QLabel(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.readAtLabel.sizePolicy().hasHeightForWidth())
self.readAtLabel.setSizePolicy(sizePolicy)
self.readAtLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.readAtLabel.setObjectName("readAtLabel")
self.horizontalLayout_14.addWidget(self.readAtLabel)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_14.addItem(spacerItem)
self.readAtPercent = QtWidgets.QSpinBox(self.groupBox)
self.readAtPercent.setMinimum(90)
self.readAtPercent.setProperty("value", 95)
self.readAtPercent.setObjectName("readAtPercent")
self.horizontalLayout_14.addWidget(self.readAtPercent)
self.horizontalLayout_13.addLayout(self.horizontalLayout_14)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.label = QtWidgets.QLabel(self.groupBox)
self.label.setObjectName("label")
self.horizontalLayout_7.addWidget(self.label)
self.darkIconsRadio = QtWidgets.QRadioButton(self.groupBox)
self.darkIconsRadio.setObjectName("darkIconsRadio")
self.horizontalLayout_7.addWidget(self.darkIconsRadio)
self.lightIconsRadio = QtWidgets.QRadioButton(self.groupBox)
self.lightIconsRadio.setObjectName("lightIconsRadio")
self.horizontalLayout_7.addWidget(self.lightIconsRadio)
self.horizontalLayout_13.addLayout(self.horizontalLayout_7)
self.verticalLayout_2.addLayout(self.horizontalLayout_13)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.refreshLibrary = QtWidgets.QCheckBox(self.groupBox)
@@ -43,28 +90,84 @@ class Ui_Dialog(object):
self.fileRemember = QtWidgets.QCheckBox(self.groupBox)
self.fileRemember.setObjectName("fileRemember")
self.horizontalLayout_4.addWidget(self.fileRemember)
self.performCulling = QtWidgets.QCheckBox(self.groupBox)
self.performCulling.setObjectName("performCulling")
self.horizontalLayout_4.addWidget(self.performCulling)
self.verticalLayout.addLayout(self.horizontalLayout_4)
self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.coverShadows = QtWidgets.QCheckBox(self.groupBox)
self.coverShadows.setObjectName("coverShadows")
self.horizontalLayout.addWidget(self.coverShadows)
self.autoTags = QtWidgets.QCheckBox(self.groupBox)
self.autoTags.setObjectName("autoTags")
self.horizontalLayout.addWidget(self.autoTags)
self.cachingEnabled = QtWidgets.QCheckBox(self.groupBox)
self.cachingEnabled.setObjectName("cachingEnabled")
self.horizontalLayout.addWidget(self.cachingEnabled)
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1)
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.verticalLayout_2.addLayout(self.horizontalLayout_4)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.languageLabel = QtWidgets.QLabel(self.groupBox)
self.coverShadows = QtWidgets.QCheckBox(self.groupBox)
self.coverShadows.setObjectName("coverShadows")
self.horizontalLayout_3.addWidget(self.coverShadows)
self.performCulling = QtWidgets.QCheckBox(self.groupBox)
self.performCulling.setObjectName("performCulling")
self.horizontalLayout_3.addWidget(self.performCulling)
self.verticalLayout_2.addLayout(self.horizontalLayout_3)
self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.autoTags = QtWidgets.QCheckBox(self.groupBox)
self.autoTags.setObjectName("autoTags")
self.horizontalLayout_9.addWidget(self.autoTags)
self.attenuateTitles = QtWidgets.QCheckBox(self.groupBox)
self.attenuateTitles.setObjectName("attenuateTitles")
self.horizontalLayout_9.addWidget(self.attenuateTitles)
self.verticalLayout_2.addLayout(self.horizontalLayout_9)
self.horizontalLayout_16 = QtWidgets.QHBoxLayout()
self.horizontalLayout_16.setObjectName("horizontalLayout_16")
self.navBarVisible = QtWidgets.QCheckBox(self.groupBox)
self.navBarVisible.setObjectName("navBarVisible")
self.horizontalLayout_16.addWidget(self.navBarVisible)
self.autoCover = QtWidgets.QCheckBox(self.groupBox)
self.autoCover.setObjectName("autoCover")
self.horizontalLayout_16.addWidget(self.autoCover)
self.verticalLayout_2.addLayout(self.horizontalLayout_16)
self.gridLayout_4.addLayout(self.verticalLayout_2, 0, 0, 1, 1)
self.verticalLayout.addWidget(self.groupBox)
self.groupBox_2 = QtWidgets.QGroupBox(self.switchPage)
self.groupBox_2.setObjectName("groupBox_2")
self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_2)
self.gridLayout_3.setObjectName("gridLayout_3")
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.hideScrollBars = QtWidgets.QCheckBox(self.groupBox_2)
self.hideScrollBars.setObjectName("hideScrollBars")
self.horizontalLayout_6.addWidget(self.hideScrollBars)
self.cachingEnabled = QtWidgets.QCheckBox(self.groupBox_2)
self.cachingEnabled.setObjectName("cachingEnabled")
self.horizontalLayout_6.addWidget(self.cachingEnabled)
self.verticalLayout_3.addLayout(self.horizontalLayout_6)
self.horizontalLayout_12 = QtWidgets.QHBoxLayout()
self.horizontalLayout_12.setObjectName("horizontalLayout_12")
self.horizontalLayout_15 = QtWidgets.QHBoxLayout()
self.horizontalLayout_15.setObjectName("horizontalLayout_15")
self.smallIncrementLabel = QtWidgets.QLabel(self.groupBox_2)
self.smallIncrementLabel.setObjectName("smallIncrementLabel")
self.horizontalLayout_15.addWidget(self.smallIncrementLabel)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_15.addItem(spacerItem1)
self.smallIncrementBox = QtWidgets.QSpinBox(self.groupBox_2)
self.smallIncrementBox.setMinimum(4)
self.smallIncrementBox.setMaximum(10)
self.smallIncrementBox.setProperty("value", 4)
self.smallIncrementBox.setObjectName("smallIncrementBox")
self.horizontalLayout_15.addWidget(self.smallIncrementBox)
self.largeIncrementLabel = QtWidgets.QLabel(self.groupBox_2)
self.largeIncrementLabel.setObjectName("largeIncrementLabel")
self.horizontalLayout_15.addWidget(self.largeIncrementLabel)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_15.addItem(spacerItem2)
self.largeIncrementBox = QtWidgets.QSpinBox(self.groupBox_2)
self.largeIncrementBox.setMinimum(1)
self.largeIncrementBox.setMaximum(10)
self.largeIncrementBox.setProperty("value", 2)
self.largeIncrementBox.setObjectName("largeIncrementBox")
self.horizontalLayout_15.addWidget(self.largeIncrementBox)
self.horizontalLayout_12.addLayout(self.horizontalLayout_15)
self.verticalLayout_3.addLayout(self.horizontalLayout_12)
self.horizontalLayout_8 = QtWidgets.QHBoxLayout()
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.languageLabel = QtWidgets.QLabel(self.groupBox_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -72,70 +175,251 @@ class Ui_Dialog(object):
self.languageLabel.setSizePolicy(sizePolicy)
self.languageLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.languageLabel.setObjectName("languageLabel")
self.horizontalLayout_3.addWidget(self.languageLabel)
self.languageBox = QtWidgets.QComboBox(self.groupBox)
self.horizontalLayout_5.addWidget(self.languageLabel)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_5.addItem(spacerItem3)
self.languageBox = QtWidgets.QComboBox(self.groupBox_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.languageBox.sizePolicy().hasHeightForWidth())
self.languageBox.setSizePolicy(sizePolicy)
self.languageBox.setObjectName("languageBox")
self.horizontalLayout_3.addWidget(self.languageBox)
self.horizontalLayout_6.addLayout(self.horizontalLayout_3)
self.hideScrollBars = QtWidgets.QCheckBox(self.groupBox)
self.hideScrollBars.setObjectName("hideScrollBars")
self.horizontalLayout_6.addWidget(self.hideScrollBars)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.label = QtWidgets.QLabel(self.groupBox)
self.label.setObjectName("label")
self.horizontalLayout_5.addWidget(self.label)
self.darkIconsRadio = QtWidgets.QRadioButton(self.groupBox)
self.darkIconsRadio.setObjectName("darkIconsRadio")
self.horizontalLayout_5.addWidget(self.darkIconsRadio)
self.lightIconsRadio = QtWidgets.QRadioButton(self.groupBox)
self.lightIconsRadio.setObjectName("lightIconsRadio")
self.horizontalLayout_5.addWidget(self.lightIconsRadio)
self.horizontalLayout_6.addLayout(self.horizontalLayout_5)
self.gridLayout.addLayout(self.horizontalLayout_6, 2, 0, 1, 1)
self.verticalLayout_2.addWidget(self.groupBox)
self.gridLayout_3.addLayout(self.verticalLayout_2, 0, 0, 1, 1)
self.horizontalLayout_5.addWidget(self.languageBox)
self.horizontalLayout_8.addLayout(self.horizontalLayout_5)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.scrollSpeedLabel = QtWidgets.QLabel(self.groupBox_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.scrollSpeedLabel.sizePolicy().hasHeightForWidth())
self.scrollSpeedLabel.setSizePolicy(sizePolicy)
self.scrollSpeedLabel.setObjectName("scrollSpeedLabel")
self.horizontalLayout.addWidget(self.scrollSpeedLabel)
spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem4)
self.scrollSpeedSlider = QtWidgets.QSlider(self.groupBox_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.scrollSpeedSlider.sizePolicy().hasHeightForWidth())
self.scrollSpeedSlider.setSizePolicy(sizePolicy)
self.scrollSpeedSlider.setMinimum(3)
self.scrollSpeedSlider.setMaximum(15)
self.scrollSpeedSlider.setOrientation(QtCore.Qt.Horizontal)
self.scrollSpeedSlider.setObjectName("scrollSpeedSlider")
self.horizontalLayout.addWidget(self.scrollSpeedSlider)
self.horizontalLayout_8.addLayout(self.horizontalLayout)
self.verticalLayout_3.addLayout(self.horizontalLayout_8)
self.gridLayout_3.addLayout(self.verticalLayout_3, 2, 0, 1, 1)
self.verticalLayout.addWidget(self.groupBox_2)
self.gridLayout_2.addLayout(self.verticalLayout, 0, 0, 1, 1)
spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem5, 1, 0, 1, 1)
self.stackedWidget.addWidget(self.switchPage)
self.annotationsPage = QtWidgets.QWidget()
self.annotationsPage.setObjectName("annotationsPage")
self.gridLayout_7 = QtWidgets.QGridLayout(self.annotationsPage)
self.gridLayout_7.setObjectName("gridLayout_7")
self.tabWidget = QtWidgets.QTabWidget(self.annotationsPage)
self.tabWidget.setObjectName("tabWidget")
self.textTab = QtWidgets.QWidget()
self.textTab.setObjectName("textTab")
self.gridLayout_8 = QtWidgets.QGridLayout(self.textTab)
self.gridLayout_8.setObjectName("gridLayout_8")
self.verticalLayout_5 = QtWidgets.QVBoxLayout()
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.horizontalLayout_11 = QtWidgets.QHBoxLayout()
self.horizontalLayout_11.setObjectName("horizontalLayout_11")
self.verticalLayout_6 = QtWidgets.QVBoxLayout()
self.verticalLayout_6.setObjectName("verticalLayout_6")
spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_6.addItem(spacerItem6)
self.newAnnotation = QtWidgets.QPushButton(self.textTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.newAnnotation.sizePolicy().hasHeightForWidth())
self.newAnnotation.setSizePolicy(sizePolicy)
self.newAnnotation.setMinimumSize(QtCore.QSize(30, 0))
self.newAnnotation.setMaximumSize(QtCore.QSize(45, 16777215))
self.newAnnotation.setText("")
self.newAnnotation.setObjectName("newAnnotation")
self.verticalLayout_6.addWidget(self.newAnnotation)
self.deleteAnnotation = QtWidgets.QPushButton(self.textTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.deleteAnnotation.sizePolicy().hasHeightForWidth())
self.deleteAnnotation.setSizePolicy(sizePolicy)
self.deleteAnnotation.setMinimumSize(QtCore.QSize(30, 0))
self.deleteAnnotation.setMaximumSize(QtCore.QSize(45, 16777215))
self.deleteAnnotation.setText("")
self.deleteAnnotation.setObjectName("deleteAnnotation")
self.verticalLayout_6.addWidget(self.deleteAnnotation)
self.editAnnotation = QtWidgets.QPushButton(self.textTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.editAnnotation.sizePolicy().hasHeightForWidth())
self.editAnnotation.setSizePolicy(sizePolicy)
self.editAnnotation.setMinimumSize(QtCore.QSize(30, 0))
self.editAnnotation.setMaximumSize(QtCore.QSize(45, 16777215))
self.editAnnotation.setText("")
self.editAnnotation.setObjectName("editAnnotation")
self.verticalLayout_6.addWidget(self.editAnnotation)
self.moveUp = QtWidgets.QPushButton(self.textTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.moveUp.sizePolicy().hasHeightForWidth())
self.moveUp.setSizePolicy(sizePolicy)
self.moveUp.setMinimumSize(QtCore.QSize(30, 0))
self.moveUp.setMaximumSize(QtCore.QSize(45, 16777215))
self.moveUp.setText("")
self.moveUp.setObjectName("moveUp")
self.verticalLayout_6.addWidget(self.moveUp)
self.moveDown = QtWidgets.QPushButton(self.textTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.moveDown.sizePolicy().hasHeightForWidth())
self.moveDown.setSizePolicy(sizePolicy)
self.moveDown.setMinimumSize(QtCore.QSize(30, 0))
self.moveDown.setMaximumSize(QtCore.QSize(45, 16777215))
self.moveDown.setText("")
self.moveDown.setObjectName("moveDown")
self.verticalLayout_6.addWidget(self.moveDown)
spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_6.addItem(spacerItem7)
self.horizontalLayout_11.addLayout(self.verticalLayout_6)
self.annotationsList = QtWidgets.QListView(self.textTab)
self.annotationsList.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.annotationsList.setProperty("showDropIndicator", False)
self.annotationsList.setDefaultDropAction(QtCore.Qt.IgnoreAction)
self.annotationsList.setObjectName("annotationsList")
self.horizontalLayout_11.addWidget(self.annotationsList)
self.verticalLayout_5.addLayout(self.horizontalLayout_11)
self.gridLayout_8.addLayout(self.verticalLayout_5, 0, 0, 1, 1)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.previewView = QtWidgets.QTextBrowser(self.textTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.previewView.sizePolicy().hasHeightForWidth())
self.previewView.setSizePolicy(sizePolicy)
self.previewView.setMaximumSize(QtCore.QSize(16777215, 100))
self.previewView.setFocusPolicy(QtCore.Qt.NoFocus)
self.previewView.setObjectName("previewView")
self.horizontalLayout_2.addWidget(self.previewView)
self.gridLayout_8.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
self.tabWidget.addTab(self.textTab, "")
self.imageTab = QtWidgets.QWidget()
self.imageTab.setObjectName("imageTab")
self.tabWidget.addTab(self.imageTab, "")
self.gridLayout_7.addWidget(self.tabWidget, 0, 0, 1, 1)
self.stackedWidget.addWidget(self.annotationsPage)
self.aboutPage = QtWidgets.QWidget()
self.aboutPage.setObjectName("aboutPage")
self.gridLayout_9 = QtWidgets.QGridLayout(self.aboutPage)
self.gridLayout_9.setObjectName("gridLayout_9")
self.aboutTabWidget = QtWidgets.QTabWidget(self.aboutPage)
self.aboutTabWidget.setObjectName("aboutTabWidget")
self.aboutTab = QtWidgets.QWidget()
self.aboutTab.setObjectName("aboutTab")
self.gridLayout_6 = QtWidgets.QGridLayout(self.aboutTab)
self.gridLayout_6.setObjectName("gridLayout_6")
self.aboutBox = QtWidgets.QTextBrowser(self.aboutTab)
self.aboutBox.setOpenExternalLinks(True)
self.aboutBox.setOpenLinks(False)
self.aboutBox.setObjectName("aboutBox")
self.gridLayout_6.addWidget(self.aboutBox, 0, 0, 1, 1)
self.aboutTabWidget.addTab(self.aboutTab, "")
self.logTab = QtWidgets.QWidget()
self.logTab.setObjectName("logTab")
self.gridLayout_10 = QtWidgets.QGridLayout(self.logTab)
self.gridLayout_10.setObjectName("gridLayout_10")
self.logBox = QtWidgets.QPlainTextEdit(self.logTab)
self.logBox.setObjectName("logBox")
self.gridLayout_10.addWidget(self.logBox, 0, 0, 1, 1)
self.aboutTabWidget.addTab(self.logTab, "")
self.gridLayout_9.addWidget(self.aboutTabWidget, 0, 0, 1, 1)
self.stackedWidget.addWidget(self.aboutPage)
self.verticalLayout_4.addWidget(self.stackedWidget)
self.horizontalLayout_10 = QtWidgets.QHBoxLayout()
self.horizontalLayout_10.setObjectName("horizontalLayout_10")
self.resetButton = QtWidgets.QPushButton(Dialog)
self.resetButton.setObjectName("resetButton")
self.horizontalLayout_10.addWidget(self.resetButton)
self.clearLogButton = QtWidgets.QPushButton(Dialog)
self.clearLogButton.setObjectName("clearLogButton")
self.horizontalLayout_10.addWidget(self.clearLogButton)
spacerItem8 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_10.addItem(spacerItem8)
self.okButton = QtWidgets.QPushButton(Dialog)
self.okButton.setObjectName("okButton")
self.horizontalLayout_2.addWidget(self.okButton)
self.horizontalLayout_10.addWidget(self.okButton)
self.cancelButton = QtWidgets.QPushButton(Dialog)
self.cancelButton.setObjectName("cancelButton")
self.horizontalLayout_2.addWidget(self.cancelButton)
self.aboutButton = QtWidgets.QPushButton(Dialog)
self.aboutButton.setCheckable(True)
self.aboutButton.setObjectName("aboutButton")
self.horizontalLayout_2.addWidget(self.aboutButton)
self.gridLayout_3.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
self.horizontalLayout_10.addWidget(self.cancelButton)
self.verticalLayout_4.addLayout(self.horizontalLayout_10)
self.gridLayout.addLayout(self.verticalLayout_4, 0, 1, 1, 1)
self.retranslateUi(Dialog)
self.tabWidget.setCurrentIndex(0)
self.aboutTabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Settings"))
self.groupBox_2.setTitle(_translate("Dialog", "Library"))
self.groupBox.setTitle(_translate("Dialog", "Switches"))
self.refreshLibrary.setText(_translate("Dialog", "Startup: Refresh library"))
self.fileRemember.setText(_translate("Dialog", "Remember open files"))
self.performCulling.setToolTip(_translate("Dialog", "Enabling reduces startup time and memory usage"))
self.performCulling.setText(_translate("Dialog", "Load covers only when needed"))
self.coverShadows.setText(_translate("Dialog", "Cover shadows"))
self.autoTags.setText(_translate("Dialog", "Generate tags from files"))
self.cachingEnabled.setToolTip(_translate("Dialog", "Greatly reduces page transition time at the cost of more memory"))
self.cachingEnabled.setText(_translate("Dialog", "Cache comic / pdf pages"))
self.languageLabel.setText(_translate("Dialog", "Dictionary:"))
self.hideScrollBars.setToolTip(_translate("Dialog", "Horizontal scrolling with Alt + Scroll\n"
"Reopen book to see changes"))
self.hideScrollBars.setText(_translate("Dialog", "Hide scrollbars when reading"))
self.groupBox.setTitle(_translate("Dialog", "Library"))
self.readAtLabel.setText(_translate("Dialog", "Consider book read at percent"))
self.label.setToolTip(_translate("Dialog", "Restart application to see changes"))
self.label.setText(_translate("Dialog", "Icon theme: "))
self.darkIconsRadio.setToolTip(_translate("Dialog", "Restart application to see changes"))
self.darkIconsRadio.setText(_translate("Dialog", "Dar&k"))
self.darkIconsRadio.setText(_translate("Dialog", "&Dark"))
self.lightIconsRadio.setToolTip(_translate("Dialog", "Restart application to see changes"))
self.lightIconsRadio.setText(_translate("Dialog", "&Light"))
self.lightIconsRadio.setText(_translate("Dialog", "L&ight"))
self.refreshLibrary.setText(_translate("Dialog", "Startup: Refresh library"))
self.fileRemember.setText(_translate("Dialog", "Remember open files"))
self.coverShadows.setText(_translate("Dialog", "Cover shadows"))
self.performCulling.setToolTip(_translate("Dialog", "Enabling reduces startup time and memory usage"))
self.performCulling.setText(_translate("Dialog", "Load covers only when needed"))
self.autoTags.setText(_translate("Dialog", "Generate tags from files"))
self.attenuateTitles.setText(_translate("Dialog", "Shrink long book titles"))
self.navBarVisible.setText(_translate("Dialog", "Show navigation bar"))
self.autoCover.setToolTip(_translate("Dialog", "<html><head/><body><p>Attempt to download missing book covers from Google books - SLOW</p></body></html>"))
self.autoCover.setText(_translate("Dialog", "Download missing covers"))
self.groupBox_2.setTitle(_translate("Dialog", "Reading"))
self.hideScrollBars.setToolTip(_translate("Dialog", "Horizontal scrolling with Alt + Scroll\n"
"Reopen book to see changes"))
self.hideScrollBars.setText(_translate("Dialog", "Hide scrollbars when reading"))
self.cachingEnabled.setToolTip(_translate("Dialog", "Greatly reduces page transition time at the cost of more memory"))
self.cachingEnabled.setText(_translate("Dialog", "Cache comic / pdf pages"))
self.smallIncrementLabel.setToolTip(_translate("Dialog", "<html><head/><body><p>UP/DOWN ARROW - Steps to take before turning comicbook page</p></body></html>"))
self.smallIncrementLabel.setText(_translate("Dialog", "Small increment"))
self.smallIncrementBox.setToolTip(_translate("Dialog", "<html><head/><body><p>UP/DOWN ARROW - Steps to take before turning comicbook page</p></body></html>"))
self.largeIncrementLabel.setToolTip(_translate("Dialog", "<html><head/><body><p>SPACEBAR - Steps to take before turning comicbook page</p></body></html>"))
self.largeIncrementLabel.setText(_translate("Dialog", "Large increment"))
self.largeIncrementBox.setToolTip(_translate("Dialog", "<html><head/><body><p>SPACEBAR - Steps to take before turning comicbook page</p></body></html>"))
self.languageLabel.setText(_translate("Dialog", "Dictionary language"))
self.scrollSpeedLabel.setText(_translate("Dialog", "Scroll speed"))
self.newAnnotation.setToolTip(_translate("Dialog", "New"))
self.deleteAnnotation.setToolTip(_translate("Dialog", "Delete"))
self.editAnnotation.setToolTip(_translate("Dialog", "Edit"))
self.moveUp.setToolTip(_translate("Dialog", "Move Up"))
self.moveDown.setToolTip(_translate("Dialog", "Move Down"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.textTab), _translate("Dialog", "Text"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.imageTab), _translate("Dialog", "Image"))
self.aboutTabWidget.setTabText(self.aboutTabWidget.indexOf(self.aboutTab), _translate("Dialog", "About"))
self.aboutTabWidget.setTabText(self.aboutTabWidget.indexOf(self.logTab), _translate("Dialog", "Log"))
self.resetButton.setText(_translate("Dialog", "Reset Application"))
self.clearLogButton.setText(_translate("Dialog", "Clear Log"))
self.okButton.setText(_translate("Dialog", "Scan Library"))
self.cancelButton.setText(_translate("Dialog", "Close"))
self.aboutButton.setText(_translate("Dialog", "About"))
from lector.widgets import SaysHelloWhenClicked

View File

@@ -0,0 +1,976 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0" language="cs" sourcelanguage="">
<context>
<name>AnnotationsUI</name>
<message>
<location filename="../../annotations.py" line="39"/>
<source>Text markup</source>
<translation>Značení textu</translation>
</message>
<message>
<location filename="../../annotations.py" line="126"/>
<source>New annotation</source>
<translation>Nová vysvětlivka</translation>
</message>
</context>
<context>
<name>BookToolBar</name>
<message>
<location filename="../../toolbars.py" line="43"/>
<source>View settings</source>
<translation>Zobrazit nastavení</translation>
</message>
<message>
<location filename="../../toolbars.py" line="251"/>
<source>Zoom Out (-)</source>
<translation>Oddálit (-)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="269"/>
<source>Original size (O)</source>
<translation>Původní velikost (O)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="59"/>
<source>Search (Ctrl + F)</source>
<translation>Hledat (Ctrl+F)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="122"/>
<source>Left align text</source>
<translation>Zarovnat text vlevo</translation>
</message>
<message>
<location filename="../../toolbars.py" line="136"/>
<source>Center align text</source>
<translation>Zarovnat text na střed</translation>
</message>
<message>
<location filename="../../toolbars.py" line="51"/>
<source>Add bookmark</source>
<translation>Přidat záložku</translation>
</message>
<message>
<location filename="../../toolbars.py" line="319"/>
<source>Table of Contents</source>
<translation>Obsah</translation>
</message>
<message>
<location filename="../../toolbars.py" line="129"/>
<source>Right align text</source>
<translation>Zarovnat text vpravo</translation>
</message>
<message>
<location filename="../../toolbars.py" line="216"/>
<source>Double page mode (D)</source>
<translation>Režim dvou stran (D)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="96"/>
<source>Font size</source>
<translation>Velikost písma</translation>
</message>
<message>
<location filename="../../toolbars.py" line="230"/>
<source>Invert page colors</source>
<translation>Obrátit barvy strany</translation>
</message>
<message>
<location filename="../../toolbars.py" line="246"/>
<source>Zoom in (+)</source>
<translation>Přiblížit (+)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="257"/>
<source>Fit Width (W)</source>
<translation>Přizpůsobit šířku (W)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="277"/>
<source>Background color</source>
<translation>Barva pozadí</translation>
</message>
<message>
<location filename="../../toolbars.py" line="67"/>
<source>Fullscreen (F)</source>
<translation>Celá obrazovka (F)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="111"/>
<source>Increase line spacing</source>
<translation>Zvětšit odstup řádku</translation>
</message>
<message>
<location filename="../../toolbars.py" line="116"/>
<source>Decrease line spacing</source>
<translation>Zmenšit odstup řádku</translation>
</message>
<message>
<location filename="../../toolbars.py" line="143"/>
<source>Justify text</source>
<translation>Zarovnat text do bloku</translation>
</message>
<message>
<location filename="../../toolbars.py" line="263"/>
<source>Best Fit (B)</source>
<translation>Nejlepší umístění (B)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="241"/>
<source>Rotate image anti-clockwise</source>
<translation>Otočit obrázek proti směru hodinových ručiček</translation>
</message>
<message>
<location filename="../../toolbars.py" line="237"/>
<source>Rotate image clockwise</source>
<translation>Otočit obrázek po směru hodinových ručiček</translation>
</message>
<message>
<location filename="../../toolbars.py" line="47"/>
<source>Annotations (Ctrl + N)</source>
<translation>Vysvětlivky (Ctrl+N)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="55"/>
<source>Bookmarks (Ctrl + B)</source>
<translation>Záložky (Ctrl+B)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="223"/>
<source>Manga mode (M)</source>
<translation>Režim manga (M)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="105"/>
<source>Decrease padding</source>
<translation>Zmenšit odstup</translation>
</message>
<message>
<location filename="../../toolbars.py" line="100"/>
<source>Increase padding</source>
<translation>Zvětšit odstup</translation>
</message>
<message>
<location filename="../../toolbars.py" line="71"/>
<source>Reset profile</source>
<translation>Obnovit výchozí profil</translation>
</message>
</context>
<context>
<name>DefinitionsUI</name>
<message>
<location filename="../../definitionsdialog.py" line="143"/>
<source>No definitions found in</source>
<translation>Žádná vymezení nenalezena v</translation>
</message>
</context>
<context>
<name>Dialog</name>
<message>
<location filename="../metadata.py" line="98"/>
<source>OK</source>
<translation>OK</translation>
</message>
<message>
<location filename="../settingswindow.py" line="418"/>
<source>Log</source>
<translation>Záznam</translation>
</message>
<message>
<location filename="../settingswindow.py" line="410"/>
<source>New</source>
<translation>Nový</translation>
</message>
<message>
<location filename="../settingswindow.py" line="412"/>
<source>Edit</source>
<translation>Upravit</translation>
</message>
<message>
<location filename="../metadata.py" line="97"/>
<source>Tags</source>
<translation>Značky</translation>
</message>
<message>
<location filename="../settingswindow.py" line="415"/>
<source>Text</source>
<translation>Text</translation>
</message>
<message>
<location filename="../metadata.py" line="95"/>
<source>Year</source>
<translation>Rok</translation>
</message>
<message>
<location filename="../settingswindow.py" line="389"/>
<source>Enabling reduces startup time and memory usage</source>
<translation>Povolení zmenší čas potřebný ke spuštění a využití paměti</translation>
</message>
<message>
<location filename="../settingswindow.py" line="383"/>
<source>&amp;Dark</source>
<translation>&amp;Tmavý</translation>
</message>
<message>
<location filename="../settingswindow.py" line="417"/>
<source>About</source>
<translation>O programu</translation>
</message>
<message>
<location filename="../settingswindow.py" line="422"/>
<source>Close</source>
<translation>Zavřít</translation>
</message>
<message>
<location filename="../settingswindow.py" line="416"/>
<source>Image</source>
<translation>Obrázek</translation>
</message>
<message>
<location filename="../metadata.py" line="91"/>
<source>Title</source>
<translation>Název</translation>
</message>
<message>
<location filename="../settingswindow.py" line="397"/>
<source>Horizontal scrolling with Alt + Scroll
Reopen book to see changes</source>
<translation>Vodorovné posunování pomocí Alt+posun
Otevřít knihu znovu pro zobrazení změn</translation>
</message>
<message>
<location filename="../settingswindow.py" line="406"/>
<source>Large increment</source>
<translation>Velký přírůstek</translation>
</message>
<message>
<location filename="../settingswindow.py" line="403"/>
<source>Small increment</source>
<translation>Malý přírůstek</translation>
</message>
<message>
<location filename="../settingswindow.py" line="378"/>
<source>Library</source>
<translation>Knihovna</translation>
</message>
<message>
<location filename="../settingswindow.py" line="390"/>
<source>Load covers only when needed</source>
<translation>Nahrát obaly, jen když je to potřeba</translation>
</message>
<message>
<location filename="../settingswindow.py" line="388"/>
<source>Cover shadows</source>
<translation>Stíny obalů</translation>
</message>
<message>
<location filename="../metadata.py" line="89"/>
<source>Cover (click to change)</source>
<translation>Obal knihy (klepnout pro jeho změnu)</translation>
</message>
<message>
<location filename="../settingswindow.py" line="413"/>
<source>Move Up</source>
<translation>Posunout nahoru</translation>
</message>
<message>
<location filename="../metadata.py" line="93"/>
<source>Author</source>
<translation>Autor</translation>
</message>
<message>
<location filename="../metadata.py" line="99"/>
<source>Cancel</source>
<translation>Zrušit</translation>
</message>
<message>
<location filename="../settingswindow.py" line="411"/>
<source>Delete</source>
<translation>Smazat</translation>
</message>
<message>
<location filename="../definitions.py" line="65"/>
<source>Dialog</source>
<translation>Dialog</translation>
</message>
<message>
<location filename="../settingswindow.py" line="387"/>
<source>Remember open files</source>
<translation>Zapamatovat si otevřené soubory</translation>
</message>
<message>
<location filename="../settingswindow.py" line="385"/>
<source>L&amp;ight</source>
<translation>&amp;Světlý</translation>
</message>
<message>
<location filename="../settingswindow.py" line="395"/>
<source>Download missing covers</source>
<translation>Stáhnout chybějící obaly</translation>
</message>
<message>
<location filename="../settingswindow.py" line="386"/>
<source>Startup: Refresh library</source>
<translation>Začátek: Nahrát knihovnu znovu</translation>
</message>
<message>
<location filename="../metadata.py" line="88"/>
<source>Edit metadata</source>
<translation>Upravit popisné údaje</translation>
</message>
<message>
<location filename="../settingswindow.py" line="421"/>
<source>Scan Library</source>
<translation>Prohledat knihovnu</translation>
</message>
<message>
<location filename="../settingswindow.py" line="396"/>
<source>Reading</source>
<translation>Čtení</translation>
</message>
<message>
<location filename="../settingswindow.py" line="404"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UP/DOWN ARROW - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ŠIPKA NAHORU/DOLŮ - Počet kroků před obrácením strany kresleného příběhu&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../settingswindow.py" line="399"/>
<source>Hide scrollbars when reading</source>
<translation>Při čtení skrýt posuvníky</translation>
</message>
<message>
<location filename="../settingswindow.py" line="419"/>
<source>Reset Application</source>
<translation>Nastavit výchozí stav programu</translation>
</message>
<message>
<location filename="../settingswindow.py" line="379"/>
<source>Consider book read at percent</source>
<translation>Považovat knihu přečtenu při procentu</translation>
</message>
<message>
<location filename="../settingswindow.py" line="394"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Attempt to download missing book covers from Google books - SLOW&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pokus o stažení chybějících obalů z Knih Google - POMALÝ&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../settingswindow.py" line="408"/>
<source>Dictionary language</source>
<translation>Jazyk slovníku</translation>
</message>
<message>
<location filename="../settingswindow.py" line="401"/>
<source>Cache comic / pdf pages</source>
<translation>Vyrovnávací paměť pro kreslené příběhy/strany PDF</translation>
</message>
<message>
<location filename="../settingswindow.py" line="392"/>
<source>Shrink long book titles</source>
<translation>Zmenšit dlouhé názvy knih</translation>
</message>
<message>
<location filename="../settingswindow.py" line="420"/>
<source>Clear Log</source>
<translation>Smazat záznam</translation>
</message>
<message>
<location filename="../settingswindow.py" line="409"/>
<source>Scroll speed</source>
<translation>Rychlost posunu</translation>
</message>
<message>
<location filename="../definitions.py" line="67"/>
<source>Play pronunciation of root word</source>
<translation>Přehrát výslovnost kořenového slova</translation>
</message>
<message>
<location filename="../settingswindow.py" line="414"/>
<source>Move Down</source>
<translation>Posunout dolů</translation>
</message>
<message>
<location filename="../settingswindow.py" line="377"/>
<source>Settings</source>
<translation>Nastavení</translation>
</message>
<message>
<location filename="../settingswindow.py" line="400"/>
<source>Greatly reduces page transition time at the cost of more memory</source>
<translation>Ohromně zmenší čas přechodu stránky za cenu více paměti</translation>
</message>
<message>
<location filename="../settingswindow.py" line="391"/>
<source>Generate tags from files</source>
<translation>Vytvořit značky ze souborů</translation>
</message>
<message>
<location filename="../settingswindow.py" line="381"/>
<source>Icon theme: </source>
<translation>Vzhled symbolů:</translation>
</message>
<message>
<location filename="../settingswindow.py" line="407"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;SPACEBAR - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;MEZERNÍK - Počet kroků před obrácením strany kresleného příběhu&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../metadata.py" line="96"/>
<source>Tags (comma separated)</source>
<translation>Značky (odděleny čárkou)</translation>
</message>
<message>
<location filename="../settingswindow.py" line="393"/>
<source>Show navigation bar</source>
<translation>Ukázat panel pro pohyb v knize</translation>
</message>
<message>
<location filename="../settingswindow.py" line="384"/>
<source>Restart application to see changes</source>
<translation>Kvůli změnám spustit program znovu</translation>
</message>
<message>
<location filename="../definitions.py" line="66"/>
<source>WERDS</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Library</name>
<message>
<location filename="../../library.py" line="130"/>
<source>Year</source>
<translation>Rok</translation>
</message>
<message>
<location filename="../../library.py" line="212"/>
<source> books</source>
<translation> knihy</translation>
</message>
<message>
<location filename="../../library.py" line="289"/>
<source>manually added</source>
<translation>přidáno ručně</translation>
</message>
<message>
<location filename="../../library.py" line="129"/>
<source>Author</source>
<translation>Autor</translation>
</message>
</context>
<context>
<name>LibraryToolBar</name>
<message>
<location filename="../../toolbars.py" line="483"/>
<source>Year</source>
<translation>Rok</translation>
</message>
<message>
<location filename="../../toolbars.py" line="444"/>
<source>About</source>
<translation>O</translation>
</message>
<message>
<location filename="../../toolbars.py" line="481"/>
<source>Title</source>
<translation>Název</translation>
</message>
<message>
<location filename="../../toolbars.py" line="430"/>
<source>Filter library</source>
<translation>Filtrovat knihovnu</translation>
</message>
<message>
<location filename="../../toolbars.py" line="482"/>
<source>Author</source>
<translation>Autor</translation>
</message>
<message>
<location filename="../../toolbars.py" line="484"/>
<source>Newest</source>
<translation>Nejnovější</translation>
</message>
<message>
<location filename="../../toolbars.py" line="422"/>
<source>Scan Library</source>
<translation>Prohledat knihovnu</translation>
</message>
<message>
<location filename="../../toolbars.py" line="486"/>
<source>Progress</source>
<translation>Postup</translation>
</message>
<message>
<location filename="../../toolbars.py" line="411"/>
<source>View as covers</source>
<translation>Zobrazit jako obaly</translation>
</message>
<message>
<location filename="../../toolbars.py" line="416"/>
<source>View as table</source>
<translation>Zobrazit jako tabulku</translation>
</message>
<message>
<location filename="../../toolbars.py" line="406"/>
<source>Delete book</source>
<translation>Smazat knihu</translation>
</message>
<message>
<location filename="../../toolbars.py" line="485"/>
<source>Last Read</source>
<translation>Naposledy čteno</translation>
</message>
<message>
<location filename="../../toolbars.py" line="402"/>
<source>Add book</source>
<translation>Přidat knihu</translation>
</message>
<message>
<location filename="../../toolbars.py" line="494"/>
<source>Sort by</source>
<translation>Řadit dle</translation>
</message>
<message>
<location filename="../../toolbars.py" line="433"/>
<source>Library background color</source>
<translation>Barva pozadí knihovny</translation>
</message>
<message>
<location filename="../../toolbars.py" line="475"/>
<source>Search for Title, Author, Tags...</source>
<translation>Hledat název, autora, značky...</translation>
</message>
<message>
<location filename="../../toolbars.py" line="438"/>
<source>Settings</source>
<translation>Nastavení</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.py" line="68"/>
<source>Library</source>
<translation>Knihovna</translation>
</message>
<message>
<location filename="../mainwindow.py" line="67"/>
<source>Lector</source>
<translation>Lector</translation>
</message>
</context>
<context>
<name>Main_BookToolBarUI</name>
<message>
<location filename="../../toolbars.py" line="63"/>
<source>Toggle distraction free mode (Ctrl + D)</source>
<translation>Zapnout/Vypnout režim bez rozptylování (Ctrl+D)</translation>
</message>
</context>
<context>
<name>Main_UI</name>
<message>
<location filename="../../__main__.py" line="825"/>
<source>Edit</source>
<translation>Upravit</translation>
</message>
<message>
<location filename="../../__main__.py" line="819"/>
<source>Start reading</source>
<translation>Číst</translation>
</message>
<message>
<location filename="../../__main__.py" line="630"/>
<source> Books</source>
<translation>Knihy</translation>
</message>
<message>
<location filename="../../__main__.py" line="581"/>
<source> books</source>
<translation> knihy</translation>
</message>
<message>
<location filename="../../__main__.py" line="940"/>
<source>Manually Added</source>
<translation>Přidáno ručně</translation>
</message>
<message>
<location filename="../../__main__.py" line="829"/>
<source>Delete</source>
<translation>Smazat</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="425"/>
<source>Images</source>
<translation>Obrázky</translation>
</message>
<message>
<location filename="../../__main__.py" line="563"/>
<source>Save changes and start library scan</source>
<translation>Uložit změny a začít prohledávat knihovnu</translation>
</message>
<message>
<location filename="../../__main__.py" line="478"/>
<source>eBooks</source>
<translation>Elektronické knihy</translation>
</message>
<message>
<location filename="../../__main__.py" line="550"/>
<source>Confirm deletion</source>
<translation>Potvrdit smazání</translation>
</message>
<message>
<location filename="../../__main__.py" line="835"/>
<source>Mark unread</source>
<translation>Označit jako nepřečtené</translation>
</message>
<message>
<location filename="../../__main__.py" line="546"/>
<source>Delete book(s)?</source>
<translation>Smazat knihu(y)?</translation>
</message>
<message>
<location filename="../../__main__.py" line="832"/>
<source>Mark read</source>
<translation>Označit jako přečtené</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="424"/>
<source>Save page as...</source>
<translation>Uložit stranu jako...</translation>
</message>
<message>
<location filename="../../__main__.py" line="477"/>
<source>Add books to database</source>
<translation>Přidat knihy do databáze</translation>
</message>
<message>
<location filename="../../__main__.py" line="492"/>
<source>Adding books...</source>
<translation>Přidávají se knihy...</translation>
</message>
</context>
<context>
<name>MetadataUI</name>
<message>
<location filename="../../metadatadialog.py" line="107"/>
<source>Year</source>
<translation>Rok</translation>
</message>
<message>
<location filename="../../metadatadialog.py" line="106"/>
<source>Author</source>
<translation>Autor</translation>
</message>
</context>
<context>
<name>PliantQGraphicsScene</name>
<message>
<location filename="../../widgets.py" line="621"/>
<source>Select new cover</source>
<translation>Vybrat nový obal</translation>
</message>
<message>
<location filename="../../widgets.py" line="622"/>
<source>Images</source>
<translation>Obrázky</translation>
</message>
</context>
<context>
<name>PliantQGraphicsView</name>
<message>
<location filename="../../contentwidgets.py" line="358"/>
<source>View</source>
<translation>Pohled</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="347"/>
<source>Exit Distraction Free mode</source>
<translation>Ukončit režim bez rozptylování</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="390"/>
<source>Zoom out (-)</source>
<translation>Oddálit (-)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="402"/>
<source>Original size (O)</source>
<translation>Původní velikost (O)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="343"/>
<source>Exit fullscreen</source>
<translation>Ukončit celou obrazovku</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="363"/>
<source>Double page mode (D)</source>
<translation>Režim dvou stran (D)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="377"/>
<source>Invert page colors</source>
<translation>Obrátit barvy strany</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="408"/>
<source>Bookmarks</source>
<translation>Záložky</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="386"/>
<source>Zoom in (+)</source>
<translation>Přiblížit (+)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="394"/>
<source>Fit width (W)</source>
<translation>Přizpůsobit na šířku okna (W)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="398"/>
<source>Best fit (B)</source>
<translation>Nejlepší umístění (B)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="354"/>
<source>Save page as...</source>
<translation>Uložit stranu jako...</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="370"/>
<source>Manga mode (M)</source>
<translation>Režim manga (M)</translation>
</message>
</context>
<context>
<name>PliantQTextBrowser</name>
<message>
<location filename="../../contentwidgets.py" line="763"/>
<source>View</source>
<translation>Pohled</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="849"/>
<source>Edit note</source>
<translation>Upravit poznámku</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="785"/>
<source>Exit Distraction Free mode</source>
<translation>Ukončit režim bez rozptylování</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="772"/>
<source>Single page</source>
<translation>Jedna strana</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="776"/>
<source>Double page</source>
<translation>Dvě strany</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="856"/>
<source>Add Bookmark</source>
<translation>Přidat záložku</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="781"/>
<source>Exit fullscreen</source>
<translation>Ukončit celou obrazovku</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="797"/>
<source>Define</source>
<translation>Vymezit</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="823"/>
<source>Annotate</source>
<translation>Přidat vysvětlivku</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="807"/>
<source>In this book</source>
<translation>V této knize</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="841"/>
<source>Search</source>
<translation>Hledat</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="862"/>
<source>Bookmarks</source>
<translation>Záložky</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="768"/>
<source>Flow text</source>
<translation>Plynulý text</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="802"/>
<source>Search for</source>
<translation>Hledat</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="852"/>
<source>Delete annotation</source>
<translation>Smazat vysvětlivku</translation>
</message>
</context>
<context>
<name>SettingsUI</name>
<message>
<location filename="../../settingsdialog.py" line="134"/>
<source>About</source>
<translation>O</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="71"/>
<source>Hindi</source>
<translation>Hindština</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="550"/>
<source>Delete database and exit?</source>
<translation>Smazat databázi a ukončit?</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="131"/>
<source>Library</source>
<translation>Knihovna</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="84"/>
<source>Save changes and start library scan</source>
<translation>Uložit změny a začít prohledávat knihovnu</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="329"/>
<source>Library scan in progress...</source>
<translation>Probíhá prohledávání knihovny...</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="333"/>
<source>Checking library folders</source>
<translation>Prověřují se složky s knihovnou</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="554"/>
<source>Confirm</source>
<translation>Potvrdit</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="70"/>
<source>Spanish</source>
<translation>Španělština</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="133"/>
<source>Annotations</source>
<translation>Vysvětlivky</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="69"/>
<source>English</source>
<translation>Angličtina</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="132"/>
<source>Switches</source>
<translation>Přepínače</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="349"/>
<source>Parsing files</source>
<translation>Zpracovávají se soubory</translation>
</message>
</context>
<context>
<name>SideDock</name>
<message>
<location filename="../../dockwidgets.py" line="132"/>
<source>New bookmark</source>
<translation>Lesezeichen hinzufügen</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="355"/>
<source>Search</source>
<translation>Hledat</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="131"/>
<source>Bookmarks</source>
<translation>Záložky</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="356"/>
<source>Search entire book</source>
<translation>Hledat v celé knize</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="358"/>
<source>Match word</source>
<translation>Shoda slov</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="357"/>
<source>Match case</source>
<translation>Rozlišovat velká a malá písmena</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="308"/>
<source>Annotations</source>
<translation>Vysvětlivky</translation>
</message>
</context>
<context>
<name>Tab</name>
<message>
<location filename="../../dockwidgets.py" line="272"/>
<source>Edit</source>
<translation>Upravit</translation>
</message>
<message>
<location filename="../../widgets.py" line="160"/>
<source>Note</source>
<translation>Poznámka</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="275"/>
<source>Delete</source>
<translation>Smazat</translation>
</message>
</context>
<context>
<name>TableProxyModel</name>
<message>
<location filename="../../models.py" line="75"/>
<source>Tags</source>
<translation>Značky</translation>
</message>
<message>
<location filename="../../models.py" line="73"/>
<source>Year</source>
<translation>Rok</translation>
</message>
<message>
<location filename="../../models.py" line="71"/>
<source>Title</source>
<translation>Název</translation>
</message>
<message>
<location filename="../../models.py" line="72"/>
<source>Author</source>
<translation>Autor</translation>
</message>
<message>
<location filename="../../models.py" line="74"/>
<source>Last Read</source>
<translation>Naposledy čteno</translation>
</message>
</context>
</TS>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,976 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0" language="ja_JP" sourcelanguage="">
<context>
<name>AnnotationsUI</name>
<message>
<location filename="../../annotations.py" line="39"/>
<source>Text markup</source>
<translation></translation>
</message>
<message>
<location filename="../../annotations.py" line="126"/>
<source>New annotation</source>
<translation></translation>
</message>
</context>
<context>
<name>BookToolBar</name>
<message>
<location filename="../../toolbars.py" line="43"/>
<source>View settings</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="51"/>
<source>Add bookmark</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="71"/>
<source>Reset profile</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="96"/>
<source>Font size</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="100"/>
<source>Increase padding</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="105"/>
<source>Decrease padding</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="111"/>
<source>Increase line spacing</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="116"/>
<source>Decrease line spacing</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="122"/>
<source>Left align text</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="129"/>
<source>Right align text</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="136"/>
<source>Center align text</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="143"/>
<source>Justify text</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="277"/>
<source>Background color</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="319"/>
<source>Table of Contents</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="55"/>
<source>Bookmarks (Ctrl + B)</source>
<translation> (Ctrl + B)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="47"/>
<source>Annotations (Ctrl + N)</source>
<translation> (Ctrl + N)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="59"/>
<source>Search (Ctrl + F)</source>
<translation> (Ctrl + F)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="67"/>
<source>Fullscreen (F)</source>
<translation> (F)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="216"/>
<source>Double page mode (D)</source>
<translation> (D)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="223"/>
<source>Manga mode (M)</source>
<translation> (M)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="246"/>
<source>Zoom in (+)</source>
<translation> (+)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="251"/>
<source>Zoom Out (-)</source>
<translation> (-)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="257"/>
<source>Fit Width (W)</source>
<translation> (W)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="263"/>
<source>Best Fit (B)</source>
<translation> (B)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="269"/>
<source>Original size (O)</source>
<translation> (O)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="230"/>
<source>Invert page colors</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="237"/>
<source>Rotate image clockwise</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../toolbars.py" line="241"/>
<source>Rotate image anti-clockwise</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DefinitionsUI</name>
<message>
<location filename="../../definitionsdialog.py" line="143"/>
<source>No definitions found in</source>
<translation></translation>
</message>
</context>
<context>
<name>Dialog</name>
<message>
<location filename="../definitions.py" line="65"/>
<source>Dialog</source>
<translation></translation>
</message>
<message>
<location filename="../definitions.py" line="66"/>
<source>WERDS</source>
<translation>WERDS</translation>
</message>
<message>
<location filename="../definitions.py" line="67"/>
<source>Play pronunciation of root word</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="377"/>
<source>Settings</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="378"/>
<source>Library</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="386"/>
<source>Startup: Refresh library</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="387"/>
<source>Remember open files</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="391"/>
<source>Generate tags from files</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="388"/>
<source>Cover shadows</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="389"/>
<source>Enabling reduces startup time and memory usage</source>
<translation>使</translation>
</message>
<message>
<location filename="../settingswindow.py" line="390"/>
<source>Load covers only when needed</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="400"/>
<source>Greatly reduces page transition time at the cost of more memory</source>
<translation>使</translation>
</message>
<message>
<location filename="../settingswindow.py" line="401"/>
<source>Cache comic / pdf pages</source>
<translation>PDFのページをキャッシュする</translation>
</message>
<message>
<location filename="../settingswindow.py" line="381"/>
<source>Icon theme: </source>
<translation>: </translation>
</message>
<message>
<location filename="../settingswindow.py" line="421"/>
<source>Scan Library</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="422"/>
<source>Close</source>
<translation></translation>
</message>
<message>
<location filename="../metadata.py" line="88"/>
<source>Edit metadata</source>
<translation></translation>
</message>
<message>
<location filename="../metadata.py" line="89"/>
<source>Cover (click to change)</source>
<translation> ()</translation>
</message>
<message>
<location filename="../metadata.py" line="91"/>
<source>Title</source>
<translation></translation>
</message>
<message>
<location filename="../metadata.py" line="93"/>
<source>Author</source>
<translation></translation>
</message>
<message>
<location filename="../metadata.py" line="95"/>
<source>Year</source>
<translation></translation>
</message>
<message>
<location filename="../metadata.py" line="96"/>
<source>Tags (comma separated)</source>
<translation> ()</translation>
</message>
<message>
<location filename="../metadata.py" line="97"/>
<source>Tags</source>
<translation></translation>
</message>
<message>
<location filename="../metadata.py" line="98"/>
<source>OK</source>
<translation>OK</translation>
</message>
<message>
<location filename="../metadata.py" line="99"/>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="397"/>
<source>Horizontal scrolling with Alt + Scroll
Reopen book to see changes</source>
<translation>Alt + Scroll
</translation>
</message>
<message>
<location filename="../settingswindow.py" line="399"/>
<source>Hide scrollbars when reading</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="384"/>
<source>Restart application to see changes</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="383"/>
<source>&amp;Dark</source>
<translation> (&amp;D)</translation>
</message>
<message>
<location filename="../settingswindow.py" line="385"/>
<source>L&amp;ight</source>
<translation> (&amp;i)</translation>
</message>
<message>
<location filename="../settingswindow.py" line="396"/>
<source>Reading</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="379"/>
<source>Consider book read at percent</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="408"/>
<source>Dictionary language</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="409"/>
<source>Scroll speed</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="415"/>
<source>Text</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="410"/>
<source>New</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="411"/>
<source>Delete</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="412"/>
<source>Edit</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="413"/>
<source>Move Up</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="414"/>
<source>Move Down</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="416"/>
<source>Image</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="392"/>
<source>Shrink long book titles</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="404"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UP/DOWN ARROW - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt; - &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../settingswindow.py" line="403"/>
<source>Small increment</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="407"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;SPACEBAR - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt; - &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../settingswindow.py" line="406"/>
<source>Large increment</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="394"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Attempt to download missing book covers from Google books - SLOW&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt; Google - &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../settingswindow.py" line="395"/>
<source>Download missing covers</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="417"/>
<source>About</source>
<translation>About</translation>
</message>
<message>
<location filename="../settingswindow.py" line="418"/>
<source>Log</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="419"/>
<source>Reset Application</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="420"/>
<source>Clear Log</source>
<translation></translation>
</message>
<message>
<location filename="../settingswindow.py" line="393"/>
<source>Show navigation bar</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Library</name>
<message>
<location filename="../../library.py" line="129"/>
<source>Author</source>
<translation></translation>
</message>
<message>
<location filename="../../library.py" line="130"/>
<source>Year</source>
<translation></translation>
</message>
<message>
<location filename="../../library.py" line="289"/>
<source>manually added</source>
<translation></translation>
</message>
<message>
<location filename="../../library.py" line="212"/>
<source> books</source>
<translation> </translation>
</message>
</context>
<context>
<name>LibraryToolBar</name>
<message>
<location filename="../../toolbars.py" line="402"/>
<source>Add book</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="406"/>
<source>Delete book</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="433"/>
<source>Library background color</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="438"/>
<source>Settings</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="411"/>
<source>View as covers</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="416"/>
<source>View as table</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="430"/>
<source>Filter library</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="475"/>
<source>Search for Title, Author, Tags...</source>
<translation>...</translation>
</message>
<message>
<location filename="../../toolbars.py" line="494"/>
<source>Sort by</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="422"/>
<source>Scan Library</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="481"/>
<source>Title</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="482"/>
<source>Author</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="483"/>
<source>Year</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="484"/>
<source>Newest</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="485"/>
<source>Last Read</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="486"/>
<source>Progress</source>
<translation></translation>
</message>
<message>
<location filename="../../toolbars.py" line="444"/>
<source>About</source>
<translation>Lector </translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.py" line="67"/>
<source>Lector</source>
<translation>Lector</translation>
</message>
<message>
<location filename="../mainwindow.py" line="68"/>
<source>Library</source>
<translation></translation>
</message>
</context>
<context>
<name>Main_BookToolBarUI</name>
<message>
<location filename="../../toolbars.py" line="63"/>
<source>Toggle distraction free mode (Ctrl + D)</source>
<translation> (Ctrl + D)</translation>
</message>
</context>
<context>
<name>Main_UI</name>
<message>
<location filename="../../__main__.py" line="477"/>
<source>Add books to database</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="478"/>
<source>eBooks</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="492"/>
<source>Adding books...</source>
<translation>...</translation>
</message>
<message>
<location filename="../../__main__.py" line="550"/>
<source>Confirm deletion</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="563"/>
<source>Save changes and start library scan</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="630"/>
<source> Books</source>
<translation> </translation>
</message>
<message>
<location filename="../../__main__.py" line="819"/>
<source>Start reading</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="825"/>
<source>Edit</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="829"/>
<source>Delete</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="832"/>
<source>Mark read</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="835"/>
<source>Mark unread</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="940"/>
<source>Manually Added</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="581"/>
<source> books</source>
<translation> </translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="424"/>
<source>Save page as...</source>
<translation>...</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="425"/>
<source>Images</source>
<translation></translation>
</message>
<message>
<location filename="../../__main__.py" line="546"/>
<source>Delete book(s)?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MetadataUI</name>
<message>
<location filename="../../metadatadialog.py" line="106"/>
<source>Author</source>
<translation></translation>
</message>
<message>
<location filename="../../metadatadialog.py" line="107"/>
<source>Year</source>
<translation></translation>
</message>
</context>
<context>
<name>PliantQGraphicsScene</name>
<message>
<location filename="../../widgets.py" line="621"/>
<source>Select new cover</source>
<translation></translation>
</message>
<message>
<location filename="../../widgets.py" line="622"/>
<source>Images</source>
<translation></translation>
</message>
</context>
<context>
<name>PliantQGraphicsView</name>
<message>
<location filename="../../contentwidgets.py" line="343"/>
<source>Exit fullscreen</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="347"/>
<source>Exit Distraction Free mode</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="354"/>
<source>Save page as...</source>
<translation>...</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="358"/>
<source>View</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="363"/>
<source>Double page mode (D)</source>
<translation> (D)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="370"/>
<source>Manga mode (M)</source>
<translation> (M)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="386"/>
<source>Zoom in (+)</source>
<translation> (+)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="390"/>
<source>Zoom out (-)</source>
<translation> (-)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="394"/>
<source>Fit width (W)</source>
<translation> (W)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="398"/>
<source>Best fit (B)</source>
<translation> (B)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="402"/>
<source>Original size (O)</source>
<translation> (O)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="408"/>
<source>Bookmarks</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="377"/>
<source>Invert page colors</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PliantQTextBrowser</name>
<message>
<location filename="../../contentwidgets.py" line="781"/>
<source>Exit fullscreen</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="785"/>
<source>Exit Distraction Free mode</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="797"/>
<source>Define</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="802"/>
<source>Search for</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="807"/>
<source>In this book</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="841"/>
<source>Search</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="849"/>
<source>Edit note</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="852"/>
<source>Delete annotation</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="856"/>
<source>Add Bookmark</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="862"/>
<source>Bookmarks</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="823"/>
<source>Annotate</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="763"/>
<source>View</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="768"/>
<source>Flow text</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="772"/>
<source>Single page</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="776"/>
<source>Double page</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsUI</name>
<message>
<location filename="../../settingsdialog.py" line="69"/>
<source>English</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="70"/>
<source>Spanish</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="71"/>
<source>Hindi</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="84"/>
<source>Save changes and start library scan</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="329"/>
<source>Library scan in progress...</source>
<translation>...</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="333"/>
<source>Checking library folders</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="349"/>
<source>Parsing files</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="131"/>
<source>Library</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="132"/>
<source>Switches</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="134"/>
<source>About</source>
<translation>About</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="133"/>
<source>Annotations</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="554"/>
<source>Confirm</source>
<translation></translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="550"/>
<source>Delete database and exit?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SideDock</name>
<message>
<location filename="../../dockwidgets.py" line="131"/>
<source>Bookmarks</source>
<translation></translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="132"/>
<source>New bookmark</source>
<translation></translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="308"/>
<source>Annotations</source>
<translation></translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="355"/>
<source>Search</source>
<translation></translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="356"/>
<source>Search entire book</source>
<translation></translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="357"/>
<source>Match case</source>
<translation></translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="358"/>
<source>Match word</source>
<translation></translation>
</message>
</context>
<context>
<name>Tab</name>
<message>
<location filename="../../dockwidgets.py" line="272"/>
<source>Edit</source>
<translation></translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="275"/>
<source>Delete</source>
<translation></translation>
</message>
<message>
<location filename="../../widgets.py" line="160"/>
<source>Note</source>
<translation></translation>
</message>
</context>
<context>
<name>TableProxyModel</name>
<message>
<location filename="../../models.py" line="71"/>
<source>Title</source>
<translation></translation>
</message>
<message>
<location filename="../../models.py" line="72"/>
<source>Author</source>
<translation></translation>
</message>
<message>
<location filename="../../models.py" line="73"/>
<source>Year</source>
<translation></translation>
</message>
<message>
<location filename="../../models.py" line="74"/>
<source>Last Read</source>
<translation></translation>
</message>
<message>
<location filename="../../models.py" line="75"/>
<source>Tags</source>
<translation></translation>
</message>
</context>
</TS>

View File

@@ -0,0 +1,976 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0" language="pt_BR" sourcelanguage="">
<context>
<name>AnnotationsUI</name>
<message>
<location filename="../../annotations.py" line="39"/>
<source>Text markup</source>
<translation>Marcação do texto</translation>
</message>
<message>
<location filename="../../annotations.py" line="126"/>
<source>New annotation</source>
<translation>Nova anotação</translation>
</message>
</context>
<context>
<name>BookToolBar</name>
<message>
<location filename="../../toolbars.py" line="43"/>
<source>View settings</source>
<translation>Ver configurações</translation>
</message>
<message>
<location filename="../../toolbars.py" line="51"/>
<source>Add bookmark</source>
<translation>Adicionar marca páginas</translation>
</message>
<message>
<location filename="../../toolbars.py" line="71"/>
<source>Reset profile</source>
<translation>Reinicializar perfil</translation>
</message>
<message>
<location filename="../../toolbars.py" line="96"/>
<source>Font size</source>
<translation>Tamanho da fonte</translation>
</message>
<message>
<location filename="../../toolbars.py" line="100"/>
<source>Increase padding</source>
<translation>Aumentar padding</translation>
</message>
<message>
<location filename="../../toolbars.py" line="105"/>
<source>Decrease padding</source>
<translation>Diminuir padding</translation>
</message>
<message>
<location filename="../../toolbars.py" line="111"/>
<source>Increase line spacing</source>
<translation>Aumentar espaçamento entre linhas</translation>
</message>
<message>
<location filename="../../toolbars.py" line="116"/>
<source>Decrease line spacing</source>
<translation>Diminuir espaçamento entre linhas</translation>
</message>
<message>
<location filename="../../toolbars.py" line="122"/>
<source>Left align text</source>
<translation>Alinhar texto à esquerda</translation>
</message>
<message>
<location filename="../../toolbars.py" line="129"/>
<source>Right align text</source>
<translation>Alinhar texto à direita</translation>
</message>
<message>
<location filename="../../toolbars.py" line="136"/>
<source>Center align text</source>
<translation>Centralizar texto</translation>
</message>
<message>
<location filename="../../toolbars.py" line="143"/>
<source>Justify text</source>
<translation>Justificar texto</translation>
</message>
<message>
<location filename="../../toolbars.py" line="277"/>
<source>Background color</source>
<translation>Cor do fundo</translation>
</message>
<message>
<location filename="../../toolbars.py" line="319"/>
<source>Table of Contents</source>
<translation>Índice</translation>
</message>
<message>
<location filename="../../toolbars.py" line="55"/>
<source>Bookmarks (Ctrl + B)</source>
<translation>Marcadores de página (Ctrl + B)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="47"/>
<source>Annotations (Ctrl + N)</source>
<translation>Anotações (Ctrl + N)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="59"/>
<source>Search (Ctrl + F)</source>
<translation>Procurar (Ctrl + F)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="67"/>
<source>Fullscreen (F)</source>
<translation>Tela cheia (F)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="216"/>
<source>Double page mode (D)</source>
<translation>Modo de páginas dupla (D)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="223"/>
<source>Manga mode (M)</source>
<translation>Modo mangá (M)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="246"/>
<source>Zoom in (+)</source>
<translation>Aumentar zoom (+)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="251"/>
<source>Zoom Out (-)</source>
<translation>Diminuir zoom (-)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="257"/>
<source>Fit Width (W)</source>
<translation>Ajustar largura (W)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="263"/>
<source>Best Fit (B)</source>
<translation>Melhor ajuste (B)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="269"/>
<source>Original size (O)</source>
<translation>Tamanho original (O)</translation>
</message>
<message>
<location filename="../../toolbars.py" line="230"/>
<source>Invert page colors</source>
<translation>Inverter cores da página</translation>
</message>
<message>
<location filename="../../toolbars.py" line="237"/>
<source>Rotate image clockwise</source>
<translation>Rotacionar imagem no sentido horário</translation>
</message>
<message>
<location filename="../../toolbars.py" line="241"/>
<source>Rotate image anti-clockwise</source>
<translation>Rotacionar imagem no sentido anti-horário</translation>
</message>
</context>
<context>
<name>DefinitionsUI</name>
<message>
<location filename="../../definitionsdialog.py" line="143"/>
<source>No definitions found in</source>
<translation>Nenhuma definição achada em</translation>
</message>
</context>
<context>
<name>Dialog</name>
<message>
<location filename="../definitions.py" line="65"/>
<source>Dialog</source>
<translation>Diálogo</translation>
</message>
<message>
<location filename="../definitions.py" line="66"/>
<source>WERDS</source>
<translation>PALAVRAS</translation>
</message>
<message>
<location filename="../definitions.py" line="67"/>
<source>Play pronunciation of root word</source>
<translation>Reproduzir pronúncia da palavra raiz</translation>
</message>
<message>
<location filename="../settingswindow.py" line="377"/>
<source>Settings</source>
<translation>Configurações</translation>
</message>
<message>
<location filename="../settingswindow.py" line="378"/>
<source>Library</source>
<translation>Biblioteca</translation>
</message>
<message>
<location filename="../settingswindow.py" line="386"/>
<source>Startup: Refresh library</source>
<translation>Inicialização: Atualizar biblioteca</translation>
</message>
<message>
<location filename="../settingswindow.py" line="387"/>
<source>Remember open files</source>
<translation>Lembrar dos arquivos abertos</translation>
</message>
<message>
<location filename="../settingswindow.py" line="391"/>
<source>Generate tags from files</source>
<translation>Gerar palavras-chave a partir de arquivos</translation>
</message>
<message>
<location filename="../settingswindow.py" line="388"/>
<source>Cover shadows</source>
<translation>Sombra nas capas</translation>
</message>
<message>
<location filename="../settingswindow.py" line="389"/>
<source>Enabling reduces startup time and memory usage</source>
<translation>Habilitar diminuir tempo de inicialização e uso de memória</translation>
</message>
<message>
<location filename="../settingswindow.py" line="390"/>
<source>Load covers only when needed</source>
<translation>Carregar capas somente quando necessário</translation>
</message>
<message>
<location filename="../settingswindow.py" line="400"/>
<source>Greatly reduces page transition time at the cost of more memory</source>
<translation>Reduz bastante o tempo de transição de páginas pelo custo de mais memória</translation>
</message>
<message>
<location filename="../settingswindow.py" line="401"/>
<source>Cache comic / pdf pages</source>
<translation>Armazenar comic / páginas pdf em cache</translation>
</message>
<message>
<location filename="../settingswindow.py" line="381"/>
<source>Icon theme: </source>
<translation>Tema do ícone: </translation>
</message>
<message>
<location filename="../settingswindow.py" line="421"/>
<source>Scan Library</source>
<translation>Scanear Biblioteca</translation>
</message>
<message>
<location filename="../settingswindow.py" line="422"/>
<source>Close</source>
<translation>Fechar</translation>
</message>
<message>
<location filename="../metadata.py" line="88"/>
<source>Edit metadata</source>
<translation>Editar metadados</translation>
</message>
<message>
<location filename="../metadata.py" line="89"/>
<source>Cover (click to change)</source>
<translation>Capa (clique para mudar)</translation>
</message>
<message>
<location filename="../metadata.py" line="91"/>
<source>Title</source>
<translation>Título</translation>
</message>
<message>
<location filename="../metadata.py" line="93"/>
<source>Author</source>
<translation>Autor</translation>
</message>
<message>
<location filename="../metadata.py" line="95"/>
<source>Year</source>
<translation>Ano</translation>
</message>
<message>
<location filename="../metadata.py" line="96"/>
<source>Tags (comma separated)</source>
<translation>Palavras-chave (separadas por vígula)</translation>
</message>
<message>
<location filename="../metadata.py" line="97"/>
<source>Tags</source>
<translation>Palavras-chave</translation>
</message>
<message>
<location filename="../metadata.py" line="98"/>
<source>OK</source>
<translation>OK</translation>
</message>
<message>
<location filename="../metadata.py" line="99"/>
<source>Cancel</source>
<translation>Cancelar</translation>
</message>
<message>
<location filename="../settingswindow.py" line="397"/>
<source>Horizontal scrolling with Alt + Scroll
Reopen book to see changes</source>
<translation>Rolagem horizontal com Alt + rolagem
Reabrir livro para ver mudanças</translation>
</message>
<message>
<location filename="../settingswindow.py" line="399"/>
<source>Hide scrollbars when reading</source>
<translation>Esconder barra de rolagem quando estiver lendo</translation>
</message>
<message>
<location filename="../settingswindow.py" line="384"/>
<source>Restart application to see changes</source>
<translation>Reabrir aplicativo para ver as mudanças</translation>
</message>
<message>
<location filename="../settingswindow.py" line="383"/>
<source>&amp;Dark</source>
<translation>&amp;Escuro</translation>
</message>
<message>
<location filename="../settingswindow.py" line="385"/>
<source>L&amp;ight</source>
<translation>C&amp;laro</translation>
</message>
<message>
<location filename="../settingswindow.py" line="396"/>
<source>Reading</source>
<translation>Leitura</translation>
</message>
<message>
<location filename="../settingswindow.py" line="379"/>
<source>Consider book read at percent</source>
<translation>Considerar livro lido na porcentagem</translation>
</message>
<message>
<location filename="../settingswindow.py" line="408"/>
<source>Dictionary language</source>
<translation>Língua do dicionário</translation>
</message>
<message>
<location filename="../settingswindow.py" line="409"/>
<source>Scroll speed</source>
<translation>Velocidade de rolagem</translation>
</message>
<message>
<location filename="../settingswindow.py" line="415"/>
<source>Text</source>
<translation>Texto</translation>
</message>
<message>
<location filename="../settingswindow.py" line="410"/>
<source>New</source>
<translation>Novo</translation>
</message>
<message>
<location filename="../settingswindow.py" line="411"/>
<source>Delete</source>
<translation>Deletar</translation>
</message>
<message>
<location filename="../settingswindow.py" line="412"/>
<source>Edit</source>
<translation>Editar</translation>
</message>
<message>
<location filename="../settingswindow.py" line="413"/>
<source>Move Up</source>
<translation>Mover para cima</translation>
</message>
<message>
<location filename="../settingswindow.py" line="414"/>
<source>Move Down</source>
<translation>Mover para baixo</translation>
</message>
<message>
<location filename="../settingswindow.py" line="416"/>
<source>Image</source>
<translation>Imagem</translation>
</message>
<message>
<location filename="../settingswindow.py" line="392"/>
<source>Shrink long book titles</source>
<translation>Encurtar títulos de livros longos</translation>
</message>
<message>
<location filename="../settingswindow.py" line="404"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;UP/DOWN ARROW - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;SETAS CIMA/BAIXO - Passos para virar a página de história em quadrinhos&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../settingswindow.py" line="403"/>
<source>Small increment</source>
<translation>Pequeno incremento</translation>
</message>
<message>
<location filename="../settingswindow.py" line="407"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;SPACEBAR - Steps to take before turning comicbook page&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;BARRA DE ESPAÇO - Passos para virar a página de história em quadrinhos&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../settingswindow.py" line="406"/>
<source>Large increment</source>
<translation>Grande incremento</translation>
</message>
<message>
<location filename="../settingswindow.py" line="394"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Attempt to download missing book covers from Google books - SLOW&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Tentativa de baixar capas ausentes do Google books - DEVAGAR&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../settingswindow.py" line="395"/>
<source>Download missing covers</source>
<translation>Fazer download das capas ausentes</translation>
</message>
<message>
<location filename="../settingswindow.py" line="417"/>
<source>About</source>
<translation>Sobre</translation>
</message>
<message>
<location filename="../settingswindow.py" line="418"/>
<source>Log</source>
<translation>Log</translation>
</message>
<message>
<location filename="../settingswindow.py" line="419"/>
<source>Reset Application</source>
<translation>Resetar aplicativo</translation>
</message>
<message>
<location filename="../settingswindow.py" line="420"/>
<source>Clear Log</source>
<translation>Apagar log</translation>
</message>
<message>
<location filename="../settingswindow.py" line="393"/>
<source>Show navigation bar</source>
<translation>Mostrar barra de navegação</translation>
</message>
</context>
<context>
<name>Library</name>
<message>
<location filename="../../library.py" line="129"/>
<source>Author</source>
<translation>Autor</translation>
</message>
<message>
<location filename="../../library.py" line="130"/>
<source>Year</source>
<translation>Ano</translation>
</message>
<message>
<location filename="../../library.py" line="289"/>
<source>manually added</source>
<translation>adicionado manualmente</translation>
</message>
<message>
<location filename="../../library.py" line="212"/>
<source> books</source>
<translation> livros</translation>
</message>
</context>
<context>
<name>LibraryToolBar</name>
<message>
<location filename="../../toolbars.py" line="402"/>
<source>Add book</source>
<translation>Adicionar livro</translation>
</message>
<message>
<location filename="../../toolbars.py" line="406"/>
<source>Delete book</source>
<translation>Deletar livro</translation>
</message>
<message>
<location filename="../../toolbars.py" line="433"/>
<source>Library background color</source>
<translation>Cor de fundo da biblioteca</translation>
</message>
<message>
<location filename="../../toolbars.py" line="438"/>
<source>Settings</source>
<translation>Configurações</translation>
</message>
<message>
<location filename="../../toolbars.py" line="411"/>
<source>View as covers</source>
<translation>Visualizar como capas</translation>
</message>
<message>
<location filename="../../toolbars.py" line="416"/>
<source>View as table</source>
<translation>Visualizar como tabela</translation>
</message>
<message>
<location filename="../../toolbars.py" line="430"/>
<source>Filter library</source>
<translation>Filtrar biblioteca</translation>
</message>
<message>
<location filename="../../toolbars.py" line="475"/>
<source>Search for Title, Author, Tags...</source>
<translation>Procurar por título, autor, palavras-chave...</translation>
</message>
<message>
<location filename="../../toolbars.py" line="494"/>
<source>Sort by</source>
<translation>Ordenar por</translation>
</message>
<message>
<location filename="../../toolbars.py" line="422"/>
<source>Scan Library</source>
<translation>Scanear biblioteca</translation>
</message>
<message>
<location filename="../../toolbars.py" line="481"/>
<source>Title</source>
<translation>Título</translation>
</message>
<message>
<location filename="../../toolbars.py" line="482"/>
<source>Author</source>
<translation>Autor</translation>
</message>
<message>
<location filename="../../toolbars.py" line="483"/>
<source>Year</source>
<translation>Ano</translation>
</message>
<message>
<location filename="../../toolbars.py" line="484"/>
<source>Newest</source>
<translation>Mais novo</translation>
</message>
<message>
<location filename="../../toolbars.py" line="485"/>
<source>Last Read</source>
<translation>Último lido</translation>
</message>
<message>
<location filename="../../toolbars.py" line="486"/>
<source>Progress</source>
<translation>Progresso</translation>
</message>
<message>
<location filename="../../toolbars.py" line="444"/>
<source>About</source>
<translation>Sobre</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.py" line="67"/>
<source>Lector</source>
<translation>Lector</translation>
</message>
<message>
<location filename="../mainwindow.py" line="68"/>
<source>Library</source>
<translation>Biblioteca</translation>
</message>
</context>
<context>
<name>Main_BookToolBarUI</name>
<message>
<location filename="../../toolbars.py" line="63"/>
<source>Toggle distraction free mode (Ctrl + D)</source>
<translation>Alternar modo sem distrações (Ctrl +D)</translation>
</message>
</context>
<context>
<name>Main_UI</name>
<message>
<location filename="../../__main__.py" line="477"/>
<source>Add books to database</source>
<translation>Adicionar livros na base de dados</translation>
</message>
<message>
<location filename="../../__main__.py" line="478"/>
<source>eBooks</source>
<translation>eBooks</translation>
</message>
<message>
<location filename="../../__main__.py" line="492"/>
<source>Adding books...</source>
<translation>Adicionando livros...</translation>
</message>
<message>
<location filename="../../__main__.py" line="550"/>
<source>Confirm deletion</source>
<translation>Confirmar remoção</translation>
</message>
<message>
<location filename="../../__main__.py" line="563"/>
<source>Save changes and start library scan</source>
<translation>Salvar mudanças e começar scanear bibloteca</translation>
</message>
<message>
<location filename="../../__main__.py" line="630"/>
<source> Books</source>
<translation> Livros</translation>
</message>
<message>
<location filename="../../__main__.py" line="819"/>
<source>Start reading</source>
<translation>Começar leitura</translation>
</message>
<message>
<location filename="../../__main__.py" line="825"/>
<source>Edit</source>
<translation>Editar</translation>
</message>
<message>
<location filename="../../__main__.py" line="829"/>
<source>Delete</source>
<translation>Deletar</translation>
</message>
<message>
<location filename="../../__main__.py" line="832"/>
<source>Mark read</source>
<translation>Marcar como lido</translation>
</message>
<message>
<location filename="../../__main__.py" line="835"/>
<source>Mark unread</source>
<translation>Marcar como não-lido</translation>
</message>
<message>
<location filename="../../__main__.py" line="940"/>
<source>Manually Added</source>
<translation>Adicionado manualmente</translation>
</message>
<message>
<location filename="../../__main__.py" line="581"/>
<source> books</source>
<translation> livros</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="424"/>
<source>Save page as...</source>
<translation>Salvar página como...</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="425"/>
<source>Images</source>
<translation>Imagens</translation>
</message>
<message>
<location filename="../../__main__.py" line="546"/>
<source>Delete book(s)?</source>
<translation>Deletar livro(s)?</translation>
</message>
</context>
<context>
<name>MetadataUI</name>
<message>
<location filename="../../metadatadialog.py" line="106"/>
<source>Author</source>
<translation>Autor</translation>
</message>
<message>
<location filename="../../metadatadialog.py" line="107"/>
<source>Year</source>
<translation>Ano</translation>
</message>
</context>
<context>
<name>PliantQGraphicsScene</name>
<message>
<location filename="../../widgets.py" line="621"/>
<source>Select new cover</source>
<translation>Selecionar nova capa</translation>
</message>
<message>
<location filename="../../widgets.py" line="622"/>
<source>Images</source>
<translation>Imagens</translation>
</message>
</context>
<context>
<name>PliantQGraphicsView</name>
<message>
<location filename="../../contentwidgets.py" line="343"/>
<source>Exit fullscreen</source>
<translation>Sair da tela cheia</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="347"/>
<source>Exit Distraction Free mode</source>
<translation>Sair do modo sem distrações</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="354"/>
<source>Save page as...</source>
<translation>Salvar página como...</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="358"/>
<source>View</source>
<translation>Visualização</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="363"/>
<source>Double page mode (D)</source>
<translation>Modo de páginas dupla (D)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="370"/>
<source>Manga mode (M)</source>
<translation>Modo mangá (M)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="386"/>
<source>Zoom in (+)</source>
<translation>Aumentar zoom (+)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="390"/>
<source>Zoom out (-)</source>
<translation>Diminuir zoom (-)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="394"/>
<source>Fit width (W)</source>
<translation>Ajustar largura (W)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="398"/>
<source>Best fit (B)</source>
<translation>Melhor ajuste (B)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="402"/>
<source>Original size (O)</source>
<translation>Tamanho original (O)</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="408"/>
<source>Bookmarks</source>
<translation>Marca páginas</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="377"/>
<source>Invert page colors</source>
<translation>Inverter cores da página</translation>
</message>
</context>
<context>
<name>PliantQTextBrowser</name>
<message>
<location filename="../../contentwidgets.py" line="781"/>
<source>Exit fullscreen</source>
<translation>Sair da tela cheia</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="785"/>
<source>Exit Distraction Free mode</source>
<translation>Sair do modo sem distrações</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="797"/>
<source>Define</source>
<translation>Definir</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="802"/>
<source>Search for</source>
<translation>Procurar por</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="807"/>
<source>In this book</source>
<translation>Neste livro</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="841"/>
<source>Search</source>
<translation>Procurar</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="849"/>
<source>Edit note</source>
<translation>Editar nota</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="852"/>
<source>Delete annotation</source>
<translation>Deletar anotação</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="856"/>
<source>Add Bookmark</source>
<translation>Adicionar marca páginas</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="862"/>
<source>Bookmarks</source>
<translation>Marca páginas</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="823"/>
<source>Annotate</source>
<translation>Anotar</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="763"/>
<source>View</source>
<translation>Visualização</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="768"/>
<source>Flow text</source>
<translation></translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="772"/>
<source>Single page</source>
<translation>Página simples</translation>
</message>
<message>
<location filename="../../contentwidgets.py" line="776"/>
<source>Double page</source>
<translation>Página dupla</translation>
</message>
</context>
<context>
<name>SettingsUI</name>
<message>
<location filename="../../settingsdialog.py" line="69"/>
<source>English</source>
<translation>Inglês</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="70"/>
<source>Spanish</source>
<translation>Espanhol</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="71"/>
<source>Hindi</source>
<translation>Hindi</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="84"/>
<source>Save changes and start library scan</source>
<translation>Salvar mudanças e começar scanear bibloteca</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="329"/>
<source>Library scan in progress...</source>
<translation>Scan da biblioteca em progresso...</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="333"/>
<source>Checking library folders</source>
<translation>Checando pastas da biblioteca</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="349"/>
<source>Parsing files</source>
<translation>Analisando arquivos</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="131"/>
<source>Library</source>
<translation>Biblioteca</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="132"/>
<source>Switches</source>
<translation>Configurações</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="134"/>
<source>About</source>
<translation>Sobre</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="133"/>
<source>Annotations</source>
<translation>Anotações</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="554"/>
<source>Confirm</source>
<translation>Confirmar</translation>
</message>
<message>
<location filename="../../settingsdialog.py" line="550"/>
<source>Delete database and exit?</source>
<translation>Deletar banco de dados e sair?</translation>
</message>
</context>
<context>
<name>SideDock</name>
<message>
<location filename="../../dockwidgets.py" line="131"/>
<source>Bookmarks</source>
<translation>Marca páginas</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="132"/>
<source>New bookmark</source>
<translation>Novo marca páginas</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="308"/>
<source>Annotations</source>
<translation>Anotações</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="355"/>
<source>Search</source>
<translation>Procurar</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="356"/>
<source>Search entire book</source>
<translation>Procurar no livro todo</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="357"/>
<source>Match case</source>
<translation>Caso correspondente</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="358"/>
<source>Match word</source>
<translation>Palavra correspondente</translation>
</message>
</context>
<context>
<name>Tab</name>
<message>
<location filename="../../dockwidgets.py" line="272"/>
<source>Edit</source>
<translation>Editar</translation>
</message>
<message>
<location filename="../../dockwidgets.py" line="275"/>
<source>Delete</source>
<translation>Deletar</translation>
</message>
<message>
<location filename="../../widgets.py" line="160"/>
<source>Note</source>
<translation>Nota</translation>
</message>
</context>
<context>
<name>TableProxyModel</name>
<message>
<location filename="../../models.py" line="71"/>
<source>Title</source>
<translation>Título</translation>
</message>
<message>
<location filename="../../models.py" line="72"/>
<source>Author</source>
<translation>Autor</translation>
</message>
<message>
<location filename="../../models.py" line="73"/>
<source>Year</source>
<translation>Ano</translation>
</message>
<message>
<location filename="../../models.py" line="74"/>
<source>Last Read</source>
<translation>Último lido</translation>
</message>
<message>
<location filename="../../models.py" line="75"/>
<source>Tags</source>
<translation>Palavras-chave</translation>
</message>
</context>
</TS>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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