diff --git a/README.md b/README.md
index f3d9d00..17e3957 100644
--- a/README.md
+++ b/README.md
@@ -11,26 +11,37 @@
**KOHighlights** is a utility for viewing and exporting the
[Koreader](https://github.com/koreader/koreader)'s highlights to simple text, html, csv or markdown files.
-This is a totally re-written application using the Qt framework (PySide).
-The original KOHighlights (using the wxPython) can be found
-[here](https://github.com/noonkey/KoHighlights), but is considered deprecated..
-
-### Screenshots
+___
+#### Screenshots
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+___
-## Usage
+## Usage/Features
* Load items by:
* Selecting the reader's drive or any folder that contains books that where opened with Koreader. This will automatically load all the metadata files from all subdirectories.
* Drag and drop files or folders. This will load the files and/or all the files inside the folders.
@@ -41,26 +52,20 @@ The original KOHighlights (using the wxPython) can be found
* Comma-separated values files (.csv)
* Markdown files (.md)
* View the highlights and various info for a book by selecting it in the list.
-* Save the highlights to the "Archive" and view them, even if your reader is not connected.
-* Merge highlights/Sync position from the same book that is read in two different devices and/or sync its reading position. To do it you have to:
- * Load both metadata (e.g. by scanning your reader's _and_ your tablet's books).
- * Select the relevant rows of the (same) book.
- * If the book has the same cre_dom_version (version of the CREngine), then the "Merge/Sync" button gets activated and you get the options to sync the highlights or the position or both.
-* Merge highlights/Sync position of a book with its archived version
- (book's right click menu)
-* Show/hide the page, date, chapter or even the highlight text while viewing or saving the highlights of the books.
+* Save the highlights to the "Archive" database and view them, even if your reader is not connected.
+* Merge highlights/Sync position from the same book that is read in different devices and/or sync its reading position.
+* Merge highlights/Sync position of a book with its archived version.
+* Show/hide the page, date, chapter or even the highlight (!) text while viewing or exporting the highlights of the books.
* Double click or press the Open Book button to view the book in your system's reader.
* Delete some or all the highlights of any book.
* Clear/reset the .sdr folders with the metadata or the books in the eReader.
### Prerequisites
-These plugins must be enabled in KOReader
-* Progress sync plugin
-* Reading statistics plugin
+The progress sync plugin must be enabled in KOReader
### Portable
In Windows, KOHighlights can run in Portable mode using a `portable_settings` directory to store its settings, that is located inside the installation directory of the app.
-Because of this, it is advised to not install the app inside the `Program Files` folder if you indent to use it as portable.
+Because of this, it is advised to not install the app inside the default `Program Files` folder if you indent to use it as portable.
There are two ways to start the app in Portable mode:
* Run the `KoHighlights Portable.exe` that is located next to the `KoHighlights.exe`.
* Run `KoHighlights.exe` with a `-p` argument.
@@ -71,13 +76,27 @@ Check the latest release on the [Downloads Page][ReleaseLink].
Read the version history at [App's Page](http://www.noembryo.com/apps.php?kohighlights).
## Dependencies
-Should run in any system with Python 2.7.x or 3.x (more testing required)
-It needs the [PySide](https://pypi.org/project/PySide/),
+* **Source code:**
+Should run in any system with Python 3.6+ (more testing required)
+It needs the [PySide2](https://pypi.org/project/PySide2/),
[BeautifulSoup4](https://pypi.org/project/beautifulsoup4/),
-[future](https://pypi.org/project/future/) and
+[packaging](https://pypi.org/project/packaging/) and
[requests](https://pypi.org/project/requests/) libraries.
-In Linux the `libqt4-sql-sqlite` package must be installed.
-PySide2/PySide6 are also supported (download the archive from the releases)
+In Windows, it might also need the [PyWin32](https://pypi.org/project/PyWin32/) and the [Pypiwin32](https://pypi.org/project/pypiwin32/) libraries.
+PySide6 is also supported (download the archive from the releases)
+* **Compiled binaries:**
+ * ***Windows***:
+ From version 2.x, KOHighlights dropped support for Windows XP.
+ Can run on any version of Windows from Windows 7 upwards.
+ For Windows 7, Microsoft Visual C++ 14.0 is required. Get it
+ [here](https://aka.ms/vs/17/release/vc_redist.x86.exe).
+ The Windows 7 32bit version also needs the KB2533623 update thats is included in [KB3063858](https://www.microsoft.com/en-us/download/details.aspx?id=47409) ([direct link](https://download.microsoft.com/download/C/9/6/C96CD606-3E05-4E1C-B201-51211AE80B1E/Windows6.1-KB3063858-x86.msu)).
+ * ***Linux***:
+ The binary is compiled using Xubuntu 18.04.
+ Any newer version should work.
+
+
+
## Extra
KOHighlights includes SLPPU (a converter between python and lua objects).
diff --git a/boot_config.py b/boot_config.py
index 80a0ab0..a173842 100644
--- a/boot_config.py
+++ b/boot_config.py
@@ -1,46 +1,35 @@
# coding=utf-8
-from __future__ import (absolute_import, division, print_function, unicode_literals)
-
import time
import sys, os
import traceback
import gzip, json
from os.path import dirname, join, isdir, expanduser
+__author__ = "noEmbryo"
+
def _(text): # for future gettext support
return text
+
APP_NAME = "KOHighlights"
APP_DIR = dirname(os.path.abspath(sys.argv[0]))
os.chdir(APP_DIR) # Set the current working directory to the app's directory
-PORTABLE = False
-PYTHON2 = sys.version_info < (3, 0)
-
USE_QT6 = False # select between PySide2/Qt5 and Pyside6/Qt6 if both are installed
-
-if PYTHON2:
- from io import open
- from codecs import open as c_open
- from PySide.QtCore import qVersion
+if USE_QT6:
+ from PySide6.QtCore import qVersion
else:
- # noinspection PyShadowingBuiltins
- unicode, basestring = str, str
- c_open = open
- if USE_QT6:
- from PySide6.QtCore import qVersion
- else:
- from PySide2.QtCore import qVersion
+ from PySide2.QtCore import qVersion
# noinspection PyTypeChecker
qt_version = qVersion().split(".")[0]
-QT4 = qt_version == "4"
QT5 = qt_version == "5"
QT6 = qt_version == "6"
if QT6 and QT5 and USE_QT6:
QT5 = False
+PORTABLE = False
if sys.platform == "win32": # Windows
import win32api
import win32event
@@ -65,7 +54,7 @@ def __del__(self):
sys.exit(0)
try:
# noinspection PyUnresolvedReferences
- portable_arg = sys.argv[1] if not PYTHON2 else sys.argv[1].decode("mbcs")
+ portable_arg = sys.argv[1]
PORTABLE = portable_arg == "-p"
except IndexError: # no arguments in the call
pass
@@ -86,7 +75,7 @@ def __del__(self):
import socket
app_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# Create an abstract socket, by prefixing it with null.
- app_socket.bind(str("\0{}_lock_port".format(APP_NAME)))
+ app_socket.bind(str(f"\0{APP_NAME}_lock_port"))
except socket.error: # port in use - another instance is running
sys.exit(0)
SETTINGS_DIR = join(expanduser("~"), ".config", APP_NAME)
@@ -96,10 +85,10 @@ def __del__(self):
def except_hook(class_type, value, trace_back):
""" Print the error to a log file
"""
- name = join(SETTINGS_DIR, "error_log_{}.txt".format(time.strftime(str("%Y-%m-%d"))))
+ name = join(SETTINGS_DIR, f"error_log_{time.strftime(str('%Y-%m-%d'))}.txt")
with open(name, "a", encoding="utf8") as log:
- log.write("\nCrash@{}\n".format(time.strftime(str("%Y-%m-%d %H:%M:%S"))))
- traceback.print_exception(class_type, value, trace_back, file=c_open(name, str("a")))
+ log.write(f"\nCrash@{time.strftime(str('%Y-%m-%d %H:%M:%S'))}\n")
+ traceback.print_exception(class_type, value, trace_back, file=open(name, str("a")))
sys.__excepthook__(class_type, value, trace_back)
@@ -108,14 +97,13 @@ def except_hook(class_type, value, trace_back):
# noinspection PyBroadException
try:
with gzip.GzipFile(join(SETTINGS_DIR, "settings.json.gz")) as settings:
- j_text = settings.read() if PYTHON2 else settings.read().decode("utf8")
- app_config = json.loads(j_text)
+ app_config = json.loads(settings.read().decode("utf8"))
except Exception: # IOError on first run or everything else
app_config = {}
FIRST_RUN = True
-BOOKS_VIEW, HIGHLIGHTS_VIEW = range(2) # app views
+BOOKS_VIEW, HIGHLIGHTS_VIEW, SYNC_VIEW = range(3) # app views
CHANGE_DB, NEW_DB, RELOAD_DB = range(3) # db change mode
(TITLE, AUTHOR, TYPE, PERCENT, RATING,
HIGH_COUNT, MODIFIED, PATH) = range(8) # file_table columns
@@ -125,7 +113,11 @@ def except_hook(class_type, value, trace_back):
(MANY_TEXT, ONE_TEXT, MANY_HTML, ONE_HTML,
MANY_CSV, ONE_CSV, MANY_MD, ONE_MD) = range(8) # save_actions
DB_MD5, DB_DATE, DB_PATH, DB_DATA = range(4) # db data (columns)
-FILTER_ALL, FILTER_HIGH, FILTER_COMM, FILTER_TITLES = range(4) # db data (columns)
+FILTER_ALL, FILTER_HIGH, FILTER_COMM, FILTER_TITLES = range(4) # filter type
+(THEME_NONE_OLD, THEME_NONE_NEW, THEME_DARK_OLD, THEME_DARK_NEW,
+ THEME_LIGHT_OLD, THEME_LIGHT_NEW) = range(6) # theme idx
+ACT_PAGE, ACT_DATE, ACT_TEXT, ACT_CHAPTER, ACT_COMMENT = range(5) # show items actions
+
NO_TITLE = _("NO TITLE FOUND")
NO_AUTHOR = _("NO AUTHOR FOUND")
@@ -133,6 +125,12 @@ def except_hook(class_type, value, trace_back):
DO_NOT_SHOW = _("Don't show this again")
DB_VERSION = 0
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
+TOOLTIP_MERGE = _("Merge the highlights from the same book in two different\ndevices, "
+ "and/or sync their reading position.\nActivated only if two entries "
+ "of the same book are selected.")
+TOOLTIP_SYNC = _("Start the sync process for all enabled groups")
+SYNC_FILE = join(SETTINGS_DIR, "sync_groups.json")
+
CSV_HEAD = "Title\tAuthors\tPage\tDate\tChapter\tHighlight\tComment\n"
CSV_KEYS = ["title", "authors", "page", "date", "chapter", "text", "comment"]
HTML_HEAD = """
diff --git a/gui_about.py b/gui_about.py
index aa16794..28d4727 100644
--- a/gui_about.py
+++ b/gui_about.py
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_about.ui'
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_about.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_about.ui' applies.
#
-# Created: Thu Feb 6 12:55:05 2020
-# by: pyside-uic 0.2.15 running on PySide 1.2.4
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
-from PySide import QtCore, QtGui
+from PySide2 import QtCore, QtGui, QtWidgets
class Ui_About(object):
def setupUi(self, About):
@@ -16,28 +17,28 @@ def setupUi(self, About):
About.resize(480, 560)
About.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
About.setModal(False)
- self.verticalLayout = QtGui.QVBoxLayout(About)
+ self.verticalLayout = QtWidgets.QVBoxLayout(About)
self.verticalLayout.setObjectName("verticalLayout")
- self.about_tabs = QtGui.QTabWidget(About)
- self.about_tabs.setTabShape(QtGui.QTabWidget.Rounded)
+ self.about_tabs = QtWidgets.QTabWidget(About)
+ self.about_tabs.setTabShape(QtWidgets.QTabWidget.Rounded)
self.about_tabs.setObjectName("about_tabs")
- self.info_tab = QtGui.QWidget()
+ self.info_tab = QtWidgets.QWidget()
self.info_tab.setObjectName("info_tab")
- self.verticalLayout_2 = QtGui.QVBoxLayout(self.info_tab)
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.info_tab)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.scrollArea_2 = QtGui.QScrollArea(self.info_tab)
+ self.scrollArea_2 = QtWidgets.QScrollArea(self.info_tab)
self.scrollArea_2.setStyleSheet("QScrollArea {background-color:transparent;}")
self.scrollArea_2.setWidgetResizable(True)
self.scrollArea_2.setObjectName("scrollArea_2")
- self.scrollAreaWidgetContents_2 = QtGui.QWidget()
+ self.scrollAreaWidgetContents_2 = QtWidgets.QWidget()
self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 454, 485))
self.scrollAreaWidgetContents_2.setStyleSheet("background-color:transparent;")
self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
- self.verticalLayout_6 = QtGui.QVBoxLayout(self.scrollAreaWidgetContents_2)
+ self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_2)
self.verticalLayout_6.setContentsMargins(6, 0, 6, 0)
self.verticalLayout_6.setObjectName("verticalLayout_6")
- self.text_lbl = QtGui.QLabel(self.scrollAreaWidgetContents_2)
+ self.text_lbl = QtWidgets.QLabel(self.scrollAreaWidgetContents_2)
self.text_lbl.setText("")
self.text_lbl.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.text_lbl.setWordWrap(True)
@@ -47,12 +48,12 @@ def setupUi(self, About):
self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2)
self.verticalLayout_2.addWidget(self.scrollArea_2)
self.about_tabs.addTab(self.info_tab, "")
- self.log_tab = QtGui.QWidget()
+ self.log_tab = QtWidgets.QWidget()
self.log_tab.setObjectName("log_tab")
- self.verticalLayout_8 = QtGui.QVBoxLayout(self.log_tab)
+ self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.log_tab)
self.verticalLayout_8.setObjectName("verticalLayout_8")
- self.log_txt = QtGui.QPlainTextEdit(self.log_tab)
- self.log_txt.setFrameShape(QtGui.QFrame.WinPanel)
+ self.log_txt = QtWidgets.QPlainTextEdit(self.log_tab)
+ self.log_txt.setFrameShape(QtWidgets.QFrame.WinPanel)
self.log_txt.setDocumentTitle("")
self.log_txt.setUndoRedoEnabled(False)
self.log_txt.setReadOnly(True)
@@ -61,27 +62,27 @@ def setupUi(self, About):
self.verticalLayout_8.addWidget(self.log_txt)
self.about_tabs.addTab(self.log_tab, "")
self.verticalLayout.addWidget(self.about_tabs)
- self.btn_box = QtGui.QFrame(About)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.btn_box = QtWidgets.QFrame(About)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.btn_box.sizePolicy().hasHeightForWidth())
self.btn_box.setSizePolicy(sizePolicy)
self.btn_box.setObjectName("btn_box")
- self.horizontalLayout = QtGui.QHBoxLayout(self.btn_box)
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.btn_box)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
- self.about_qt_btn = QtGui.QPushButton(self.btn_box)
+ self.about_qt_btn = QtWidgets.QPushButton(self.btn_box)
self.about_qt_btn.setObjectName("about_qt_btn")
self.horizontalLayout.addWidget(self.about_qt_btn)
- spacerItem = QtGui.QSpacerItem(92, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ spacerItem = QtWidgets.QSpacerItem(92, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
- self.updates_btn = QtGui.QPushButton(self.btn_box)
+ self.updates_btn = QtWidgets.QPushButton(self.btn_box)
self.updates_btn.setObjectName("updates_btn")
self.horizontalLayout.addWidget(self.updates_btn)
- spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
- self.close_btn = QtGui.QPushButton(self.btn_box)
+ self.close_btn = QtWidgets.QPushButton(self.btn_box)
self.close_btn.setObjectName("close_btn")
self.horizontalLayout.addWidget(self.close_btn)
self.verticalLayout.addWidget(self.btn_box)
@@ -92,11 +93,11 @@ def setupUi(self, About):
QtCore.QMetaObject.connectSlotsByName(About)
def retranslateUi(self, About):
- self.about_tabs.setTabText(self.about_tabs.indexOf(self.info_tab), QtGui.QApplication.translate("About", "Information", None, QtGui.QApplication.UnicodeUTF8))
- self.about_tabs.setTabText(self.about_tabs.indexOf(self.log_tab), QtGui.QApplication.translate("About", "Log", None, QtGui.QApplication.UnicodeUTF8))
- self.about_qt_btn.setText(QtGui.QApplication.translate("About", "About Qt", None, QtGui.QApplication.UnicodeUTF8))
- self.updates_btn.setToolTip(QtGui.QApplication.translate("About", "Check online for an updated version", None, QtGui.QApplication.UnicodeUTF8))
- self.updates_btn.setText(QtGui.QApplication.translate("About", "Check for Updates", None, QtGui.QApplication.UnicodeUTF8))
- self.close_btn.setText(QtGui.QApplication.translate("About", "Close", None, QtGui.QApplication.UnicodeUTF8))
+ self.about_tabs.setTabText(self.about_tabs.indexOf(self.info_tab), QtWidgets.QApplication.translate("About", "Information", None, -1))
+ self.about_tabs.setTabText(self.about_tabs.indexOf(self.log_tab), QtWidgets.QApplication.translate("About", "Log", None, -1))
+ self.about_qt_btn.setText(QtWidgets.QApplication.translate("About", "About Qt", None, -1))
+ self.updates_btn.setToolTip(QtWidgets.QApplication.translate("About", "Check online for an updated version", None, -1))
+ self.updates_btn.setText(QtWidgets.QApplication.translate("About", "Check for Updates", None, -1))
+ self.close_btn.setText(QtWidgets.QApplication.translate("About", "Close", None, -1))
import images_rc
diff --git a/gui_auto_info.py b/gui_auto_info.py
index 2aff20f..a99639f 100644
--- a/gui_auto_info.py
+++ b/gui_auto_info.py
@@ -1,37 +1,38 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_auto_info.ui'
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_auto_info.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_auto_info.ui' applies.
#
-# Created: Thu Nov 24 15:53:16 2022
-# by: pyside-uic 0.2.15 running on PySide 1.2.4
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
-from PySide import QtCore, QtGui
+from PySide2 import QtCore, QtGui, QtWidgets
class Ui_AutoInfo(object):
def setupUi(self, AutoInfo):
AutoInfo.setObjectName("AutoInfo")
AutoInfo.setWindowModality(QtCore.Qt.NonModal)
AutoInfo.resize(300, 100)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(AutoInfo.sizePolicy().hasHeightForWidth())
AutoInfo.setSizePolicy(sizePolicy)
AutoInfo.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
AutoInfo.setModal(True)
- self.verticalLayout = QtGui.QVBoxLayout(AutoInfo)
+ self.verticalLayout = QtWidgets.QVBoxLayout(AutoInfo)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
- self.label = QtGui.QLabel(AutoInfo)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
+ self.label = QtWidgets.QLabel(AutoInfo)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
- self.label.setFrameShape(QtGui.QFrame.Box)
- self.label.setFrameShadow(QtGui.QFrame.Raised)
+ self.label.setFrameShape(QtWidgets.QFrame.Box)
+ self.label.setFrameShadow(QtWidgets.QFrame.Raised)
self.label.setText("")
self.label.setTextFormat(QtCore.Qt.AutoText)
self.label.setAlignment(QtCore.Qt.AlignCenter)
@@ -43,6 +44,6 @@ def setupUi(self, AutoInfo):
QtCore.QMetaObject.connectSlotsByName(AutoInfo)
def retranslateUi(self, AutoInfo):
- AutoInfo.setWindowTitle(QtGui.QApplication.translate("AutoInfo", "Info", None, QtGui.QApplication.UnicodeUTF8))
+ AutoInfo.setWindowTitle(QtWidgets.QApplication.translate("AutoInfo", "Info", None, -1))
import images_rc
diff --git a/gui_edit.py b/gui_edit.py
index 092f663..f4918db 100644
--- a/gui_edit.py
+++ b/gui_edit.py
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_edit.ui'
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_edit.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_edit.ui' applies.
#
-# Created: Sun Mar 31 18:17:28 2019
-# by: pyside-uic 0.2.15 running on PySide 1.2.4
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
-from PySide import QtCore, QtGui
+from PySide2 import QtCore, QtGui, QtWidgets
class Ui_TextDialog(object):
def setupUi(self, TextDialog):
@@ -16,29 +17,29 @@ def setupUi(self, TextDialog):
TextDialog.resize(360, 180)
TextDialog.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
TextDialog.setModal(False)
- self.verticalLayout = QtGui.QVBoxLayout(TextDialog)
+ self.verticalLayout = QtWidgets.QVBoxLayout(TextDialog)
self.verticalLayout.setObjectName("verticalLayout")
- self.high_edit_txt = QtGui.QTextEdit(TextDialog)
- self.high_edit_txt.setFrameShape(QtGui.QFrame.WinPanel)
+ self.high_edit_txt = QtWidgets.QTextEdit(TextDialog)
+ self.high_edit_txt.setFrameShape(QtWidgets.QFrame.WinPanel)
self.high_edit_txt.setAcceptRichText(False)
self.high_edit_txt.setObjectName("high_edit_txt")
self.verticalLayout.addWidget(self.high_edit_txt)
- self.btn_box = QtGui.QFrame(TextDialog)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.btn_box = QtWidgets.QFrame(TextDialog)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.btn_box.sizePolicy().hasHeightForWidth())
self.btn_box.setSizePolicy(sizePolicy)
self.btn_box.setObjectName("btn_box")
- self.horizontalLayout = QtGui.QHBoxLayout(self.btn_box)
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.btn_box)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
- spacerItem = QtGui.QSpacerItem(175, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ spacerItem = QtWidgets.QSpacerItem(175, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
- self.ok_btn = QtGui.QPushButton(self.btn_box)
+ self.ok_btn = QtWidgets.QPushButton(self.btn_box)
self.ok_btn.setObjectName("ok_btn")
self.horizontalLayout.addWidget(self.ok_btn)
- self.cancel_btn = QtGui.QPushButton(self.btn_box)
+ self.cancel_btn = QtWidgets.QPushButton(self.btn_box)
self.cancel_btn.setObjectName("cancel_btn")
self.horizontalLayout.addWidget(self.cancel_btn)
self.verticalLayout.addWidget(self.btn_box)
@@ -49,8 +50,8 @@ def setupUi(self, TextDialog):
QtCore.QMetaObject.connectSlotsByName(TextDialog)
def retranslateUi(self, TextDialog):
- self.ok_btn.setToolTip(QtGui.QApplication.translate("TextDialog", "Check online for an updated version", None, QtGui.QApplication.UnicodeUTF8))
- self.ok_btn.setText(QtGui.QApplication.translate("TextDialog", "OK", None, QtGui.QApplication.UnicodeUTF8))
- self.cancel_btn.setText(QtGui.QApplication.translate("TextDialog", "Cancel", None, QtGui.QApplication.UnicodeUTF8))
+ self.ok_btn.setToolTip(QtWidgets.QApplication.translate("TextDialog", "Check online for an updated version", None, -1))
+ self.ok_btn.setText(QtWidgets.QApplication.translate("TextDialog", "OK", None, -1))
+ self.cancel_btn.setText(QtWidgets.QApplication.translate("TextDialog", "Cancel", None, -1))
import images_rc
diff --git a/gui_filter.py b/gui_filter.py
index a09243b..4f456bd 100644
--- a/gui_filter.py
+++ b/gui_filter.py
@@ -1,31 +1,32 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_filter.ui'
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_filter.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_filter.ui' applies.
#
-# Created: Thu Apr 6 23:38:59 2023
-# by: pyside-uic 0.2.15 running on PySide 1.2.4
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
-from PySide import QtCore, QtGui
+from PySide2 import QtCore, QtGui, QtWidgets
class Ui_Filter(object):
def setupUi(self, Filter):
Filter.setObjectName("Filter")
Filter.resize(215, 66)
Filter.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
- self.verticalLayout = QtGui.QVBoxLayout(Filter)
+ self.verticalLayout = QtWidgets.QVBoxLayout(Filter)
self.verticalLayout.setContentsMargins(4, 4, 4, 4)
self.verticalLayout.setObjectName("verticalLayout")
- self.filter_frm1 = QtGui.QFrame(Filter)
- self.filter_frm1.setFrameShape(QtGui.QFrame.StyledPanel)
- self.filter_frm1.setFrameShadow(QtGui.QFrame.Raised)
+ self.filter_frm1 = QtWidgets.QFrame(Filter)
+ self.filter_frm1.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.filter_frm1.setFrameShadow(QtWidgets.QFrame.Raised)
self.filter_frm1.setObjectName("filter_frm1")
- self.horizontalLayout_4 = QtGui.QHBoxLayout(self.filter_frm1)
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.filter_frm1)
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
- self.filter_txt = QtGui.QLineEdit(self.filter_frm1)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
+ self.filter_txt = QtWidgets.QLineEdit(self.filter_frm1)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.filter_txt.sizePolicy().hasHeightForWidth())
@@ -33,8 +34,8 @@ def setupUi(self, Filter):
self.filter_txt.setText("")
self.filter_txt.setObjectName("filter_txt")
self.horizontalLayout_4.addWidget(self.filter_txt)
- self.filter_btn = QtGui.QPushButton(self.filter_frm1)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+ self.filter_btn = QtWidgets.QPushButton(self.filter_frm1)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.filter_btn.sizePolicy().hasHeightForWidth())
@@ -45,28 +46,28 @@ def setupUi(self, Filter):
self.filter_btn.setObjectName("filter_btn")
self.horizontalLayout_4.addWidget(self.filter_btn)
self.verticalLayout.addWidget(self.filter_frm1)
- self.filter_frm2 = QtGui.QFrame(Filter)
- self.filter_frm2.setFrameShape(QtGui.QFrame.StyledPanel)
- self.filter_frm2.setFrameShadow(QtGui.QFrame.Raised)
+ self.filter_frm2 = QtWidgets.QFrame(Filter)
+ self.filter_frm2.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.filter_frm2.setFrameShadow(QtWidgets.QFrame.Raised)
self.filter_frm2.setObjectName("filter_frm2")
- self.horizontalLayout = QtGui.QHBoxLayout(self.filter_frm2)
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.filter_frm2)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
- self.filter_box = QtGui.QComboBox(self.filter_frm2)
+ self.filter_box = QtWidgets.QComboBox(self.filter_frm2)
self.filter_box.setObjectName("filter_box")
self.filter_box.addItem("")
self.filter_box.addItem("")
self.filter_box.addItem("")
self.filter_box.addItem("")
self.horizontalLayout.addWidget(self.filter_box)
- self.filtered_lbl = QtGui.QLabel(self.filter_frm2)
+ self.filtered_lbl = QtWidgets.QLabel(self.filter_frm2)
self.filtered_lbl.setText("")
self.filtered_lbl.setObjectName("filtered_lbl")
self.horizontalLayout.addWidget(self.filtered_lbl)
- spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
- self.clear_filter_btn = QtGui.QPushButton(self.filter_frm2)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+ self.clear_filter_btn = QtWidgets.QPushButton(self.filter_frm2)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.clear_filter_btn.sizePolicy().hasHeightForWidth())
@@ -83,17 +84,17 @@ def setupUi(self, Filter):
QtCore.QMetaObject.connectSlotsByName(Filter)
def retranslateUi(self, Filter):
- self.filter_txt.setToolTip(QtGui.QApplication.translate("Filter", "Type the keywords to filter the visible items", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_txt.setPlaceholderText(QtGui.QApplication.translate("Filter", "Type here to filter...", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_btn.setToolTip(QtGui.QApplication.translate("Filter", "Set filter", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_btn.setText(QtGui.QApplication.translate("Filter", "Filter", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_box.setToolTip(QtGui.QApplication.translate("Filter", "Select where to search for the keywords", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_box.setItemText(0, QtGui.QApplication.translate("Filter", "Filter All:", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_box.setItemText(1, QtGui.QApplication.translate("Filter", "Filter Highlights:", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_box.setItemText(2, QtGui.QApplication.translate("Filter", "Filter Comments:", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_box.setItemText(3, QtGui.QApplication.translate("Filter", "Filter Book Titles:", None, QtGui.QApplication.UnicodeUTF8))
- self.clear_filter_btn.setToolTip(QtGui.QApplication.translate("Filter", "Clear the filter field", None, QtGui.QApplication.UnicodeUTF8))
- self.clear_filter_btn.setStatusTip(QtGui.QApplication.translate("Filter", "Clears the filter field", None, QtGui.QApplication.UnicodeUTF8))
- self.clear_filter_btn.setText(QtGui.QApplication.translate("Filter", "Clear", None, QtGui.QApplication.UnicodeUTF8))
+ self.filter_txt.setToolTip(QtWidgets.QApplication.translate("Filter", "Type the keywords to filter the visible items", None, -1))
+ self.filter_txt.setPlaceholderText(QtWidgets.QApplication.translate("Filter", "Type here to filter...", None, -1))
+ self.filter_btn.setToolTip(QtWidgets.QApplication.translate("Filter", "Set filter", None, -1))
+ self.filter_btn.setText(QtWidgets.QApplication.translate("Filter", "Filter", None, -1))
+ self.filter_box.setToolTip(QtWidgets.QApplication.translate("Filter", "Select where to search for the keywords", None, -1))
+ self.filter_box.setItemText(0, QtWidgets.QApplication.translate("Filter", "Filter All:", None, -1))
+ self.filter_box.setItemText(1, QtWidgets.QApplication.translate("Filter", "Filter Highlights:", None, -1))
+ self.filter_box.setItemText(2, QtWidgets.QApplication.translate("Filter", "Filter Comments:", None, -1))
+ self.filter_box.setItemText(3, QtWidgets.QApplication.translate("Filter", "Filter Book Titles:", None, -1))
+ self.clear_filter_btn.setToolTip(QtWidgets.QApplication.translate("Filter", "Clear the filter field", None, -1))
+ self.clear_filter_btn.setStatusTip(QtWidgets.QApplication.translate("Filter", "Clears the filter field", None, -1))
+ self.clear_filter_btn.setText(QtWidgets.QApplication.translate("Filter", "Clear", None, -1))
import images_rc
diff --git a/gui_main.py b/gui_main.py
index 1a5b1bd..ea6243f 100644
--- a/gui_main.py
+++ b/gui_main.py
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_main.ui'
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_main.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_main.ui' applies.
#
-# Created: Fri Jan 12 21:47:31 2024
-# by: pyside-uic 0.2.15 running on PySide 1.2.4
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
-from PySide import QtCore, QtGui
+from PySide2 import QtCore, QtGui, QtWidgets
class Ui_Base(object):
def setupUi(self, Base):
@@ -18,50 +19,50 @@ def setupUi(self, Base):
Base.setWindowIcon(icon)
Base.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
Base.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
- self.centralwidget = QtGui.QWidget(Base)
+ self.centralwidget = QtWidgets.QWidget(Base)
self.centralwidget.setObjectName("centralwidget")
- self.verticalLayout_2 = QtGui.QVBoxLayout(self.centralwidget)
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.views = QtGui.QStackedWidget(self.centralwidget)
+ self.views = QtWidgets.QStackedWidget(self.centralwidget)
self.views.setObjectName("views")
- self.books_pg = QtGui.QWidget()
+ self.books_pg = QtWidgets.QWidget()
self.books_pg.setObjectName("books_pg")
- self.verticalLayout_3 = QtGui.QVBoxLayout(self.books_pg)
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.books_pg)
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_3.setObjectName("verticalLayout_3")
- self.splitter = QtGui.QSplitter(self.books_pg)
+ self.splitter = QtWidgets.QSplitter(self.books_pg)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.file_table = DropTableWidget(self.splitter)
self.file_table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- self.file_table.setFrameShape(QtGui.QFrame.WinPanel)
- self.file_table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
- self.file_table.setDragDropMode(QtGui.QAbstractItemView.DropOnly)
+ self.file_table.setFrameShape(QtWidgets.QFrame.WinPanel)
+ self.file_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
+ self.file_table.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly)
self.file_table.setDefaultDropAction(QtCore.Qt.CopyAction)
- self.file_table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
- self.file_table.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
+ self.file_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+ self.file_table.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.file_table.setWordWrap(False)
self.file_table.setCornerButtonEnabled(False)
self.file_table.setColumnCount(8)
self.file_table.setObjectName("file_table")
self.file_table.setColumnCount(8)
self.file_table.setRowCount(0)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.file_table.setHorizontalHeaderItem(0, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.file_table.setHorizontalHeaderItem(1, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.file_table.setHorizontalHeaderItem(2, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.file_table.setHorizontalHeaderItem(3, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.file_table.setHorizontalHeaderItem(4, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.file_table.setHorizontalHeaderItem(5, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.file_table.setHorizontalHeaderItem(6, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.file_table.setHorizontalHeaderItem(7, item)
self.file_table.horizontalHeader().setDefaultSectionSize(22)
self.file_table.horizontalHeader().setHighlightSections(False)
@@ -70,151 +71,151 @@ def setupUi(self, Base):
self.file_table.verticalHeader().setDefaultSectionSize(22)
self.file_table.verticalHeader().setHighlightSections(True)
self.file_table.verticalHeader().setMinimumSectionSize(22)
- self.frame = QtGui.QFrame(self.splitter)
- self.frame.setFrameShape(QtGui.QFrame.WinPanel)
- self.frame.setFrameShadow(QtGui.QFrame.Sunken)
+ self.frame = QtWidgets.QFrame(self.splitter)
+ self.frame.setFrameShape(QtWidgets.QFrame.WinPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Sunken)
self.frame.setObjectName("frame")
- self.verticalLayout = QtGui.QVBoxLayout(self.frame)
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.frame)
+ self.verticalLayout.setSpacing(3)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
- self.header = QtGui.QWidget(self.frame)
+ self.header = QtWidgets.QWidget(self.frame)
self.header.setObjectName("header")
- self.horizontalLayout = QtGui.QHBoxLayout(self.header)
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.header)
self.horizontalLayout.setContentsMargins(0, 0, -1, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
- self.fold_btn = QtGui.QToolButton(self.header)
+ self.fold_btn = QtWidgets.QToolButton(self.header)
self.fold_btn.setStyleSheet("QToolButton{border:none;}")
self.fold_btn.setCheckable(True)
self.fold_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.fold_btn.setArrowType(QtCore.Qt.DownArrow)
self.fold_btn.setObjectName("fold_btn")
self.horizontalLayout.addWidget(self.fold_btn)
- self.frame_2 = QtGui.QFrame(self.header)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
+ self.frame_2 = QtWidgets.QFrame(self.header)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth())
self.frame_2.setSizePolicy(sizePolicy)
- self.frame_2.setFrameShape(QtGui.QFrame.HLine)
- self.frame_2.setFrameShadow(QtGui.QFrame.Sunken)
+ self.frame_2.setFrameShape(QtWidgets.QFrame.HLine)
+ self.frame_2.setFrameShadow(QtWidgets.QFrame.Sunken)
self.frame_2.setLineWidth(1)
self.frame_2.setObjectName("frame_2")
self.horizontalLayout.addWidget(self.frame_2)
self.verticalLayout.addWidget(self.header)
- self.book_info = QtGui.QFrame(self.frame)
- self.book_info.setFrameShape(QtGui.QFrame.StyledPanel)
- self.book_info.setFrameShadow(QtGui.QFrame.Raised)
+ self.book_info = QtWidgets.QFrame(self.frame)
+ self.book_info.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.book_info.setFrameShadow(QtWidgets.QFrame.Raised)
self.book_info.setObjectName("book_info")
- self.gridLayout = QtGui.QGridLayout(self.book_info)
+ self.gridLayout = QtWidgets.QGridLayout(self.book_info)
self.gridLayout.setContentsMargins(6, 0, 6, 0)
self.gridLayout.setObjectName("gridLayout")
- self.title_lbl = QtGui.QLabel(self.book_info)
+ self.title_lbl = QtWidgets.QLabel(self.book_info)
self.title_lbl.setObjectName("title_lbl")
self.gridLayout.addWidget(self.title_lbl, 0, 0, 1, 1)
- self.series_lbl = QtGui.QLabel(self.book_info)
+ self.series_lbl = QtWidgets.QLabel(self.book_info)
self.series_lbl.setObjectName("series_lbl")
self.gridLayout.addWidget(self.series_lbl, 2, 0, 1, 1)
- self.author_lbl = QtGui.QLabel(self.book_info)
+ self.author_lbl = QtWidgets.QLabel(self.book_info)
self.author_lbl.setObjectName("author_lbl")
self.gridLayout.addWidget(self.author_lbl, 1, 0, 1, 1)
- self.lang_lbl = QtGui.QLabel(self.book_info)
+ self.lang_lbl = QtWidgets.QLabel(self.book_info)
self.lang_lbl.setObjectName("lang_lbl")
self.gridLayout.addWidget(self.lang_lbl, 4, 0, 1, 1)
- self.pages_lbl = QtGui.QLabel(self.book_info)
+ self.pages_lbl = QtWidgets.QLabel(self.book_info)
self.pages_lbl.setObjectName("pages_lbl")
self.gridLayout.addWidget(self.pages_lbl, 4, 2, 1, 1)
- self.lang_txt = QtGui.QLineEdit(self.book_info)
+ self.lang_txt = QtWidgets.QLineEdit(self.book_info)
self.lang_txt.setReadOnly(True)
self.lang_txt.setObjectName("lang_txt")
self.gridLayout.addWidget(self.lang_txt, 4, 1, 1, 1)
- self.pages_txt = QtGui.QLineEdit(self.book_info)
+ self.pages_txt = QtWidgets.QLineEdit(self.book_info)
self.pages_txt.setReadOnly(True)
self.pages_txt.setObjectName("pages_txt")
self.gridLayout.addWidget(self.pages_txt, 4, 3, 1, 1)
- self.tags_lbl = QtGui.QLabel(self.book_info)
+ self.tags_lbl = QtWidgets.QLabel(self.book_info)
self.tags_lbl.setObjectName("tags_lbl")
self.gridLayout.addWidget(self.tags_lbl, 3, 0, 1, 1)
- self.description_btn = QtGui.QToolButton(self.book_info)
+ self.description_btn = QtWidgets.QToolButton(self.book_info)
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/stuff/description.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.description_btn.setIcon(icon1)
self.description_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.description_btn.setObjectName("description_btn")
self.gridLayout.addWidget(self.description_btn, 4, 4, 1, 1)
- self.tags_txt = QtGui.QLineEdit(self.book_info)
+ self.tags_txt = QtWidgets.QLineEdit(self.book_info)
self.tags_txt.setReadOnly(True)
self.tags_txt.setObjectName("tags_txt")
self.gridLayout.addWidget(self.tags_txt, 3, 1, 1, 4)
- self.series_txt = QtGui.QLineEdit(self.book_info)
+ self.series_txt = QtWidgets.QLineEdit(self.book_info)
self.series_txt.setReadOnly(True)
self.series_txt.setObjectName("series_txt")
self.gridLayout.addWidget(self.series_txt, 2, 1, 1, 4)
- self.author_txt = QtGui.QLineEdit(self.book_info)
+ self.author_txt = QtWidgets.QLineEdit(self.book_info)
self.author_txt.setReadOnly(True)
self.author_txt.setObjectName("author_txt")
self.gridLayout.addWidget(self.author_txt, 1, 1, 1, 4)
- self.title_txt = QtGui.QLineEdit(self.book_info)
+ self.title_txt = QtWidgets.QLineEdit(self.book_info)
self.title_txt.setReadOnly(True)
self.title_txt.setObjectName("title_txt")
self.gridLayout.addWidget(self.title_txt, 0, 1, 1, 4)
- self.review_lbl = QtGui.QLabel(self.book_info)
+ self.review_lbl = QtWidgets.QLabel(self.book_info)
self.review_lbl.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.review_lbl.setObjectName("review_lbl")
self.gridLayout.addWidget(self.review_lbl, 5, 0, 1, 1)
- self.review_txt = QtGui.QLabel(self.book_info)
- self.review_txt.setStyleSheet("background-color: rgb(255, 255, 255);")
- self.review_txt.setFrameShape(QtGui.QFrame.NoFrame)
+ self.review_txt = QtWidgets.QLabel(self.book_info)
+ self.review_txt.setFrameShape(QtWidgets.QFrame.NoFrame)
self.review_txt.setText("")
self.review_txt.setWordWrap(True)
self.review_txt.setObjectName("review_txt")
self.gridLayout.addWidget(self.review_txt, 5, 1, 1, 4)
self.verticalLayout.addWidget(self.book_info)
- self.high_list = QtGui.QListWidget(self.frame)
+ self.high_list = QtWidgets.QListWidget(self.frame)
self.high_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- self.high_list.setFrameShape(QtGui.QFrame.WinPanel)
- self.high_list.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
- self.high_list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
- self.high_list.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
- self.high_list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
+ self.high_list.setFrameShape(QtWidgets.QFrame.WinPanel)
+ self.high_list.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
+ self.high_list.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+ self.high_list.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+ self.high_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.high_list.setWordWrap(True)
self.high_list.setObjectName("high_list")
self.verticalLayout.addWidget(self.high_list)
self.verticalLayout_3.addWidget(self.splitter)
self.views.addWidget(self.books_pg)
- self.highlights_pg = QtGui.QWidget()
+ self.highlights_pg = QtWidgets.QWidget()
self.highlights_pg.setObjectName("highlights_pg")
- self.verticalLayout_4 = QtGui.QVBoxLayout(self.highlights_pg)
+ self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.highlights_pg)
self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_4.setObjectName("verticalLayout_4")
- self.high_table = QtGui.QTableWidget(self.highlights_pg)
+ self.high_table = QtWidgets.QTableWidget(self.highlights_pg)
self.high_table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- self.high_table.setFrameShape(QtGui.QFrame.WinPanel)
- self.high_table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
- self.high_table.setDragDropMode(QtGui.QAbstractItemView.DropOnly)
+ self.high_table.setFrameShape(QtWidgets.QFrame.WinPanel)
+ self.high_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
+ self.high_table.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly)
self.high_table.setDefaultDropAction(QtCore.Qt.CopyAction)
- self.high_table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
- self.high_table.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
+ self.high_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+ self.high_table.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.high_table.setWordWrap(False)
self.high_table.setCornerButtonEnabled(False)
self.high_table.setColumnCount(8)
self.high_table.setObjectName("high_table")
self.high_table.setColumnCount(8)
self.high_table.setRowCount(0)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.high_table.setHorizontalHeaderItem(0, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.high_table.setHorizontalHeaderItem(1, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.high_table.setHorizontalHeaderItem(2, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.high_table.setHorizontalHeaderItem(3, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.high_table.setHorizontalHeaderItem(4, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.high_table.setHorizontalHeaderItem(5, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.high_table.setHorizontalHeaderItem(6, item)
- item = QtGui.QTableWidgetItem()
+ item = QtWidgets.QTableWidgetItem()
self.high_table.setHorizontalHeaderItem(7, item)
self.high_table.horizontalHeader().setHighlightSections(False)
self.high_table.horizontalHeader().setMinimumSectionSize(22)
@@ -225,13 +226,35 @@ def setupUi(self, Base):
self.high_table.verticalHeader().setMinimumSectionSize(22)
self.verticalLayout_4.addWidget(self.high_table)
self.views.addWidget(self.highlights_pg)
+ self.sync_pg = QtWidgets.QWidget()
+ self.sync_pg.setObjectName("sync_pg")
+ self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.sync_pg)
+ self.verticalLayout_5.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_5.setObjectName("verticalLayout_5")
+ self.sync_table = XTableWidget(self.sync_pg)
+ self.sync_table.setAcceptDrops(True)
+ self.sync_table.setDragEnabled(True)
+ self.sync_table.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
+ self.sync_table.setDefaultDropAction(QtCore.Qt.MoveAction)
+ self.sync_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+ self.sync_table.setObjectName("sync_table")
+ self.sync_table.setColumnCount(1)
+ self.sync_table.setRowCount(0)
+ item = QtWidgets.QTableWidgetItem()
+ self.sync_table.setHorizontalHeaderItem(0, item)
+ self.sync_table.horizontalHeader().setVisible(False)
+ self.sync_table.horizontalHeader().setStretchLastSection(True)
+ self.sync_table.verticalHeader().setDefaultSectionSize(90)
+ self.sync_table.verticalHeader().setMinimumSectionSize(90)
+ self.verticalLayout_5.addWidget(self.sync_table)
+ self.views.addWidget(self.sync_pg)
self.verticalLayout_2.addWidget(self.views)
Base.setCentralWidget(self.centralwidget)
- self.statusbar = QtGui.QStatusBar(Base)
+ self.statusbar = QtWidgets.QStatusBar(Base)
self.statusbar.setStyleSheet("QStatusBar{padding-left:8px;font-weight:bold;}")
self.statusbar.setObjectName("statusbar")
Base.setStatusBar(self.statusbar)
- self.tool_bar = QtGui.QToolBar(Base)
+ self.tool_bar = QtWidgets.QToolBar(Base)
self.tool_bar.setWindowTitle("toolBar")
self.tool_bar.setMovable(True)
self.tool_bar.setAllowedAreas(QtCore.Qt.BottomToolBarArea|QtCore.Qt.TopToolBarArea)
@@ -239,15 +262,15 @@ def setupUi(self, Base):
self.tool_bar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.tool_bar.setObjectName("tool_bar")
Base.addToolBar(QtCore.Qt.TopToolBarArea, self.tool_bar)
- self.act_english = QtGui.QAction(Base)
+ self.act_english = QtWidgets.QAction(Base)
self.act_english.setCheckable(True)
self.act_english.setChecked(False)
self.act_english.setObjectName("act_english")
- self.act_greek = QtGui.QAction(Base)
+ self.act_greek = QtWidgets.QAction(Base)
self.act_greek.setCheckable(True)
self.act_greek.setChecked(False)
self.act_greek.setObjectName("act_greek")
- self.act_view_book = QtGui.QAction(Base)
+ self.act_view_book = QtWidgets.QAction(Base)
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/stuff/files_view.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.act_view_book.setIcon(icon2)
@@ -259,36 +282,37 @@ def setupUi(self, Base):
def retranslateUi(self, Base):
self.file_table.setSortingEnabled(True)
- self.file_table.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("Base", "Title", None, QtGui.QApplication.UnicodeUTF8))
- self.file_table.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("Base", "Author", None, QtGui.QApplication.UnicodeUTF8))
- self.file_table.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("Base", "Type", None, QtGui.QApplication.UnicodeUTF8))
- self.file_table.horizontalHeaderItem(3).setText(QtGui.QApplication.translate("Base", "Percent", None, QtGui.QApplication.UnicodeUTF8))
- self.file_table.horizontalHeaderItem(4).setText(QtGui.QApplication.translate("Base", "Rating", None, QtGui.QApplication.UnicodeUTF8))
- self.file_table.horizontalHeaderItem(5).setText(QtGui.QApplication.translate("Base", "Highlights", None, QtGui.QApplication.UnicodeUTF8))
- self.file_table.horizontalHeaderItem(6).setText(QtGui.QApplication.translate("Base", "Modified", None, QtGui.QApplication.UnicodeUTF8))
- self.file_table.horizontalHeaderItem(7).setText(QtGui.QApplication.translate("Base", "Path", None, QtGui.QApplication.UnicodeUTF8))
- self.fold_btn.setText(QtGui.QApplication.translate("Base", "Hide Book Info", None, QtGui.QApplication.UnicodeUTF8))
- self.title_lbl.setText(QtGui.QApplication.translate("Base", "Title", None, QtGui.QApplication.UnicodeUTF8))
- self.series_lbl.setText(QtGui.QApplication.translate("Base", "Series", None, QtGui.QApplication.UnicodeUTF8))
- self.author_lbl.setText(QtGui.QApplication.translate("Base", "Author", None, QtGui.QApplication.UnicodeUTF8))
- self.lang_lbl.setText(QtGui.QApplication.translate("Base", "Language", None, QtGui.QApplication.UnicodeUTF8))
- self.pages_lbl.setText(QtGui.QApplication.translate("Base", "Pages", None, QtGui.QApplication.UnicodeUTF8))
- self.tags_lbl.setText(QtGui.QApplication.translate("Base", "Tags", None, QtGui.QApplication.UnicodeUTF8))
- self.description_btn.setText(QtGui.QApplication.translate("Base", "Description", None, QtGui.QApplication.UnicodeUTF8))
- self.review_lbl.setText(QtGui.QApplication.translate("Base", "Review", None, QtGui.QApplication.UnicodeUTF8))
+ self.file_table.horizontalHeaderItem(0).setText(QtWidgets.QApplication.translate("Base", "Title", None, -1))
+ self.file_table.horizontalHeaderItem(1).setText(QtWidgets.QApplication.translate("Base", "Author", None, -1))
+ self.file_table.horizontalHeaderItem(2).setText(QtWidgets.QApplication.translate("Base", "Type", None, -1))
+ self.file_table.horizontalHeaderItem(3).setText(QtWidgets.QApplication.translate("Base", "Percent", None, -1))
+ self.file_table.horizontalHeaderItem(4).setText(QtWidgets.QApplication.translate("Base", "Rating", None, -1))
+ self.file_table.horizontalHeaderItem(5).setText(QtWidgets.QApplication.translate("Base", "Highlights", None, -1))
+ self.file_table.horizontalHeaderItem(6).setText(QtWidgets.QApplication.translate("Base", "Modified", None, -1))
+ self.file_table.horizontalHeaderItem(7).setText(QtWidgets.QApplication.translate("Base", "Path", None, -1))
+ self.fold_btn.setText(QtWidgets.QApplication.translate("Base", "Hide Book Info", None, -1))
+ self.title_lbl.setText(QtWidgets.QApplication.translate("Base", "Title", None, -1))
+ self.series_lbl.setText(QtWidgets.QApplication.translate("Base", "Series", None, -1))
+ self.author_lbl.setText(QtWidgets.QApplication.translate("Base", "Author", None, -1))
+ self.lang_lbl.setText(QtWidgets.QApplication.translate("Base", "Language", None, -1))
+ self.pages_lbl.setText(QtWidgets.QApplication.translate("Base", "Pages", None, -1))
+ self.tags_lbl.setText(QtWidgets.QApplication.translate("Base", "Tags", None, -1))
+ self.description_btn.setText(QtWidgets.QApplication.translate("Base", "Description", None, -1))
+ self.review_lbl.setText(QtWidgets.QApplication.translate("Base", "Review", None, -1))
self.high_table.setSortingEnabled(True)
- self.high_table.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("Base", "Highlight", None, QtGui.QApplication.UnicodeUTF8))
- self.high_table.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("Base", "Comment", None, QtGui.QApplication.UnicodeUTF8))
- self.high_table.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("Base", "Date", None, QtGui.QApplication.UnicodeUTF8))
- self.high_table.horizontalHeaderItem(3).setText(QtGui.QApplication.translate("Base", "Title", None, QtGui.QApplication.UnicodeUTF8))
- self.high_table.horizontalHeaderItem(4).setText(QtGui.QApplication.translate("Base", "Author", None, QtGui.QApplication.UnicodeUTF8))
- self.high_table.horizontalHeaderItem(5).setText(QtGui.QApplication.translate("Base", "Page", None, QtGui.QApplication.UnicodeUTF8))
- self.high_table.horizontalHeaderItem(6).setText(QtGui.QApplication.translate("Base", "Chapter", None, QtGui.QApplication.UnicodeUTF8))
- self.high_table.horizontalHeaderItem(7).setText(QtGui.QApplication.translate("Base", "Book path", None, QtGui.QApplication.UnicodeUTF8))
- self.act_english.setText(QtGui.QApplication.translate("Base", "English", None, QtGui.QApplication.UnicodeUTF8))
- self.act_greek.setText(QtGui.QApplication.translate("Base", "Greek", None, QtGui.QApplication.UnicodeUTF8))
- self.act_view_book.setText(QtGui.QApplication.translate("Base", "View Book", None, QtGui.QApplication.UnicodeUTF8))
- self.act_view_book.setShortcut(QtGui.QApplication.translate("Base", "Ctrl+B", None, QtGui.QApplication.UnicodeUTF8))
+ self.high_table.horizontalHeaderItem(0).setText(QtWidgets.QApplication.translate("Base", "Highlight", None, -1))
+ self.high_table.horizontalHeaderItem(1).setText(QtWidgets.QApplication.translate("Base", "Comment", None, -1))
+ self.high_table.horizontalHeaderItem(2).setText(QtWidgets.QApplication.translate("Base", "Date", None, -1))
+ self.high_table.horizontalHeaderItem(3).setText(QtWidgets.QApplication.translate("Base", "Title", None, -1))
+ self.high_table.horizontalHeaderItem(4).setText(QtWidgets.QApplication.translate("Base", "Author", None, -1))
+ self.high_table.horizontalHeaderItem(5).setText(QtWidgets.QApplication.translate("Base", "Page", None, -1))
+ self.high_table.horizontalHeaderItem(6).setText(QtWidgets.QApplication.translate("Base", "Chapter", None, -1))
+ self.high_table.horizontalHeaderItem(7).setText(QtWidgets.QApplication.translate("Base", "Path", None, -1))
+ self.sync_table.horizontalHeaderItem(0).setText(QtWidgets.QApplication.translate("Base", "Sync Groups", None, -1))
+ self.act_english.setText(QtWidgets.QApplication.translate("Base", "English", None, -1))
+ self.act_greek.setText(QtWidgets.QApplication.translate("Base", "Greek", None, -1))
+ self.act_view_book.setText(QtWidgets.QApplication.translate("Base", "View Book", None, -1))
+ self.act_view_book.setShortcut(QtWidgets.QApplication.translate("Base", "Ctrl+B", None, -1))
-from secondary import DropTableWidget
+from secondary import DropTableWidget, XTableWidget
import images_rc
diff --git a/gui_main.ui b/gui_main.ui
index 518fd1f..ce815f4 100644
--- a/gui_main.ui
+++ b/gui_main.ui
@@ -144,6 +144,9 @@
QFrame::Sunken
+
+ 3
+
0
@@ -331,9 +334,6 @@
-
-
- background-color: rgb(255, 255, 255);
-
QFrame::NoFrame
@@ -477,7 +477,50 @@
- Book path
+ Path
+
+
+
+
+
+
+
+
+
+ 0
+
+ -
+
+
+ true
+
+
+ true
+
+
+ QAbstractItemView::InternalMove
+
+
+ Qt::MoveAction
+
+
+ QAbstractItemView::SelectRows
+
+
+ false
+
+
+ true
+
+
+ 90
+
+
+ 90
+
+
+
+ Sync Groups
@@ -560,6 +603,11 @@
QTableWidget
+
+ XTableWidget
+ QTableWidget
+
+
diff --git a/gui_status.py b/gui_status.py
index 772f75f..941ddbc 100644
--- a/gui_status.py
+++ b/gui_status.py
@@ -1,58 +1,67 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_status.ui'
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_status.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_status.ui' applies.
#
-# Created: Thu Mar 9 14:39:35 2023
-# by: pyside-uic 0.2.15 running on PySide 1.2.4
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
-from PySide import QtCore, QtGui
+from PySide2 import QtCore, QtGui, QtWidgets
class Ui_Status(object):
def setupUi(self, Status):
Status.setObjectName("Status")
- Status.resize(277, 55)
- Status.setWindowTitle("")
- self.horizontalLayout_2 = QtGui.QHBoxLayout(Status)
+ Status.resize(286, 32)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Status)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.frame = QtGui.QFrame(Status)
- self.frame.setFrameShape(QtGui.QFrame.StyledPanel)
- self.frame.setFrameShadow(QtGui.QFrame.Raised)
+ self.frame = QtWidgets.QFrame(Status)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
- self.horizontalLayout = QtGui.QHBoxLayout(self.frame)
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
- self.anim_lbl = QtGui.QLabel(self.frame)
+ self.anim_lbl = QtWidgets.QLabel(self.frame)
self.anim_lbl.setText("")
self.anim_lbl.setObjectName("anim_lbl")
self.horizontalLayout.addWidget(self.anim_lbl)
- self.show_items_btn = QtGui.QToolButton(self.frame)
+ self.theme_box = QtWidgets.QComboBox(self.frame)
+ self.theme_box.setObjectName("theme_box")
+ self.theme_box.addItem("")
+ self.theme_box.addItem("")
+ self.theme_box.addItem("")
+ self.theme_box.addItem("")
+ self.theme_box.addItem("")
+ self.theme_box.addItem("")
+ self.horizontalLayout.addWidget(self.theme_box)
+ self.show_items_btn = QtWidgets.QToolButton(self.frame)
self.show_items_btn.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/stuff/show_pages.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.show_items_btn.setIcon(icon)
self.show_items_btn.setIconSize(QtCore.QSize(24, 24))
self.show_items_btn.setChecked(False)
- self.show_items_btn.setPopupMode(QtGui.QToolButton.InstantPopup)
+ self.show_items_btn.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.show_items_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.show_items_btn.setObjectName("show_items_btn")
self.horizontalLayout.addWidget(self.show_items_btn)
self.horizontalLayout_2.addWidget(self.frame)
- self.act_page = QtGui.QAction(Status)
+ self.act_page = QtWidgets.QAction(Status)
self.act_page.setCheckable(True)
self.act_page.setObjectName("act_page")
- self.act_date = QtGui.QAction(Status)
+ self.act_date = QtWidgets.QAction(Status)
self.act_date.setCheckable(True)
self.act_date.setObjectName("act_date")
- self.act_text = QtGui.QAction(Status)
+ self.act_text = QtWidgets.QAction(Status)
self.act_text.setCheckable(True)
self.act_text.setObjectName("act_text")
- self.act_comment = QtGui.QAction(Status)
+ self.act_comment = QtWidgets.QAction(Status)
self.act_comment.setCheckable(True)
self.act_comment.setObjectName("act_comment")
- self.act_chapter = QtGui.QAction(Status)
+ self.act_chapter = QtWidgets.QAction(Status)
self.act_chapter.setCheckable(True)
self.act_chapter.setObjectName("act_chapter")
@@ -60,15 +69,21 @@ def setupUi(self, Status):
QtCore.QMetaObject.connectSlotsByName(Status)
def retranslateUi(self, Status):
- self.show_items_btn.setToolTip(QtGui.QApplication.translate("Status", "Show/Hide elements of Highlights. Also affects\n"
-"what will be saved to the text/html files.", None, QtGui.QApplication.UnicodeUTF8))
- self.show_items_btn.setStatusTip(QtGui.QApplication.translate("Status", "Show/Hide elements of Highlights. Also affects what will be saved to the text/html files.", None, QtGui.QApplication.UnicodeUTF8))
- self.show_items_btn.setText(QtGui.QApplication.translate("Status", "Show in Highlights", None, QtGui.QApplication.UnicodeUTF8))
- self.act_page.setText(QtGui.QApplication.translate("Status", "Page", None, QtGui.QApplication.UnicodeUTF8))
- self.act_date.setText(QtGui.QApplication.translate("Status", "Date", None, QtGui.QApplication.UnicodeUTF8))
- self.act_text.setText(QtGui.QApplication.translate("Status", "Highlight", None, QtGui.QApplication.UnicodeUTF8))
- self.act_comment.setText(QtGui.QApplication.translate("Status", "Comment", None, QtGui.QApplication.UnicodeUTF8))
- self.act_chapter.setText(QtGui.QApplication.translate("Status", "Chapter", None, QtGui.QApplication.UnicodeUTF8))
- self.act_chapter.setToolTip(QtGui.QApplication.translate("Status", "Chapter", None, QtGui.QApplication.UnicodeUTF8))
+ self.theme_box.setItemText(0, QtWidgets.QApplication.translate("Status", "No theme - Old icons", None, -1))
+ self.theme_box.setItemText(1, QtWidgets.QApplication.translate("Status", "No theme - New icons", None, -1))
+ self.theme_box.setItemText(2, QtWidgets.QApplication.translate("Status", "Dark theme - Old icons", None, -1))
+ self.theme_box.setItemText(3, QtWidgets.QApplication.translate("Status", "Dark theme - New icons", None, -1))
+ self.theme_box.setItemText(4, QtWidgets.QApplication.translate("Status", "Light theme - Old icons", None, -1))
+ self.theme_box.setItemText(5, QtWidgets.QApplication.translate("Status", "Light theme - New icons", None, -1))
+ self.show_items_btn.setToolTip(QtWidgets.QApplication.translate("Status", "Show/Hide elements of Highlights. Also affects\n"
+"what will be saved to the text/html files.", None, -1))
+ self.show_items_btn.setStatusTip(QtWidgets.QApplication.translate("Status", "Show/Hide elements of Highlights. Also affects what will be saved to the text/html files.", None, -1))
+ self.show_items_btn.setText(QtWidgets.QApplication.translate("Status", "Show in Highlights", None, -1))
+ self.act_page.setText(QtWidgets.QApplication.translate("Status", "Page", None, -1))
+ self.act_date.setText(QtWidgets.QApplication.translate("Status", "Date", None, -1))
+ self.act_text.setText(QtWidgets.QApplication.translate("Status", "Highlight", None, -1))
+ self.act_comment.setText(QtWidgets.QApplication.translate("Status", "Comment", None, -1))
+ self.act_chapter.setText(QtWidgets.QApplication.translate("Status", "Chapter", None, -1))
+ self.act_chapter.setToolTip(QtWidgets.QApplication.translate("Status", "Chapter", None, -1))
import images_rc
diff --git a/gui_status.ui b/gui_status.ui
index 268a885..b2e4ce5 100644
--- a/gui_status.ui
+++ b/gui_status.ui
@@ -6,13 +6,10 @@
0
0
- 277
- 55
+ 286
+ 32
-
-
-
0
@@ -36,6 +33,40 @@
+ -
+
+
-
+
+ No theme - Old icons
+
+
+ -
+
+ No theme - New icons
+
+
+ -
+
+ Dark theme - Old icons
+
+
+ -
+
+ Dark theme - New icons
+
+
+ -
+
+ Light theme - Old icons
+
+
+ -
+
+ Light theme - New icons
+
+
+
+
-
diff --git a/gui_sync_group.py b/gui_sync_group.py
new file mode 100644
index 0000000..ce76850
--- /dev/null
+++ b/gui_sync_group.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_sync_group.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_sync_group.ui' applies.
+#
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
+#
+# WARNING! All changes made in this file will be lost!
+
+from PySide2 import QtCore, QtGui, QtWidgets
+
+class Ui_SyncGroup(object):
+ def setupUi(self, SyncGroup):
+ SyncGroup.setObjectName("SyncGroup")
+ SyncGroup.resize(560, 65)
+ self.horizontalLayout_5 = QtWidgets.QHBoxLayout(SyncGroup)
+ self.horizontalLayout_5.setSpacing(0)
+ self.horizontalLayout_5.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_5.setObjectName("horizontalLayout_5")
+ self.frame = QtWidgets.QFrame(SyncGroup)
+ self.frame.setObjectName("frame")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.frame)
+ self.verticalLayout.setSpacing(0)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ spacerItem = QtWidgets.QSpacerItem(20, 30, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem)
+ self.power_btn = QtWidgets.QToolButton(self.frame)
+ self.power_btn.setText("")
+ icon = QtGui.QIcon()
+ icon.addPixmap(QtGui.QPixmap(":/stuff/power32gray.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ icon.addPixmap(QtGui.QPixmap(":/stuff/power32red.png"), QtGui.QIcon.Normal, QtGui.QIcon.On)
+ self.power_btn.setIcon(icon)
+ self.power_btn.setIconSize(QtCore.QSize(16, 16))
+ self.power_btn.setCheckable(True)
+ self.power_btn.setChecked(True)
+ self.power_btn.setAutoRaise(True)
+ self.power_btn.setObjectName("power_btn")
+ self.verticalLayout.addWidget(self.power_btn)
+ spacerItem1 = QtWidgets.QSpacerItem(20, 28, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem1)
+ self.horizontalLayout_5.addWidget(self.frame)
+ self.group_frm = QtWidgets.QFrame(SyncGroup)
+ self.group_frm.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.group_frm.setFrameShape(QtWidgets.QFrame.Box)
+ self.group_frm.setFrameShadow(QtWidgets.QFrame.Plain)
+ self.group_frm.setLineWidth(1)
+ self.group_frm.setObjectName("group_frm")
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.group_frm)
+ self.verticalLayout_3.setSpacing(0)
+ self.verticalLayout_3.setContentsMargins(4, 0, 2, 0)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.frame_2 = QtWidgets.QFrame(self.group_frm)
+ self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_2.setObjectName("frame_2")
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.frame_2)
+ self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ self.refresh_btn = QtWidgets.QToolButton(self.frame_2)
+ self.refresh_btn.setText("")
+ icon1 = QtGui.QIcon()
+ icon1.addPixmap(QtGui.QPixmap(":/stuff/refresh16.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.refresh_btn.setIcon(icon1)
+ self.refresh_btn.setIconSize(QtCore.QSize(16, 16))
+ self.refresh_btn.setChecked(False)
+ self.refresh_btn.setAutoRaise(True)
+ self.refresh_btn.setObjectName("refresh_btn")
+ self.horizontalLayout_4.addWidget(self.refresh_btn)
+ spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_4.addItem(spacerItem2)
+ self.title_lbl = QtWidgets.QLabel(self.frame_2)
+ self.title_lbl.setAutoFillBackground(True)
+ self.title_lbl.setText("")
+ self.title_lbl.setAlignment(QtCore.Qt.AlignCenter)
+ self.title_lbl.setObjectName("title_lbl")
+ self.horizontalLayout_4.addWidget(self.title_lbl)
+ spacerItem3 = QtWidgets.QSpacerItem(161, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_4.addItem(spacerItem3)
+ self.sync_btn = QtWidgets.QToolButton(self.frame_2)
+ icon2 = QtGui.QIcon()
+ icon2.addPixmap(QtGui.QPixmap(":/stuff/sync.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.sync_btn.setIcon(icon2)
+ self.sync_btn.setChecked(False)
+ self.sync_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
+ self.sync_btn.setAutoRaise(True)
+ self.sync_btn.setObjectName("sync_btn")
+ self.horizontalLayout_4.addWidget(self.sync_btn)
+ self.verticalLayout_3.addWidget(self.frame_2)
+ self.items_frm = QtWidgets.QFrame(self.group_frm)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.items_frm.sizePolicy().hasHeightForWidth())
+ self.items_frm.setSizePolicy(sizePolicy)
+ self.items_frm.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.items_frm.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.items_frm.setObjectName("items_frm")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.items_frm)
+ self.verticalLayout_2.setSpacing(2)
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.checks_frm = QtWidgets.QFrame(self.items_frm)
+ self.checks_frm.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.checks_frm.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.checks_frm.setObjectName("checks_frm")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.checks_frm)
+ self.horizontalLayout.setSpacing(4)
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.sync_pos_chk = QtWidgets.QCheckBox(self.checks_frm)
+ self.sync_pos_chk.setObjectName("sync_pos_chk")
+ self.horizontalLayout.addWidget(self.sync_pos_chk)
+ self.merge_chk = QtWidgets.QCheckBox(self.checks_frm)
+ self.merge_chk.setObjectName("merge_chk")
+ self.horizontalLayout.addWidget(self.merge_chk)
+ self.sync_db_chk = QtWidgets.QCheckBox(self.checks_frm)
+ self.sync_db_chk.setObjectName("sync_db_chk")
+ self.horizontalLayout.addWidget(self.sync_db_chk)
+ spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout.addItem(spacerItem4)
+ self.verticalLayout_2.addWidget(self.checks_frm)
+ self.verticalLayout_3.addWidget(self.items_frm)
+ self.horizontalLayout_5.addWidget(self.group_frm)
+
+ self.retranslateUi(SyncGroup)
+ QtCore.QObject.connect(self.power_btn, QtCore.SIGNAL("toggled(bool)"), self.group_frm.setEnabled)
+ QtCore.QMetaObject.connectSlotsByName(SyncGroup)
+
+ def retranslateUi(self, SyncGroup):
+ self.power_btn.setToolTip(QtWidgets.QApplication.translate("SyncGroup", "Enable or disable this Sync Group", None, -1))
+ self.power_btn.setStatusTip(QtWidgets.QApplication.translate("SyncGroup", "Enable or disable this Sync Group", None, -1))
+ self.refresh_btn.setToolTip(QtWidgets.QApplication.translate("SyncGroup", "Reload the Group and check the paths for errors", None, -1))
+ self.refresh_btn.setStatusTip(QtWidgets.QApplication.translate("SyncGroup", "Reload the Group and check the paths for errors", None, -1))
+ self.sync_btn.setToolTip(QtWidgets.QApplication.translate("SyncGroup", "Start the sync/merge process for this group", None, -1))
+ self.sync_btn.setStatusTip(QtWidgets.QApplication.translate("SyncGroup", "Start the sync/merge process for this group", None, -1))
+ self.sync_btn.setText(QtWidgets.QApplication.translate("SyncGroup", "Sync this group", None, -1))
+ self.sync_pos_chk.setToolTip(QtWidgets.QApplication.translate("SyncGroup", "Sync the current position and read percent of the books", None, -1))
+ self.sync_pos_chk.setStatusTip(QtWidgets.QApplication.translate("SyncGroup", "Sync the current position and read percent of the books", None, -1))
+ self.sync_pos_chk.setText(QtWidgets.QApplication.translate("SyncGroup", "Sync position", None, -1))
+ self.merge_chk.setToolTip(QtWidgets.QApplication.translate("SyncGroup", "Merge the highlights from both books\\n(and/or the archived version)", None, -1))
+ self.merge_chk.setStatusTip(QtWidgets.QApplication.translate("SyncGroup", "Merge the highlights from both books\\\\n(and/or the archived version)", None, -1))
+ self.merge_chk.setText(QtWidgets.QApplication.translate("SyncGroup", "Merge Highlights", None, -1))
+ self.sync_db_chk.setToolTip(QtWidgets.QApplication.translate("SyncGroup", "Keep a synced version of the book in the archived database", None, -1))
+ self.sync_db_chk.setStatusTip(QtWidgets.QApplication.translate("SyncGroup", "Keep a synced version of the book in the archived database", None, -1))
+ self.sync_db_chk.setText(QtWidgets.QApplication.translate("SyncGroup", "Sync with archived", None, -1))
+
+import images_rc
diff --git a/gui_sync_group.ui b/gui_sync_group.ui
new file mode 100644
index 0000000..46b1462
--- /dev/null
+++ b/gui_sync_group.ui
@@ -0,0 +1,350 @@
+
+
+ SyncGroup
+
+
+
+ 0
+ 0
+ 560
+ 65
+
+
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 30
+
+
+
+
+ -
+
+
+ Enable or disable this Sync Group
+
+
+ Enable or disable this Sync Group
+
+
+
+
+
+
+ :/stuff/power32gray.png
+ :/stuff/power32red.png:/stuff/power32gray.png
+
+
+
+ 16
+ 16
+
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 28
+
+
+
+
+
+
+
+ -
+
+
+ Qt::CustomContextMenu
+
+
+ QFrame::Box
+
+
+ QFrame::Plain
+
+
+ 1
+
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
-
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
-
+
+
+ Reload the Group and check the paths for errors
+
+
+ Reload the Group and check the paths for errors
+
+
+
+
+
+
+ :/stuff/refresh16.png:/stuff/refresh16.png
+
+
+
+ 16
+ 16
+
+
+
+ false
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ true
+
+
+
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 161
+ 20
+
+
+
+
+ -
+
+
+ Start the sync/merge process for this group
+
+
+ Start the sync/merge process for this group
+
+
+ Sync this group
+
+
+
+ :/stuff/sync.png:/stuff/sync.png
+
+
+ false
+
+
+ Qt::ToolButtonTextBesideIcon
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 2
+
+
+ 0
+
+
-
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 4
+
+
+ 0
+
+
-
+
+
+ Sync the current position and read percent of the books
+
+
+ Sync the current position and read percent of the books
+
+
+ Sync position
+
+
+
+ -
+
+
+ Merge the highlights from both books\n(and/or the archived version)
+
+
+ Merge the highlights from both books\\n(and/or the archived version)
+
+
+ Merge Highlights
+
+
+
+ -
+
+
+ Keep a synced version of the book in the archived database
+
+
+ Keep a synced version of the book in the archived database
+
+
+ Sync with archived
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ power_btn
+ toggled(bool)
+ group_frm
+ setEnabled(bool)
+
+
+ 22
+ 21
+
+
+ 81
+ 83
+
+
+
+
+
diff --git a/gui_sync_item.py b/gui_sync_item.py
new file mode 100644
index 0000000..4ebaa1c
--- /dev/null
+++ b/gui_sync_item.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_sync_item.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_sync_item.ui' applies.
+#
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
+#
+# WARNING! All changes made in this file will be lost!
+
+from PySide2 import QtCore, QtGui, QtWidgets
+
+class Ui_SyncItem(object):
+ def setupUi(self, SyncItem):
+ SyncItem.setObjectName("SyncItem")
+ SyncItem.resize(446, 25)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout(SyncItem)
+ self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.label = QtWidgets.QLabel(SyncItem)
+ self.label.setObjectName("label")
+ self.horizontalLayout_2.addWidget(self.label)
+ self.sync_path_txt = QtWidgets.QLineEdit(SyncItem)
+ self.sync_path_txt.setReadOnly(True)
+ self.sync_path_txt.setObjectName("sync_path_txt")
+ self.horizontalLayout_2.addWidget(self.sync_path_txt)
+ self.sync_path_btn = QtWidgets.QPushButton(SyncItem)
+ self.sync_path_btn.setObjectName("sync_path_btn")
+ self.horizontalLayout_2.addWidget(self.sync_path_btn)
+ self.frame = QtWidgets.QFrame(SyncItem)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame)
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.add_btn = QtWidgets.QToolButton(self.frame)
+ self.add_btn.setText("")
+ icon = QtGui.QIcon()
+ icon.addPixmap(QtGui.QPixmap(":/stuff/add.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.add_btn.setIcon(icon)
+ self.add_btn.setObjectName("add_btn")
+ self.horizontalLayout.addWidget(self.add_btn)
+ self.del_btn = QtWidgets.QToolButton(self.frame)
+ self.del_btn.setText("")
+ icon1 = QtGui.QIcon()
+ icon1.addPixmap(QtGui.QPixmap(":/stuff/del.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.del_btn.setIcon(icon1)
+ self.del_btn.setObjectName("del_btn")
+ self.horizontalLayout.addWidget(self.del_btn)
+ self.horizontalLayout_2.addWidget(self.frame)
+
+ self.retranslateUi(SyncItem)
+ QtCore.QMetaObject.connectSlotsByName(SyncItem)
+
+ def retranslateUi(self, SyncItem):
+ self.label.setText(QtWidgets.QApplication.translate("SyncItem", "Sync path", None, -1))
+ self.sync_path_txt.setToolTip(QtWidgets.QApplication.translate("SyncItem", "The path to the book\'s metadata file", None, -1))
+ self.sync_path_txt.setStatusTip(QtWidgets.QApplication.translate("SyncItem", "The path to the book\'s metadata file", None, -1))
+ self.sync_path_txt.setPlaceholderText(QtWidgets.QApplication.translate("SyncItem", "Select metadata file to sync", None, -1))
+ self.sync_path_btn.setToolTip(QtWidgets.QApplication.translate("SyncItem", "Select/Change the metadata file path", None, -1))
+ self.sync_path_btn.setStatusTip(QtWidgets.QApplication.translate("SyncItem", "Select/Change the metadata file path", None, -1))
+ self.sync_path_btn.setText(QtWidgets.QApplication.translate("SyncItem", "Select", None, -1))
+ self.add_btn.setToolTip(QtWidgets.QApplication.translate("SyncItem", "Add a new Sync path", None, -1))
+ self.add_btn.setStatusTip(QtWidgets.QApplication.translate("SyncItem", "Add a new Sync path", None, -1))
+ self.del_btn.setToolTip(QtWidgets.QApplication.translate("SyncItem", "Remove this Sync path", None, -1))
+ self.del_btn.setStatusTip(QtWidgets.QApplication.translate("SyncItem", "Remove this Sync path", None, -1))
+
+import images_rc
diff --git a/gui_sync_item.ui b/gui_sync_item.ui
new file mode 100644
index 0000000..9a8aa7b
--- /dev/null
+++ b/gui_sync_item.ui
@@ -0,0 +1,108 @@
+
+
+ SyncItem
+
+
+
+ 0
+ 0
+ 446
+ 25
+
+
+
+
+ 0
+
+ -
+
+
+ Sync path
+
+
+
+ -
+
+
+ The path to the book's metadata file
+
+
+ The path to the book's metadata file
+
+
+ true
+
+
+ Select metadata file to sync
+
+
+
+ -
+
+
+ Select/Change the metadata file path
+
+
+ Select/Change the metadata file path
+
+
+ Select
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
-
+
+
+ Add a new Sync path
+
+
+ Add a new Sync path
+
+
+
+
+
+
+ :/stuff/add.png:/stuff/add.png
+
+
+
+ -
+
+
+ Remove this Sync path
+
+
+ Remove this Sync path
+
+
+
+
+
+
+ :/stuff/del.png:/stuff/del.png
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui_toolbar.py b/gui_toolbar.py
index 402892d..36e190e 100644
--- a/gui_toolbar.py
+++ b/gui_toolbar.py
@@ -1,89 +1,95 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_toolbar.ui'
+# Form implementation generated from reading ui file 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_toolbar.ui',
+# licensing of 'D:\Apps\DEV\PROJECTS\KoHighlights\gui_toolbar.ui' applies.
#
-# Created: Thu Mar 9 14:39:30 2023
-# by: pyside-uic 0.2.15 running on PySide 1.2.4
+# Created: Thu May 2 17:29:33 2024
+# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
-from PySide import QtCore, QtGui
+from PySide2 import QtCore, QtGui, QtWidgets
class Ui_ToolBar(object):
def setupUi(self, ToolBar):
ToolBar.setObjectName("ToolBar")
- ToolBar.resize(967, 73)
+ ToolBar.resize(1035, 91)
ToolBar.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
ToolBar.setWindowTitle("")
ToolBar.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
- self.verticalLayout_2 = QtGui.QVBoxLayout(ToolBar)
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(ToolBar)
self.verticalLayout_2.setSpacing(0)
self.verticalLayout_2.setContentsMargins(0, 0, 2, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.tool_frame = QtGui.QFrame(ToolBar)
+ self.tool_frame = QtWidgets.QFrame(ToolBar)
self.tool_frame.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tool_frame.setObjectName("tool_frame")
- self.horizontalLayout = QtGui.QHBoxLayout(self.tool_frame)
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.tool_frame)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
- self.check_btn = QtGui.QToolButton(self.tool_frame)
- self.check_btn.setMinimumSize(QtCore.QSize(80, 0))
- self.check_btn.setText("")
- icon = QtGui.QIcon()
- icon.addPixmap(QtGui.QPixmap("../KataLib/:/stuff/exec.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.check_btn.setIcon(icon)
- self.check_btn.setIconSize(QtCore.QSize(48, 48))
- self.check_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
- self.check_btn.setAutoRaise(True)
- self.check_btn.setObjectName("check_btn")
- self.horizontalLayout.addWidget(self.check_btn)
- self.scan_btn = QtGui.QToolButton(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.scan_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.scan_btn.sizePolicy().hasHeightForWidth())
self.scan_btn.setSizePolicy(sizePolicy)
self.scan_btn.setMinimumSize(QtCore.QSize(80, 0))
- icon1 = QtGui.QIcon()
- icon1.addPixmap(QtGui.QPixmap(":/stuff/folder_reader.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.scan_btn.setIcon(icon1)
+ icon = QtGui.QIcon()
+ icon.addPixmap(QtGui.QPixmap(":/stuff/folder_reader.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.scan_btn.setIcon(icon)
self.scan_btn.setIconSize(QtCore.QSize(48, 48))
self.scan_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.scan_btn.setAutoRaise(True)
self.scan_btn.setObjectName("scan_btn")
self.horizontalLayout.addWidget(self.scan_btn)
- self.export_btn = QtGui.QToolButton(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.export_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.export_btn.sizePolicy().hasHeightForWidth())
self.export_btn.setSizePolicy(sizePolicy)
self.export_btn.setMinimumSize(QtCore.QSize(80, 0))
- icon2 = QtGui.QIcon()
- icon2.addPixmap(QtGui.QPixmap(":/stuff/file_save.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.export_btn.setIcon(icon2)
+ icon1 = QtGui.QIcon()
+ icon1.addPixmap(QtGui.QPixmap(":/stuff/file_save.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.export_btn.setIcon(icon1)
self.export_btn.setIconSize(QtCore.QSize(48, 48))
+ self.export_btn.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
self.export_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.export_btn.setAutoRaise(True)
self.export_btn.setObjectName("export_btn")
self.horizontalLayout.addWidget(self.export_btn)
- self.open_btn = QtGui.QToolButton(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.open_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.open_btn.sizePolicy().hasHeightForWidth())
self.open_btn.setSizePolicy(sizePolicy)
self.open_btn.setMinimumSize(QtCore.QSize(80, 0))
- icon3 = QtGui.QIcon()
- icon3.addPixmap(QtGui.QPixmap(":/stuff/files_view.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.open_btn.setIcon(icon3)
+ icon2 = QtGui.QIcon()
+ icon2.addPixmap(QtGui.QPixmap(":/stuff/files_view.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.open_btn.setIcon(icon2)
self.open_btn.setIconSize(QtCore.QSize(48, 48))
self.open_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.open_btn.setAutoRaise(True)
self.open_btn.setObjectName("open_btn")
self.horizontalLayout.addWidget(self.open_btn)
- self.filter_btn = QtGui.QToolButton(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.add_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.add_btn.sizePolicy().hasHeightForWidth())
+ self.add_btn.setSizePolicy(sizePolicy)
+ icon3 = QtGui.QIcon()
+ icon3.addPixmap(QtGui.QPixmap(":/stuff/files_add.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.add_btn.setIcon(icon3)
+ self.add_btn.setIconSize(QtCore.QSize(48, 48))
+ self.add_btn.setChecked(False)
+ self.add_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
+ self.add_btn.setAutoRaise(True)
+ self.add_btn.setObjectName("add_btn")
+ self.horizontalLayout.addWidget(self.add_btn)
+ self.filter_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.filter_btn.sizePolicy().hasHeightForWidth())
@@ -98,8 +104,8 @@ def setupUi(self, ToolBar):
self.filter_btn.setAutoRaise(True)
self.filter_btn.setObjectName("filter_btn")
self.horizontalLayout.addWidget(self.filter_btn)
- self.merge_btn = QtGui.QToolButton(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.merge_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.merge_btn.sizePolicy().hasHeightForWidth())
@@ -109,13 +115,13 @@ def setupUi(self, ToolBar):
icon5.addPixmap(QtGui.QPixmap(":/stuff/files_merge.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.merge_btn.setIcon(icon5)
self.merge_btn.setIconSize(QtCore.QSize(48, 48))
- self.merge_btn.setPopupMode(QtGui.QToolButton.MenuButtonPopup)
+ self.merge_btn.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
self.merge_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.merge_btn.setAutoRaise(True)
self.merge_btn.setObjectName("merge_btn")
self.horizontalLayout.addWidget(self.merge_btn)
- self.delete_btn = QtGui.QToolButton(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.delete_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.delete_btn.sizePolicy().hasHeightForWidth())
@@ -125,13 +131,13 @@ def setupUi(self, ToolBar):
icon6.addPixmap(QtGui.QPixmap(":/stuff/files_delete.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.delete_btn.setIcon(icon6)
self.delete_btn.setIconSize(QtCore.QSize(48, 48))
- self.delete_btn.setPopupMode(QtGui.QToolButton.MenuButtonPopup)
+ self.delete_btn.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
self.delete_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.delete_btn.setAutoRaise(True)
self.delete_btn.setObjectName("delete_btn")
self.horizontalLayout.addWidget(self.delete_btn)
- self.clear_btn = QtGui.QToolButton(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.clear_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.clear_btn.sizePolicy().hasHeightForWidth())
@@ -145,15 +151,15 @@ def setupUi(self, ToolBar):
self.clear_btn.setAutoRaise(True)
self.clear_btn.setObjectName("clear_btn")
self.horizontalLayout.addWidget(self.clear_btn)
- spacerItem = QtGui.QSpacerItem(86, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ spacerItem = QtWidgets.QSpacerItem(86, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
- self.view_grp = QtGui.QGroupBox(self.tool_frame)
+ self.view_grp = QtWidgets.QGroupBox(self.tool_frame)
self.view_grp.setObjectName("view_grp")
- self.horizontalLayout_3 = QtGui.QHBoxLayout(self.view_grp)
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.view_grp)
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
- self.books_view_btn = QtGui.QToolButton(self.view_grp)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
+ self.books_view_btn = QtWidgets.QToolButton(self.view_grp)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.books_view_btn.sizePolicy().hasHeightForWidth())
@@ -169,8 +175,8 @@ def setupUi(self, ToolBar):
self.books_view_btn.setAutoRaise(True)
self.books_view_btn.setObjectName("books_view_btn")
self.horizontalLayout_3.addWidget(self.books_view_btn)
- self.high_view_btn = QtGui.QToolButton(self.view_grp)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
+ self.high_view_btn = QtWidgets.QToolButton(self.view_grp)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.high_view_btn.sizePolicy().hasHeightForWidth())
@@ -185,28 +191,44 @@ def setupUi(self, ToolBar):
self.high_view_btn.setAutoRaise(True)
self.high_view_btn.setObjectName("high_view_btn")
self.horizontalLayout_3.addWidget(self.high_view_btn)
+ self.sync_view_btn = QtWidgets.QToolButton(self.view_grp)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.sync_view_btn.sizePolicy().hasHeightForWidth())
+ self.sync_view_btn.setSizePolicy(sizePolicy)
+ icon10 = QtGui.QIcon()
+ icon10.addPixmap(QtGui.QPixmap(":/stuff/view-sync.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.sync_view_btn.setIcon(icon10)
+ self.sync_view_btn.setIconSize(QtCore.QSize(48, 48))
+ self.sync_view_btn.setCheckable(True)
+ self.sync_view_btn.setAutoExclusive(True)
+ self.sync_view_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
+ self.sync_view_btn.setAutoRaise(True)
+ self.sync_view_btn.setObjectName("sync_view_btn")
+ self.horizontalLayout_3.addWidget(self.sync_view_btn)
self.horizontalLayout.addWidget(self.view_grp)
- self.mode_grp = QtGui.QGroupBox(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.mode_grp = QtWidgets.QGroupBox(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.mode_grp.sizePolicy().hasHeightForWidth())
self.mode_grp.setSizePolicy(sizePolicy)
self.mode_grp.setObjectName("mode_grp")
- self.verticalLayout = QtGui.QVBoxLayout(self.mode_grp)
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.mode_grp)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
- self.loaded_btn = QtGui.QToolButton(self.mode_grp)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.loaded_btn = QtWidgets.QToolButton(self.mode_grp)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.loaded_btn.sizePolicy().hasHeightForWidth())
self.loaded_btn.setSizePolicy(sizePolicy)
self.loaded_btn.setMinimumSize(QtCore.QSize(80, 0))
- icon10 = QtGui.QIcon()
- icon10.addPixmap(QtGui.QPixmap(":/stuff/books.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.loaded_btn.setIcon(icon10)
+ icon11 = QtGui.QIcon()
+ icon11.addPixmap(QtGui.QPixmap(":/stuff/books.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.loaded_btn.setIcon(icon11)
self.loaded_btn.setIconSize(QtCore.QSize(24, 24))
self.loaded_btn.setCheckable(True)
self.loaded_btn.setChecked(True)
@@ -216,34 +238,34 @@ def setupUi(self, ToolBar):
self.loaded_btn.setObjectName("loaded_btn")
self.verticalLayout.addWidget(self.loaded_btn)
self.db_btn = XToolButton(self.mode_grp)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.db_btn.sizePolicy().hasHeightForWidth())
self.db_btn.setSizePolicy(sizePolicy)
self.db_btn.setMinimumSize(QtCore.QSize(80, 0))
- icon11 = QtGui.QIcon()
- icon11.addPixmap(QtGui.QPixmap(":/stuff/db.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.db_btn.setIcon(icon11)
+ icon12 = QtGui.QIcon()
+ icon12.addPixmap(QtGui.QPixmap(":/stuff/db.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.db_btn.setIcon(icon12)
self.db_btn.setIconSize(QtCore.QSize(24, 24))
self.db_btn.setCheckable(True)
self.db_btn.setAutoExclusive(True)
- self.db_btn.setPopupMode(QtGui.QToolButton.MenuButtonPopup)
+ self.db_btn.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
self.db_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.db_btn.setAutoRaise(True)
self.db_btn.setObjectName("db_btn")
self.verticalLayout.addWidget(self.db_btn)
self.horizontalLayout.addWidget(self.mode_grp)
- self.about_btn = QtGui.QToolButton(self.tool_frame)
- sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ self.about_btn = QtWidgets.QToolButton(self.tool_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.about_btn.sizePolicy().hasHeightForWidth())
self.about_btn.setSizePolicy(sizePolicy)
self.about_btn.setMinimumSize(QtCore.QSize(80, 0))
- icon12 = QtGui.QIcon()
- icon12.addPixmap(QtGui.QPixmap(":/stuff/logo64.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.about_btn.setIcon(icon12)
+ icon13 = QtGui.QIcon()
+ icon13.addPixmap(QtGui.QPixmap(":/stuff/logo64.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.about_btn.setIcon(icon13)
self.about_btn.setIconSize(QtCore.QSize(48, 48))
self.about_btn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.about_btn.setAutoRaise(True)
@@ -255,47 +277,53 @@ def setupUi(self, ToolBar):
QtCore.QMetaObject.connectSlotsByName(ToolBar)
def retranslateUi(self, ToolBar):
- self.scan_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Scans a directory for Koreader metadata files\n"
-"Can also be the eReader\'s root directory (Ctrl+L)", None, QtGui.QApplication.UnicodeUTF8))
- self.scan_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Scans a directory for Koreader metadata files. Can also be the eReader\'s root directory (Ctrl+L)", None, QtGui.QApplication.UnicodeUTF8))
- self.scan_btn.setText(QtGui.QApplication.translate("ToolBar", "Scan Directory", None, QtGui.QApplication.UnicodeUTF8))
- self.export_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Export selected highlights (Ctrl+S)", None, QtGui.QApplication.UnicodeUTF8))
- self.export_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Export selected highlights (Ctrl+S)", None, QtGui.QApplication.UnicodeUTF8))
- self.export_btn.setText(QtGui.QApplication.translate("ToolBar", "Export", None, QtGui.QApplication.UnicodeUTF8))
- self.open_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "View the selected book (Ctrl+B)", None, QtGui.QApplication.UnicodeUTF8))
- self.open_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "View the selected book (Ctrl+B)", None, QtGui.QApplication.UnicodeUTF8))
- self.open_btn.setText(QtGui.QApplication.translate("ToolBar", "View", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Open the filtering popup (Alt+F)", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Open the filtering popup (Alt+F)", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_btn.setText(QtGui.QApplication.translate("ToolBar", "Filter", None, QtGui.QApplication.UnicodeUTF8))
- self.filter_btn.setShortcut(QtGui.QApplication.translate("ToolBar", "Alt+F", None, QtGui.QApplication.UnicodeUTF8))
- self.merge_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Merge the highlights from the same book in two different\n"
+ self.tool_frame.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Right-click to change icon size", None, -1))
+ self.scan_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Scans a directory for Koreader metadata files\n"
+"Can also be the eReader\'s root directory (Ctrl+L)", None, -1))
+ self.scan_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Scans a directory for Koreader metadata files. Can also be the eReader\'s root directory (Ctrl+L)", None, -1))
+ self.scan_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Scan Directory", None, -1))
+ self.export_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Export selected highlights (Ctrl+S)", None, -1))
+ self.export_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Export selected highlights (Ctrl+S)", None, -1))
+ self.export_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Export", None, -1))
+ self.open_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "View the selected book (Ctrl+B)", None, -1))
+ self.open_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "View the selected book (Ctrl+B)", None, -1))
+ self.open_btn.setText(QtWidgets.QApplication.translate("ToolBar", "View", None, -1))
+ self.add_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Add a sync group", None, -1))
+ self.add_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Add a sync group", None, -1))
+ self.add_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Add", None, -1))
+ self.filter_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Open the filtering popup (Alt+F)", None, -1))
+ self.filter_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Open the filtering popup (Alt+F)", None, -1))
+ self.filter_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Filter", None, -1))
+ self.filter_btn.setShortcut(QtWidgets.QApplication.translate("ToolBar", "Alt+F", None, -1))
+ self.merge_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Merge the highlights from the same book in two different\n"
"devices, and/or sync their reading position.\n"
-"Activated only if two entries of the same book are selected.", None, QtGui.QApplication.UnicodeUTF8))
- self.merge_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Merge the highlights from the same book in two different devices, and/or sync their reading position. Activated only if two entries of the same book are selected.", None, QtGui.QApplication.UnicodeUTF8))
- self.merge_btn.setText(QtGui.QApplication.translate("ToolBar", "Merge/Sync", None, QtGui.QApplication.UnicodeUTF8))
- self.delete_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Delete selected Highlights (Del)", None, QtGui.QApplication.UnicodeUTF8))
- self.delete_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Delete selected Highlights (Del)", None, QtGui.QApplication.UnicodeUTF8))
- self.delete_btn.setText(QtGui.QApplication.translate("ToolBar", "Delete", None, QtGui.QApplication.UnicodeUTF8))
- self.clear_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Clears the books list (Ctrl+Backspace)", None, QtGui.QApplication.UnicodeUTF8))
- self.clear_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Clears the books list (Ctrl+Backspace)", None, QtGui.QApplication.UnicodeUTF8))
- self.clear_btn.setText(QtGui.QApplication.translate("ToolBar", "Clear List", None, QtGui.QApplication.UnicodeUTF8))
- self.books_view_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Books View", None, QtGui.QApplication.UnicodeUTF8))
- self.books_view_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Books View", None, QtGui.QApplication.UnicodeUTF8))
- self.books_view_btn.setText(QtGui.QApplication.translate("ToolBar", "Books", None, QtGui.QApplication.UnicodeUTF8))
- self.high_view_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Highlights View", None, QtGui.QApplication.UnicodeUTF8))
- self.high_view_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Highlights View", None, QtGui.QApplication.UnicodeUTF8))
- self.high_view_btn.setText(QtGui.QApplication.translate("ToolBar", "Highlights", None, QtGui.QApplication.UnicodeUTF8))
- self.loaded_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Show the loaded files", None, QtGui.QApplication.UnicodeUTF8))
- self.loaded_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Show the loaded files", None, QtGui.QApplication.UnicodeUTF8))
- self.loaded_btn.setText(QtGui.QApplication.translate("ToolBar", "Loaded", None, QtGui.QApplication.UnicodeUTF8))
- self.db_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Show the archived files in the database\n"
-"(Right click for database actions menu)", None, QtGui.QApplication.UnicodeUTF8))
- self.db_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Show the archived files in the database (Right click for database actions menu)", None, QtGui.QApplication.UnicodeUTF8))
- self.db_btn.setText(QtGui.QApplication.translate("ToolBar", "Archived", None, QtGui.QApplication.UnicodeUTF8))
- self.about_btn.setToolTip(QtGui.QApplication.translate("ToolBar", "Info about the KoHighlights (Ctrl+I)", None, QtGui.QApplication.UnicodeUTF8))
- self.about_btn.setStatusTip(QtGui.QApplication.translate("ToolBar", "Info about the KoHighlights (Ctrl+I)", None, QtGui.QApplication.UnicodeUTF8))
- self.about_btn.setText(QtGui.QApplication.translate("ToolBar", "About", None, QtGui.QApplication.UnicodeUTF8))
+"Activated only if two entries of the same book are selected.", None, -1))
+ self.merge_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Merge the highlights from the same book in two different devices, and/or sync their reading position. Activated only if two entries of the same book are selected.", None, -1))
+ self.merge_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Merge/Sync", None, -1))
+ self.delete_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Delete selected", None, -1))
+ self.delete_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Delete", None, -1))
+ self.clear_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Clear the list\'s entries (Ctrl+Backspace)", None, -1))
+ self.clear_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Clear the list\'s entries (Ctrl+Backspace)", None, -1))
+ self.clear_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Clear List", None, -1))
+ self.books_view_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Books View", None, -1))
+ self.books_view_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Books View", None, -1))
+ self.books_view_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Books", None, -1))
+ self.high_view_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Highlights View", None, -1))
+ self.high_view_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Highlights View", None, -1))
+ self.high_view_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Highlights", None, -1))
+ self.sync_view_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Sync Groups View", None, -1))
+ self.sync_view_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Sync Groups View", None, -1))
+ self.sync_view_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Sync Groups", None, -1))
+ self.loaded_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Show the loaded files", None, -1))
+ self.loaded_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Show the loaded files", None, -1))
+ self.loaded_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Loaded", None, -1))
+ self.db_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Show the archived files in the database\n"
+"(Right click for database actions menu)", None, -1))
+ self.db_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Show the archived files in the database (Right click for database actions menu)", None, -1))
+ self.db_btn.setText(QtWidgets.QApplication.translate("ToolBar", "Archived", None, -1))
+ self.about_btn.setToolTip(QtWidgets.QApplication.translate("ToolBar", "Info about the KoHighlights (Ctrl+I)", None, -1))
+ self.about_btn.setStatusTip(QtWidgets.QApplication.translate("ToolBar", "Info about the KoHighlights (Ctrl+I)", None, -1))
+ self.about_btn.setText(QtWidgets.QApplication.translate("ToolBar", "About", None, -1))
from secondary import XToolButton
import images_rc
diff --git a/gui_toolbar.ui b/gui_toolbar.ui
index 804168c..5c95e41 100644
--- a/gui_toolbar.ui
+++ b/gui_toolbar.ui
@@ -6,8 +6,8 @@
0
0
- 967
- 73
+ 1035
+ 91
@@ -40,24 +40,40 @@
Qt::CustomContextMenu
+
+ Right-click to change icon size
+
0
-
-
+
+
+
+ 0
+ 0
+
+
80
0
+
+ Scans a directory for Koreader metadata files
+Can also be the eReader's root directory (Ctrl+L)
+
+
+ Scans a directory for Koreader metadata files. Can also be the eReader's root directory (Ctrl+L)
+
-
+ Scan Directory
- ../KataLib/:/stuff/exec.png../KataLib/:/stuff/exec.png
+ :/stuff/folder_reader.png:/stuff/folder_reader.png
@@ -74,7 +90,7 @@
-
-
+
0
@@ -88,18 +104,17 @@
- Scans a directory for Koreader metadata files
-Can also be the eReader's root directory (Ctrl+L)
+ Export selected highlights (Ctrl+S)
- Scans a directory for Koreader metadata files. Can also be the eReader's root directory (Ctrl+L)
+ Export selected highlights (Ctrl+S)
- Scan Directory
+ Export
- :/stuff/folder_reader.png:/stuff/folder_reader.png
+ :/stuff/file_save.png:/stuff/file_save.png
@@ -107,6 +122,9 @@ Can also be the eReader's root directory (Ctrl+L)
48
+
+ QToolButton::MenuButtonPopup
+
Qt::ToolButtonTextUnderIcon
@@ -116,7 +134,7 @@ Can also be the eReader's root directory (Ctrl+L)
-
-
+
0
@@ -130,17 +148,17 @@ Can also be the eReader's root directory (Ctrl+L)
- Export selected highlights (Ctrl+S)
+ View the selected book (Ctrl+B)
- Export selected highlights (Ctrl+S)
+ View the selected book (Ctrl+B)
- Export
+ View
- :/stuff/file_save.png:/stuff/file_save.png
+ :/stuff/files_view.png:/stuff/files_view.png
@@ -157,31 +175,25 @@ Can also be the eReader's root directory (Ctrl+L)
-
-
+
0
0
-
-
- 80
- 0
-
-
- View the selected book (Ctrl+B)
+ Add a sync group
- View the selected book (Ctrl+B)
+ Add a sync group
- View
+ Add
- :/stuff/files_view.png:/stuff/files_view.png
+ :/stuff/files_add.png:/stuff/files_add.png
@@ -189,6 +201,9 @@ Can also be the eReader's root directory (Ctrl+L)
48
+
+ false
+
Qt::ToolButtonTextUnderIcon
@@ -302,10 +317,7 @@ Activated only if two entries of the same book are selected.
- Delete selected Highlights (Del)
-
-
- Delete selected Highlights (Del)
+ Delete selected
Delete
@@ -346,10 +358,10 @@ Activated only if two entries of the same book are selected.
- Clears the books list (Ctrl+Backspace)
+ Clear the list's entries (Ctrl+Backspace)
- Clears the books list (Ctrl+Backspace)
+ Clear the list's entries (Ctrl+Backspace)
Clear List
@@ -476,6 +488,47 @@ Activated only if two entries of the same book are selected.
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Sync Groups View
+
+
+ Sync Groups View
+
+
+ Sync Groups
+
+
+
+ :/stuff/view-sync.png:/stuff/view-sync.png
+
+
+
+ 48
+ 48
+
+
+
+ true
+
+
+ true
+
+
+ Qt::ToolButtonTextUnderIcon
+
+
+ true
+
+
+
diff --git a/images.qrc b/images.qrc
index 0a1187d..b28479e 100644
--- a/images.qrc
+++ b/images.qrc
@@ -1,36 +1,43 @@
-stuff/files_view.png
-stuff/exec.png
-stuff/refresh16.png
-stuff/copy.png
-stuff/file_exists.png
+stuff/trash.png
+stuff/files_add.png
+stuff/db_open.png
+stuff/folder_open.png
stuff/delete.png
-stuff/logo64.png
-stuff/filter.png
-stuff/paypal.png
-stuff/wait.gif
+stuff/calendar.png
stuff/file_missing.png
-stuff/trans32.png
+stuff/show_pages.png
+stuff/paypal.png
+stuff/files_delete.png
+stuff/label_green.png
stuff/file_edit.png
-stuff/folder_reader.png
-stuff/sort.png
-stuff/db_open.png
+stuff/file_exists.png
stuff/db_add.png
-stuff/books.png
-stuff/calendar.png
-stuff/view_books.png
-stuff/view-highlights.png
-stuff/label_green.png
+stuff/font.ttf
+stuff/copy.png
+stuff/power32gray.png
stuff/paypal76.png
-stuff/logo.png
+stuff/folder_reader.png
+stuff/view-sync.png
+stuff/power32red.png
+stuff/sync.png
stuff/file_save.png
+stuff/files_view.png
+stuff/logo64.png
+stuff/wait.gif
+stuff/trans32.png
+stuff/logo.png
+stuff/add.png
+stuff/db.png
+stuff/books.png
stuff/description.png
-stuff/folder_open.png
+stuff/filter.png
+stuff/refresh16.png
+stuff/view-highlights.png
+stuff/view_books.png
stuff/files_merge.png
-stuff/db.png
-stuff/trash.png
-stuff/show_pages.png
-stuff/files_delete.png
+stuff/del.png
+stuff/sort.png
\ No newline at end of file
diff --git a/main.py b/main.py
index cdc41e3..7733e33 100644
--- a/main.py
+++ b/main.py
@@ -1,5 +1,4 @@
# coding=utf-8
-from __future__ import absolute_import, division, print_function, unicode_literals
from boot_config import *
from boot_config import _
import os, sys, re
@@ -12,54 +11,48 @@
import hashlib
from datetime import datetime
from functools import partial
+from copy import deepcopy
from collections import defaultdict
+from ntpath import normpath
from os.path import (isdir, isfile, join, basename, splitext, dirname, split, getmtime,
abspath, splitdrive)
from pprint import pprint
-
-if QT4: # ___ ______________ DEPENDENCIES __________________________
- from PySide.QtSql import QSqlDatabase, QSqlQuery
- from PySide.QtCore import (Qt, QTimer, Slot, QThread, QMimeData, QModelIndex,
- QByteArray, QPoint)
- from PySide.QtGui import (QMainWindow, QApplication, QMessageBox, QIcon, QFileDialog,
- QTableWidgetItem, QTextCursor, QMenu, QAction, QHeaderView,
- QPixmap, QListWidgetItem, QBrush, QColor)
-elif QT5:
+if QT5:
from PySide2.QtWidgets import (QMainWindow, QHeaderView, QApplication, QMessageBox,
QAction, QMenu, QTableWidgetItem, QListWidgetItem,
- QFileDialog)
+ QFileDialog, QComboBox)
from PySide2.QtCore import (Qt, QTimer, QThread, QModelIndex, Slot, QPoint, QMimeData,
QByteArray)
from PySide2.QtSql import QSqlDatabase, QSqlQuery
- from PySide2.QtGui import QIcon, QPixmap, QTextCursor, QBrush, QColor
+ from PySide2.QtGui import QIcon, QPixmap, QTextCursor, QBrush, QColor, QFontDatabase
else: # Qt6
from PySide6.QtWidgets import (QMainWindow, QHeaderView, QApplication, QMessageBox,
- QFileDialog, QTableWidgetItem, QMenu, QListWidgetItem)
+ QFileDialog, QTableWidgetItem, QMenu, QListWidgetItem,
+ QComboBox)
from PySide6.QtCore import Qt, QTimer, Slot, QPoint, QThread, QModelIndex, QMimeData
- from PySide6.QtGui import QIcon, QAction, QBrush, QColor, QPixmap, QTextCursor
+ from PySide6.QtGui import (QIcon, QAction, QBrush, QColor, QPixmap, QTextCursor,
+ QFontDatabase)
from PySide6.QtSql import QSqlDatabase, QSqlQuery
from secondary import *
from gui_main import Ui_Base
-if PYTHON2: # ___ __________ PYTHON 2/3 COMPATIBILITY ______________
- import cPickle as pickle
- from distutils.version import LooseVersion as version_parse
-else:
- from packaging.version import parse as version_parse
- import pickle
+from packaging.version import parse as version_parse
+import pickle
__author__ = "noEmbryo"
-__version__ = "1.7.6.0"
+__version__ = "2.0.0.0"
class Base(QMainWindow, Ui_Base):
def __init__(self, parent=None):
super(Base, self).__init__(parent)
+ # noinspection PyArgumentList
+ self.app = QApplication.instance()
self.scan_thread = None
self.setupUi(self)
@@ -87,43 +80,46 @@ def __init__(self, parent=None):
self.exit_msg = True
self.db_path = join(SETTINGS_DIR, "data.db")
self.date_vacuumed = datetime.now().strftime(DATE_FORMAT)
+ self.theme = THEME_NONE_OLD
self.date_format = DATE_FORMAT
+ self.show_items = [True, True, True, True, True]
# ___ ___________________________________
self.file_selection = None
self.sel_idx = None
self.sel_indexes = []
- self.high_view_selection = None
- self.sel_high_view = []
self.high_list_selection = None
self.sel_high_list = []
+ self.high_view_selection = None
+ self.sel_high_view = []
+ self.sync_view_selection = None
+ self.sel_sync_view = []
self.loaded_paths = set()
self.books2reload = set()
self.parent_book_data = {}
self.reload_highlights = True
+ self.sync_groups_loaded = False
self.threads = []
self.query = None
self.db = None
self.books = []
+ self.sync_groups = []
+ tooltip = _("Right click to ignore english articles")
+ self.file_table.horizontalHeaderItem(TITLE).setToolTip(tooltip)
self.header_main = self.file_table.horizontalHeader()
self.header_main.setDefaultAlignment(Qt.AlignLeft)
self.header_main.setContextMenuPolicy(Qt.CustomContextMenu)
self.header_high_view = self.high_table.horizontalHeader()
self.header_high_view.setDefaultAlignment(Qt.AlignLeft)
# self.header_high_view.setResizeMode(HIGHLIGHT_H, QHeaderView.Stretch)
- if QT4:
- self.file_table.verticalHeader().setResizeMode(QHeaderView.Fixed)
- self.header_main.setMovable(True)
- self.high_table.verticalHeader().setResizeMode(QHeaderView.Fixed)
- self.header_high_view.setMovable(True)
- elif QT5 or QT6:
- self.file_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
- self.header_main.setSectionsMovable(True)
- self.high_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
- self.header_high_view.setSectionsMovable(True)
+
+ self.file_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
+ self.header_main.setSectionsMovable(True)
+ self.high_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
+ self.header_high_view.setSectionsMovable(True)
self.splitter.setCollapsible(0, False)
self.splitter.setCollapsible(1, False)
@@ -136,19 +132,38 @@ def __init__(self, parent=None):
self.ico_file_save = QIcon(":/stuff/file_save.png")
self.ico_files_merge = QIcon(":/stuff/files_merge.png")
self.ico_files_delete = QIcon(":/stuff/files_delete.png")
- self.ico_file_exists = QIcon(":/stuff/file_exists.png")
- self.ico_file_missing = QIcon(":/stuff/file_missing.png")
+ self.ico_db_add = QIcon(":/stuff/db_add.png")
+ self.ico_db_open = QIcon(":/stuff/db_open.png")
+ self.ico_refresh = QIcon(":/stuff/refresh16.png")
+ self.ico_folder_open = QIcon(":/stuff/folder_open.png")
+ self.ico_calendar = QIcon(":/stuff/calendar.png")
+ self.ico_sort = QIcon(":/stuff/sort.png")
+ self.ico_view_books = QIcon(":/stuff/view_books.png")
+ self.ico_files_view = QIcon(":/stuff/files_view.png")
self.ico_file_edit = QIcon(":/stuff/file_edit.png")
self.ico_copy = QIcon(":/stuff/copy.png")
self.ico_delete = QIcon(":/stuff/delete.png")
+ self.def_icons = [
+ self.ico_file_save,
+ self.ico_files_merge,
+ self.ico_files_delete,
+ self.ico_db_add,
+ self.ico_db_open,
+ self.ico_refresh,
+ self.ico_folder_open,
+ self.ico_calendar,
+ self.ico_sort,
+ self.ico_view_books,
+ self.ico_files_view,
+ self.ico_file_edit,
+ self.ico_copy,
+ self.ico_delete,
+ ]
+ self.ico_file_exists = QIcon(":/stuff/file_exists.png")
+ self.ico_file_missing = QIcon(":/stuff/file_missing.png")
self.ico_label_green = QIcon(":/stuff/label_green.png")
- self.ico_view_books = QIcon(":/stuff/view_books.png")
- self.ico_db_add = QIcon(":/stuff/db_add.png")
- self.ico_db_open = QIcon(":/stuff/db_open.png")
self.ico_app = QIcon(":/stuff/logo64.png")
self.ico_empty = QIcon(":/stuff/trans32.png")
- self.ico_refresh = QIcon(":/stuff/refresh16.png")
- self.ico_folder_open = QIcon(":/stuff/folder_open.png")
# noinspection PyArgumentList
self.clip = QApplication.clipboard()
@@ -163,11 +178,18 @@ def __init__(self, parent=None):
self.toolbar.merge_btn.setEnabled(False)
self.toolbar.delete_btn.setEnabled(False)
+ self.export_menu = QMenu(self)
+ self.export_menu.setTitle(_("Export"))
+ self.export_menu.setIcon(self.ico_file_save)
+ self.export_menu.aboutToShow.connect(self.create_export_menu) # assign menu
+ if QT6: # QT6 requires exec() instead of exec_()
+ self.export_menu.exec_ = getattr(self.export_menu, "exec")
+ self.toolbar.export_btn.setMenu(self.export_menu)
+
self.status = Status(self)
self.statusbar.addPermanentWidget(self.status)
self.edit_high = TextDialog(self)
- self.edit_high.on_ok = self.edit_comment_ok
self.edit_high.setWindowTitle(_("Comments"))
self.description = TextDialog(self)
@@ -179,6 +201,19 @@ def __init__(self, parent=None):
self.review_lbl.setVisible(False)
self.review_txt.setVisible(False)
+ tbar = self.toolbar
+ self.def_btn_icos = []
+ self.buttons = [(tbar.scan_btn, "A"), (tbar.export_btn, "B"),
+ (tbar.open_btn, "C"), (tbar.filter_btn, "D"),
+ (tbar.merge_btn, "E"), (tbar.add_btn, "X"),
+ (tbar.delete_btn, "O"), (tbar.clear_btn, "G"),
+ (tbar.books_view_btn, "H"), (tbar.high_view_btn, "I"),
+ (tbar.sync_view_btn, "W"), (tbar.loaded_btn, "H"),
+ (tbar.db_btn, "K"), (self.status.show_items_btn, "U"),
+ (self.description_btn, "V"), (self.filter.filter_btn, "D"),
+ (self.filter.clear_filter_btn, "G"),
+ ]
+
# noinspection PyTypeChecker,PyCallByClass
QTimer.singleShot(10000, self.auto_check4update) # check for updates
@@ -192,12 +227,15 @@ def __init__(self, parent=None):
def on_load(self):
""" Things that must be done after the initialization
"""
+ fdb = QFontDatabase()
+ fdb.addApplicationFont(":/stuff/font.ttf")
+ # fdb.removeApplicationFont(0)
+
self.settings_load()
self.init_db()
if FIRST_RUN: # on first run
self.toolbar.loaded_btn.click()
self.splitter.setSizes((500, 250))
- self.toolbar.export_btn.setMenu(self.get_export_menu()) # assign/create menu
self.toolbar.merge_btn.setMenu(self.merge_menu()) # assign/create menu
self.toolbar.delete_btn.setMenu(self.delete_menu()) # assign/create menu
self.connect_gui()
@@ -210,7 +248,7 @@ def on_load(self):
self.toolbar.loaded_btn.setChecked(True) # open in Loaded mode
else:
self.toolbar.db_btn.setChecked(True) # open in Archived mode
- text = _("Loading {} database").format(APP_NAME)
+ text = _(f"Loading {APP_NAME} database")
self.loading_thread(DBLoader, self.books, text)
self.read_books_from_db() # always load db on start
if self.current_view == BOOKS_VIEW:
@@ -218,8 +256,93 @@ def on_load(self):
else:
self.toolbar.high_view_btn.click() # open in Highlights view
+ self.setup_buttons()
self.show()
+ def setup_buttons(self):
+ for btn, char in self.buttons:
+ self.def_btn_icos.append(btn.icon())
+ size = btn.iconSize().toTuple()
+ btn.xig = XIconGlyph(self, {"family": "XFont", "size": size, "char": char})
+ # btn.glyph = btn.xig.get_icon()
+
+ def set_new_icons(self, menus=True):
+ """ Create the new icons
+
+ :type menus: bool
+ :param menus: Create the new menu icons too
+ """
+ # noinspection PyTypeChecker
+ QTimer.singleShot(0, partial(self.delayed_set_new_icons, menus))
+
+ def delayed_set_new_icons(self, menus=True):
+ """ Delay the creation of the icons to allow for the new palette to be set
+
+ :type menus: bool
+ :param menus: Create the menu icons too
+ """
+ for btn, _ in self.buttons:
+ size = btn.iconSize().toTuple()
+ btn.setIcon(btn.xig.get_icon({"size": size}))
+
+ if menus: # recreate the menu icons
+ xig = XIconGlyph(self, {"family": "XFont", "size": (16, 16)})
+ self.export_menu.setIcon(xig.get_icon({"char": "B"}))
+ self.ico_file_save = xig.get_icon({"char": "B"})
+ self.ico_files_merge = xig.get_icon({"char": "E"})
+ self.ico_files_delete = xig.get_icon({"char": "O"})
+ self.ico_db_add = xig.get_icon({"char": "L"})
+ self.ico_db_open = xig.get_icon({"char": "M"})
+ self.ico_refresh = xig.get_icon({"char": "N"})
+ self.ico_folder_open = xig.get_icon({"char": "P"})
+ self.ico_calendar = xig.get_icon({"char": "T"})
+ self.ico_sort = xig.get_icon({"char": "S"})
+ self.ico_view_books = xig.get_icon({"char": "H"})
+ self.act_view_book.setIcon(xig.get_icon({"char": "C"}))
+ self.ico_file_edit = xig.get_icon({"char": "Q"})
+ self.ico_copy = xig.get_icon({"char": "R"})
+ self.ico_delete = xig.get_icon({"char": "O"})
+
+ def set_old_icons(self):
+ """ Reload the old icons
+ """
+ for idx, item in enumerate(self.buttons):
+ btn = item[0]
+ btn.setIcon(self.def_btn_icos[idx])
+
+ self.ico_file_save = self.def_icons[0]
+ self.ico_files_merge = self.def_icons[1]
+ self.ico_files_delete = self.def_icons[2]
+ self.ico_db_add = self.def_icons[3]
+ self.ico_db_open = self.def_icons[4]
+ self.ico_refresh = self.def_icons[5]
+ self.ico_folder_open = self.def_icons[6]
+ self.ico_calendar = self.def_icons[7]
+ self.ico_sort = self.def_icons[8]
+ self.ico_view_books = self.def_icons[9]
+ self.act_view_book.setIcon(self.def_icons[10])
+ self.ico_file_edit = self.def_icons[11]
+ self.ico_copy = self.def_icons[12]
+ self.ico_delete = self.def_icons[13]
+
+ def reset_theme_colors(self):
+ """ Resets the widget colors after a theme change
+ """
+ color = self.app.palette().base().color().name()
+ self.review_txt.setStyleSheet(f'background-color: "{color}";')
+ color = self.app.palette().window().color().name()
+ for item in [self.status.show_items_btn, self.status.theme_box]:
+ item.setStyleSheet(f'background-color: "{color}";')
+
+ color = self.app.palette().button().color().name()
+ for row in range(self.sync_table.rowCount()):
+ wdg = self.sync_table.cellWidget(row, 0)
+ wdg.setStyleSheet('QFrame#items_frm {background-color: "%s";}' % color)
+ wdg.setup_icons()
+
+ self.setup_buttons()
+ self.reload_table(_("Reloading books..."))
+
# ___ ___________________ EVENTS STUFF __________________________
def connect_gui(self):
@@ -237,6 +360,10 @@ def connect_gui(self):
self.header_high_view.sectionClicked.connect(self.on_highlight_column_clicked)
self.header_high_view.sectionResized.connect(self.on_highlight_column_resized)
+ self.sync_table.base = self
+ self.sync_view_selection = self.sync_table.selectionModel()
+ self.sync_view_selection.selectionChanged.connect(self.sync_view_selection_update)
+
sys.stdout = LogStream()
sys.stdout.setObjectName("out")
sys.stdout.append_to_log.connect(self.write_to_log)
@@ -255,40 +382,36 @@ def keyPressEvent(self, event):
if mod == Qt.ControlModifier: # if control is pressed
if key == Qt.Key_Backspace:
self.toolbar.on_clear_btn_clicked()
- return True
- if key == Qt.Key_L:
+ elif key == Qt.Key_L:
self.toolbar.on_scan_btn_clicked()
- return True
- if key == Qt.Key_S:
+ elif key == Qt.Key_S:
self.on_export()
- return True
- if key == Qt.Key_I:
+ elif key == Qt.Key_I:
self.toolbar.on_about_btn_clicked()
- return True
- if key == Qt.Key_F:
+ elif key == Qt.Key_F:
self.toolbar.filter_btn.click()
- return True
- if key == Qt.Key_Q:
+ elif key == Qt.Key_Q:
self.close()
- if self.current_view == HIGHLIGHTS_VIEW and self.sel_high_view:
+ elif self.current_view == HIGHLIGHTS_VIEW and self.sel_high_view:
if key == Qt.Key_C:
self.copy_text_2clip(self.get_highlights()[0])
- return True
+
if mod == Qt.AltModifier: # if alt is pressed
if key == Qt.Key_A:
self.on_archive()
return True
- if self.current_view == HIGHLIGHTS_VIEW and self.sel_high_view:
+ elif self.current_view == HIGHLIGHTS_VIEW and self.sel_high_view:
if key == Qt.Key_C:
self.copy_text_2clip(self.get_highlights()[1])
return True
if key == Qt.Key_Escape:
self.close()
- return True
- if key == Qt.Key_Delete:
- self.delete_actions(0)
- return True
+ elif key == Qt.Key_Delete:
+ if self.current_view == BOOKS_VIEW:
+ self.delete_actions(0)
+ else:
+ self.toolbar.on_delete_btn_clicked()
def closeEvent(self, event):
""" Accepts or rejects the `exit` command
@@ -300,7 +423,7 @@ def closeEvent(self, event):
self.bye_bye_stuff()
event.accept()
return
- popup = self.popup(_("Confirmation"), _("Exit {}?").format(APP_NAME), buttons=2,
+ popup = self.popup(_("Confirmation"), _(f"Exit {APP_NAME}?"), buttons=2,
check_text=DO_NOT_SHOW)
self.exit_msg = not popup.checked
if popup.buttonRole(popup.clickedButton()) == QMessageBox.AcceptRole:
@@ -331,7 +454,7 @@ def init_db(self):
self.query.exec_ = getattr(self.query, "exec")
if app_config:
pass
- # self.query.exec_("""PRAGMA user_version""") # 2do: enable if db changes
+ # self.query.exec_("""PRAGMA user_version""") # 2check: enable if db changes
# while self.query.next():
# self.check_db_version(self.query.value(0)) # check the db version
self.set_db_version() if not isfile(self.db_path) else None
@@ -354,7 +477,7 @@ def update_db(self, db_version):
def set_db_version(self):
""" Set the current database version
"""
- self.query.exec_("""PRAGMA user_version = {}""".format(DB_VERSION))
+ self.query.exec_(f"""PRAGMA user_version = {DB_VERSION}""")
def change_db(self, mode):
""" Changes the current db file
@@ -424,6 +547,7 @@ def add_books2db(self, books):
def read_books_from_db(self):
""" Reads the contents of the books' db table
"""
+ # print("Reading data from db")
del self.books[:]
self.query.setForwardOnly(True)
self.query.exec_("""SELECT * FROM books""")
@@ -494,7 +618,7 @@ def on_file_table_fileDropped(self, dropped):
# self.file_table.setSortingEnabled(False)
for i in dropped:
if splitext(i)[1] == ".lua":
- self.create_row(i)
+ self.create_row(normpath(i))
# self.file_table.setSortingEnabled(True)
folders = [j for j in dropped if isdir(j)]
for folder in folders:
@@ -535,36 +659,44 @@ def populate_book_info(self, data, row):
:type row: int
:param row: The item's row number
"""
+ stats = "doc_props" if "doc_props" in data else "stats" if "stats" in data else ""
for key, field in zip(self.info_keys, self.info_fields):
try:
- if key == "title" and not data["stats"][key]:
+ if key == "title" and not data[stats][key]:
path = self.file_table.item(row, PATH).data(0)
try:
name = path.split("#] ")[1]
value = splitext(name)[0]
except IndexError: # no "#] " in filename
value = ""
+ elif key == "pages":
+ if "doc_pages" in data:
+ value = data["doc_pages"]
+ else: # older type file
+ value = data[stats][key]
elif key == "keywords":
keywords = data["doc_props"][key].split("\n")
value = "; ".join([i.rstrip("\\") for i in keywords])
else:
- value = data["stats"][key]
+ value = data[stats][key]
try:
field.setText(value)
except TypeError: # Needs string only
field.setText(str(value) if value else "") # "" if 0
except KeyError: # older type file or other problems
path = self.file_table.item(row, PATH).data(0)
- stats = self.get_item_stats(path, data)
+ stats_ = self.get_item_stats(data, path)
if key == "title":
- field.setText(stats[1])
+ field.setText(stats_["title"])
elif key == "authors":
- field.setText(stats[2])
+ field.setText(stats_["authors"])
else:
field.setText("")
review = data.get("summary", {}).get("note", "")
self.review_lbl.setVisible(bool(review))
+ color = self.app.palette().base().color().name()
+ self.review_txt.setStyleSheet(f'background-color: "{color}";')
self.review_txt.setVisible(bool(review))
self.review_txt.setText(review)
@@ -595,12 +727,7 @@ def on_file_table_customContextMenuRequested(self, point):
self.act_view_book.setEnabled(self.toolbar.open_btn.isEnabled())
self.act_view_book.setData(row)
menu.addAction(self.act_view_book)
-
- export_menu = self.get_export_menu()
- export_menu.setIcon(self.ico_file_save)
- export_menu.setTitle(_("Export"))
- menu.addMenu(export_menu)
-
+ menu.addMenu(self.export_menu)
if not self.db_mode:
action = QAction(_("Archive") + "\tAlt+A", menu)
action.setIcon(self.ico_db_add)
@@ -611,11 +738,28 @@ def on_file_table_customContextMenuRequested(self, point):
sync_group = QMenu(self)
sync_group.setTitle(_("Sync"))
sync_group.setIcon(self.ico_files_merge)
- if self.check4archive_merge() is not False:
+
+ action = QAction(_("Create a sync group"), sync_group)
+ action.setIcon(self.ico_files_merge)
+ path = self.file_table.item(row, PATH).data(0)
+ title = self.file_table.item(row, TITLE).data(0)
+ data = self.file_table.item(row, TITLE).data(Qt.UserRole)
+ book_data = {"path": path, "data": data}
+ info = {"title": title,
+ "sync_pos": False,
+ "merge": False,
+ "sync_db": True,
+ "items": [book_data],
+ "enabled": True}
+ action.triggered.connect(partial(self.create_sync_row, info))
+ sync_group.addAction(action)
+
+ if self.check4archive_merge(book_data) is not False:
sync_menu = self.create_archive_merge_menu()
sync_menu.setTitle(_("Sync with archived"))
sync_menu.setIcon(self.ico_files_merge)
sync_group.addMenu(sync_menu)
+
action = QAction(_("Sync with file"), sync_group)
action.setIcon(self.ico_files_merge)
action.triggered.connect(self.use_meta_files)
@@ -635,11 +779,20 @@ def on_file_table_customContextMenuRequested(self, point):
action.triggered.connect(partial(self.open_file, folder_path))
menu.addAction(action)
+ elif len(self.sel_indexes) == 2:
+ if self.toolbar.merge_btn.isEnabled():
+ action = QAction(_("Sync books"), menu)
+ action.setIcon(self.ico_files_merge)
+ action.triggered.connect(self.toolbar.on_merge_btn_clicked)
+ menu.addAction(action)
+
+ menu.addSeparator()
delete_menu = self.delete_menu()
delete_menu.setIcon(self.ico_files_delete)
delete_menu.setTitle(_("Delete") + "\tDel")
menu.addMenu(delete_menu)
else:
+ menu.addSeparator()
action = QAction(_("Delete") + "\tDel", menu)
action.setIcon(self.ico_files_delete)
action.triggered.connect(partial(self.delete_actions, 0))
@@ -673,7 +826,7 @@ def get_book_path(meta_path, data):
ext = splitext(meta_path)[1]
book_path = splitext(split(meta_path)[0])[0] + ext
book_exists = isfile(book_path)
- if not book_exists: # use the recorded file path
+ if not book_exists: # use the recorded file path (newer metadata only)
doc_path = data.get("doc_path")
if doc_path:
drive = splitdrive(meta_path)[0]
@@ -719,6 +872,8 @@ def file_selection_update(self, selected, deselected):
else:
self.high_list.clear()
self.description_btn.setEnabled(False)
+ self.review_txt.hide()
+ self.review_lbl.hide()
for field in self.info_fields:
field.setText("")
self.toolbar.activate_buttons()
@@ -760,7 +915,9 @@ def toggle_title_sort(self):
""" Toggles the way titles are sorted (use or not A/The)
"""
self.alt_title_sort = not self.alt_title_sort
- text = _("ReSorting books...")
+ self.reload_table(_("ReSorting books..."))
+
+ def reload_table(self, text):
if not self.db_mode:
self.loading_thread(ReLoader, self.loaded_paths.copy(), text)
else:
@@ -790,7 +947,7 @@ def on_archive(self):
if self.archive_warning: # warn about book replacement in archive
extra = _("these books") if len(self.sel_indexes) > 1 else _("this book")
popup = self.popup(_("Question!"),
- _("Add or replace {} in the archive?").format(extra),
+ _(f"Add or replace {extra} in the archive?"),
buttons=2, icon=QMessageBox.Question,
check_text=DO_NOT_SHOW)
self.archive_warning = not popup.checked
@@ -806,9 +963,18 @@ def on_archive(self):
path = self.file_table.item(row, PATH).text()
date = self.file_table.item(row, MODIFIED).text()
data = self.file_table.item(row, TITLE).data(Qt.UserRole)
- if not data["highlight"]: # no highlights, don't add
- empty += 1
- continue
+ annotations = data.get("annotations")
+ if annotations is not None: # new format metadata
+ for high_idx in annotations:
+ if annotations[high_idx]["pos0"]:
+ break # there is at least one highlight in the book
+ else: # no highlights don't add
+ empty += 1
+ continue
+ else: # old format metadata
+ if not data["highlight"]: # no highlights, don't add
+ empty += 1
+ continue
try:
md5 = data["partial_md5_checksum"]
except KeyError: # older metadata, don't add
@@ -823,24 +989,24 @@ def on_archive(self):
extra = ""
if empty:
- extra += _("\nNot added {} books with no highlights.").format(empty)
+ extra += _(f"\nNot added {empty} books with no highlights.")
if older:
- extra += _("\nNot added {} books with old type metadata.").format(older)
+ extra += _(f"\nNot added {older} books with old type metadata.")
self.popup(_("Added!"),
- _("{} books were added/updated to the Archive from the {} processed.")
- .format(added, len(self.sel_indexes)) + extra,
+ _(f"{added} books were added/updated to the Archive from the "
+ f"{len(self.sel_indexes)} processed."),
icon=QMessageBox.Information)
def loading_thread(self, worker, args, text, clear=True):
""" Populates the file_table with different contents
"""
- if clear:
+ if clear and self.current_view != SYNC_VIEW:
self.toolbar.on_clear_btn_clicked()
self.file_table.setSortingEnabled(False) # re-enable it after populating table
self.status.animation(True)
- self.auto_info.set_text(_("{}.\nPlease Wait...").format(text))
+ self.auto_info.set_text(_(f"{text}.\nPlease Wait..."))
self.auto_info.show()
scan_thread = QThread()
@@ -884,20 +1050,26 @@ def create_row(self, meta_path, data=None, date=None):
if meta_path in self.loaded_paths:
return # already loaded file
self.loaded_paths.add(meta_path)
- data = decode_data(meta_path)
+ try:
+ data = decode_data(meta_path)
+ except PermissionError:
+ self.base.error(_(f"Could not access the book's metadata file\n"
+ f"{meta_path}"))
+ return
if not data:
print("No data here!", meta_path)
return
date = str(datetime.fromtimestamp(getmtime(meta_path))).split(".")[0]
- stats = self.get_item_stats(meta_path, data)
+ stats = self.get_item_stats(data, meta_path)
else: # for db entries
- stats = self.get_item_db_stats(data)
- icon, title, authors, percent, rating, status, high_count = stats
-
- # noinspection PyArgumentList
- color = ("#660000" if status == "abandoned" else
- # "#005500" if status == "complete" else
- QApplication.palette().text().color())
+ stats = self.get_item_stats(data)
+ title = stats["title"]
+ authors = stats["authors"]
+ percent = stats["percent"]
+ rating = stats["rating"]
+ status = stats["status"]
+ high_count = stats["high_count"]
+ icon = self.ico_label_green if high_count else self.ico_empty
self.file_table.setSortingEnabled(False)
self.file_table.insertRow(0)
@@ -917,7 +1089,7 @@ def create_row(self, meta_path, data=None, date=None):
book_icon = self.ico_file_exists if book_exists else self.ico_file_missing
type_item = QTableWidgetItem(book_icon, ext)
type_item.setToolTip(book_path if book_exists else
- _("The {} file is missing!").format(ext))
+ _(f"The {ext} file is missing!"))
type_item.setData(Qt.UserRole, (book_path, book_exists))
self.file_table.setItem(0, TYPE, type_item)
@@ -945,71 +1117,58 @@ def create_row(self, meta_path, data=None, date=None):
for i in range(8): # colorize row
item = self.file_table.item(0, i)
- item.setForeground(QBrush(QColor(color)))
+ if status == "abandoned":
+ if self.theme in (THEME_DARK_NEW, THEME_DARK_OLD):
+ color = "#DD0000"
+ else:
+ color = "#660000"
+ item.setForeground(QBrush(QColor(color)))
self.file_table.setSortingEnabled(True)
- def get_item_db_stats(self, data):
- """ Returns the title and authors of a history file
+ @staticmethod
+ def get_item_stats(data, filename=None):
+ """ Returns the title and authors of a metadata file
:type data: dict
:param data: The dict converted lua file
- """
- if data["highlight"]:
- icon = self.ico_label_green
- high_count = str(len(data["highlight"]))
- else:
- icon = self.ico_empty
- high_count = ""
- title = data["stats"]["title"]
- authors = data["stats"]["authors"]
- title = title if title else NO_TITLE
- authors = authors if authors else NO_AUTHOR
- try:
- percent = str(int(data["percent_finished"] * 100)) + "%"
- except KeyError:
- percent = ""
- if "summary" in data:
- rating = data["summary"].get("rating")
- rating = rating * "*" if rating else ""
- status = data["summary"].get("status")
- else:
- rating = ""
- status = None
-
- return icon, title, authors, percent, rating, status, high_count
-
- def get_item_stats(self, filename, data):
- """ Returns the title and authors of a metadata file
-
:type filename: str|unicode
:param filename: The filename to get the stats for
- :type data: dict
- :param data: The dict converted lua file
"""
- if data["highlight"]:
- icon = self.ico_label_green
- high_count = str(len(data["highlight"]))
- else:
- icon = self.ico_empty
- high_count = ""
- try:
- title = data["stats"]["title"]
- authors = data["stats"]["authors"]
- except KeyError: # older type file
- title = splitext(basename(filename))[0]
- try:
- name = title.split("#] ")[1]
- title = splitext(name)[0]
- except IndexError: # no "#] " in filename
- pass
- authors = OLD_TYPE
- if not title:
+ stats = "doc_props" if "doc_props" in data else "stats" if "stats" in data else ""
+ if filename: # stats from a file
try:
- name = filename.split("#] ")[1]
- title = splitext(name)[0]
- except IndexError: # no "#] " in filename
- title = NO_TITLE
- authors = authors if authors else NO_AUTHOR
+ title = data[stats]["title"]
+ authors = data[stats]["authors"]
+ except KeyError: # much older type file
+ title = splitext(basename(filename))[0]
+ try:
+ name = title.split("#] ")[1]
+ title = splitext(name)[0]
+ except IndexError: # no "#] " in filename
+ pass
+ authors = OLD_TYPE
+ if not title:
+ try:
+ name = filename.split("#] ")[1]
+ title = splitext(name)[0]
+ except IndexError: # no "#] " in filename
+ title = NO_TITLE
+ authors = authors if authors else NO_AUTHOR
+ else: # stats from a db entry
+ title = data[stats]["title"]
+ authors = data[stats]["authors"]
+
+ annotations = data.get("annotations")
+ if annotations is not None: # new format metadata
+ high_count = len([i for i in annotations.values() if i.get("pos0")])
+ else: # old format metadata
+ high_count = 0
+ if data["highlight"]:
+ for page in data["highlight"]:
+ high_count += len(data["highlight"][page])
+ high_count = str(high_count) if high_count else ""
+ high_count = str(high_count) if high_count else ""
+
try:
percent = str(int(data["percent_finished"] * 100)) + "%"
except KeyError:
@@ -1021,110 +1180,575 @@ def get_item_stats(self, filename, data):
else:
rating = ""
status = None
+ return {"title": title, "authors": authors, "percent": percent,
+ "rating": rating, "status": status, "high_count": high_count}
- return icon, title, authors, percent, rating, status, high_count
-
- # ___ ___________________ HIGHLIGHT TABLE STUFF _________________
+ # ___ ___________________ HIGHLIGHTS LIST STUFF _________________
- @Slot(QTableWidgetItem)
- def on_high_table_itemClicked(self, item):
- """ When an item of the high_table is clicked
+ def populate_high_list(self, data, path=""):
+ """ Populates the Highlights list of `Book` view
- :type item: QTableWidgetItem
- :param item: The item (cell) that is clicked
+ :type data: dict
+ :param data: The item/book's data
+ :type path: str|unicode
+ :param path: The item/book's path
"""
- row = item.row()
- path = self.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)["path"]
+ space = (" " if self.status.act_page.isChecked() and
+ self.status.act_date.isChecked() else "")
+ line_break = (":\n" if self.status.act_page.isChecked() or
+ self.status.act_date.isChecked() else "")
+ def_date_format = self.date_format == DATE_FORMAT
+ highlights = self.get_highlights_from_data(data, path)
+ for i in sorted(highlights, key=self.sort_high4view):
+ chapter_text = (f"[{i['chapter']}]\n"
+ if (i["chapter"] and self.status.act_chapter.isChecked())
+ else "")
+ page_text = (_(f"Page {i['page']}")
+ if self.status.act_page.isChecked() else "")
+ date = i["date"] if def_date_format else self.get_date_text(i["date"])
+ date_text = "[" + date + "]" if self.status.act_date.isChecked() else ""
+ high_text = i["text"] if self.status.act_text.isChecked() else ""
+ line_break2 = ("\n" if self.status.act_comment.isChecked() and i["comment"]
+ else "")
+ high_comment = line_break2 + "● " + i["comment"] if line_break2 else ""
+ highlight = (page_text + space + date_text + line_break + chapter_text +
+ high_text + high_comment + "\n")
- # needed for edit "Comments" or "Find in Books" in Highlight View
- for row in range(self.file_table.rowCount()): # 2check: need to optimize?
- if path == self.file_table.item(row, TYPE).data(Qt.UserRole)[0]:
- self.parent_book_data = self.file_table.item(row, TITLE).data(Qt.UserRole)
- break
+ highlight_item = QListWidgetItem(highlight, self.high_list)
+ highlight_item.setData(Qt.UserRole, i)
- @Slot(QModelIndex)
- def on_high_table_doubleClicked(self, index):
- """ When an item of the high_table is double-clicked
+ def get_highlights_from_data(self, data, path="", meta_path=""):
+ """ Get the HighLights from the .sdr data
- :type index: QTableWidgetItem
- :param index: The item (cell) that is clicked
+ :type data: dict
+ :param data: The lua converted book data
+ :type path: str|unicode
+ :param path: The book's path
+ :type meta_path: str|unicode
+ :param meta_path: The book metadata file's path
"""
- column = index.column()
- if column == COMMENT_H:
- self.on_edit_comment()
+ stats = "doc_props" if "doc_props" in data else "stats" if "stats" in data else ""
+ authors = data.get(stats, {}).get("authors", NO_AUTHOR)
+ title = data.get(stats, {}).get("title", NO_TITLE)
+ common = {"authors": authors, "title": title,
+ "path": path, "meta_path": meta_path}
- @Slot(QPoint)
- def on_high_table_customContextMenuRequested(self, point):
- """ When an item of the high_table is right-clicked
+ highlights = []
+ annotations = data.get("annotations")
+ if annotations is not None: # new format metadata
+ for idx in annotations:
+ highlight = self.get_new_highlight_info(data, idx)
+ if highlight:
+ # noinspection PyTypeChecker
+ highlight.update(common)
+ highlights.append(highlight)
+ else:
+ for page in data["highlight"]:
+ for page_id in data["highlight"][page]:
+ highlight = self.get_old_highlight_info(data, page, page_id)
+ if highlight:
+ # noinspection PyTypeChecker
+ highlight.update(common)
+ highlights.append(highlight)
+ return highlights
- :type point: QPoint
- :param point: The point where the right-click happened
- """
- if not len(self.sel_high_view): # no items selected
- return
+ @staticmethod
+ def get_new_highlight_info(data, idx):
+ """ Get the highlight's info (text, comment, date and page) [new format]
- menu = QMenu(self.high_table)
- if QT6: # QT6 requires exec() instead of exec_()
- menu.exec_ = getattr(menu, "exec")
+ :type data: dict
+ :param data: The book's metadata
+ :type idx: int
+ :param idx: The highlight's idx
+ """
+ high_stuff = data["annotations"][idx]
+ if not high_stuff.get("pos0"):
+ return # this is a bookmark not a highlight
+ pages = data["doc_pages"]
+ page = high_stuff.get("pageno", 0)
+ highlight = {"text": high_stuff.get("text", "").replace("\\\n", "\n"),
+ "chapter": high_stuff.get("chapter", ""),
+ "comment": high_stuff.get("note", "").replace("\\\n", "\n"),
+ "date": high_stuff.get("datetime", ""), "idx": idx,
+ "page": page, "pages": pages, "new": True}
+ return highlight
- row = self.high_table.itemAt(point).row()
- self.act_view_book.setData(row)
- self.act_view_book.setEnabled(self.toolbar.open_btn.isEnabled())
- menu.addAction(self.act_view_book)
+ @staticmethod
+ def get_old_highlight_info(data, page, page_id):
+ """ Get the highlight's info (text, comment, date and page)
- highlights, comments = self.get_highlights()
+ :type data: dict
+ :param data: The highlight's data
+ :type page: int
+ :param page The page where the highlight starts
+ :type page_id: int
+ :param page_id The count of this page's highlight
+ """
+ pages = data.get("doc_pages", data.get("stats", {}).get("pages", 0))
+ highlight = {"page": str(page), "page_id": page_id, "pages": pages, "new": False}
+ try:
+ high_stuf = data["highlight"][page][page_id]
+ date = high_stuf["datetime"]
+ text4check = high_stuf["text"]
+ chapter = high_stuf.get("chapter", "")
+ pat = r"Page \d+ (.+?) @ \d+-\d+-\d+ \d+:\d+:\d+"
+ text = text4check.replace("\\\n", "\n")
+ comment = ""
+ for idx in data["bookmarks"]: # check for comment text
+ if text4check == data["bookmarks"][idx]["notes"]:
+ bkm_text = data["bookmarks"][idx].get("text", "")
+ if not bkm_text or (bkm_text == text4check):
+ break
+ bkm_text = re.sub(pat, r"\1", bkm_text, 1, re.DOTALL | re.MULTILINE)
+ if text4check != bkm_text: # there is a comment
+ comment = bkm_text.replace("\\\n", "\n")
+ break
+ highlight["date"] = date
+ highlight["text"] = text
+ highlight["comment"] = comment
+ highlight["chapter"] = chapter
+ except KeyError: # blank highlight
+ return
+ return highlight
- high_text = _("Copy Highlights")
- com_text = _("Copy Comments")
- if len(self.sel_high_view) == 1: # single selection
- high_text = _("Copy Highlight")
- com_text = _("Copy Comment")
+ @Slot(QPoint)
+ def on_high_list_customContextMenuRequested(self, point):
+ """ When a highlight is right-clicked
- text = _("Find in Archive") if self.db_mode else _("Find in Books")
- action = QAction(text, menu)
- action.triggered.connect(partial(self.find_in_books, highlights))
- action.setIcon(self.ico_view_books)
- menu.addAction(action)
+ :type point: QPoint
+ :param point: The point where the right-click happened
+ """
+ if self.sel_high_list:
+ menu = QMenu(self.high_list)
+ if QT6: # QT6 requires exec() instead of exec_()
+ menu.exec_ = getattr(menu, "exec")
- action = QAction(_("Comments"), menu)
+ action = QAction(_("Comment"), menu)
action.triggered.connect(self.on_edit_comment)
action.setIcon(self.ico_file_edit)
menu.addAction(action)
- action = QAction(high_text + "\tCtrl+C", menu)
- action.triggered.connect(partial(self.copy_text_2clip, highlights))
- action.setIcon(self.ico_copy)
- menu.addAction(action)
+ action = QAction(_("Copy"), menu)
+ action.triggered.connect(self.on_copy_highlights)
+ action.setIcon(self.ico_copy)
+ menu.addAction(action)
- action = QAction(com_text + "\tAlt+C", menu)
- action.triggered.connect(partial(self.copy_text_2clip, comments))
- action.setIcon(self.ico_copy)
- menu.addAction(action)
+ menu.addSeparator()
- action = QAction(_("Export to file"), menu)
- action.triggered.connect(self.on_export)
- action.setData(2)
- action.setIcon(self.ico_file_save)
- menu.addAction(action)
+ action = QAction(_("Delete"), menu)
+ action.triggered.connect(self.on_delete_highlights)
+ action.setIcon(self.ico_delete)
+ menu.addAction(action)
- menu.exec_(self.high_table.mapToGlobal(point))
+ menu.exec_(self.high_list.mapToGlobal(point))
- def get_highlights(self):
- """ Returns the selected highlights and the comments texts
+ @Slot()
+ def on_high_list_itemDoubleClicked(self):
+ """ An item on the Highlight List is double-clicked
"""
- highlights = ""
- comments = ""
- for idx in self.sel_high_view:
- item_row = idx.row()
- data = self.high_table.item(item_row, HIGHLIGHT_H).data(Qt.UserRole)
- highlight = data["text"]
- if highlight:
- highlights += highlight + "\n\n"
- comment = data["comment"]
- if comment:
- comments += comment + "\n\n"
- highlights = highlights.rstrip("\n").replace("\n", os.linesep)
- comments = comments.rstrip("\n").replace("\n", os.linesep)
+ self.on_edit_comment()
+
+ def on_edit_comment(self):
+ """ Opens a window to edit the selected highlight's comment
+ """
+ if self.file_table.isVisible(): # edit comments from Book View
+ row = self.sel_high_list[-1].row()
+ comment = self.high_list.item(row).data(Qt.UserRole)["comment"]
+ elif self.high_table.isVisible(): # edit comments from Highlights View
+ row = self.sel_high_view[-1].row()
+ high_data = self.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)
+ comment = high_data["comment"]
+ else:
+ return
+ self.edit_high.high_edit_txt.setText(comment)
+ # self.edit_high.high_edit_txt.setFocus()
+ if QT6: # QT6 requires exec() instead of exec_()
+ self.edit_high.exec_ = getattr(self.edit_high, "exec")
+ self.edit_high.exec_()
+
+ def edit_comment_ok(self):
+ """ Change the selected highlight's comment
+ """
+ note = self.edit_high.high_edit_txt.toPlainText()
+ if self.file_table.isVisible(): # update comment from Book table
+ high_row = self.sel_high_list[-1].row()
+ high_data = self.high_list.item(high_row).data(Qt.UserRole)
+ item = self.file_table.item
+ row = self.sel_idx.row()
+ data = item(row, TITLE).data(Qt.UserRole)
+ self.update_comment(data, high_data, note)
+
+ if not self.db_mode: # Loaded mode
+ path = item(row, PATH).text()
+ self.save_book_data(path, data)
+ else: # Archived mode
+ self.update_book2db(data)
+ self.on_file_table_itemClicked(item(row, 0), reset=False)
+
+ elif self.high_table.isVisible(): # update comment from Highlights table
+ row = self.sel_high_view[-1].row()
+ high_data = self.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)
+ meta_path = high_data["meta_path"]
+ data = self.get_parent_book_data(row)[0]
+
+ self.update_comment(data, high_data, note)
+ self.high_table.item(row, HIGHLIGHT_H).setData(Qt.UserRole, high_data)
+ self.high_table.item(row, COMMENT_H).setText(note)
+
+ if not self.db_mode: # Loaded mode
+ self.save_book_data(meta_path, data)
+ else: # Archived mode
+ self.update_book2db(data)
+
+ self.reload_highlights = True
+
+ @staticmethod
+ def update_comment(data, high_data, note):
+ """ Update the comment of the selected highlight
+ """
+ date = datetime.now().strftime(DATE_FORMAT)
+ high_text = high_data["text"].replace("\n", "\\\n")
+ annotations = data.get("annotations")
+ if annotations is not None: # new format metadata
+ for idx in annotations:
+ if high_text == annotations[idx]["text"]:
+ annotations[idx]["note"] = note.replace("\n", "\\\n")
+ annotations[idx]["datetime"] = date # update last edit date
+ high_data["comment"] = note
+ break
+ else: # old format metadata
+ for bkm in data["bookmarks"]:
+ if high_text == data["bookmarks"][bkm]["notes"]:
+ high_data["comment"] = note
+ data["bookmarks"][bkm]["text"] = note.replace("\n", "\\\n")
+ data["bookmarks"][bkm]["datetime"] = date # update bkm edit date
+ for pg in data["highlight"]: # and highlight's too
+ for pg_id in data["highlight"][pg]:
+ if data["highlight"][pg][pg_id]["text"] == high_text:
+ data["highlight"][pg][pg_id]["datetime"] = date
+ break
+
+ def on_copy_highlights(self):
+ """ Copy the selected highlights to clipboard
+ """
+ clipboard_text = ""
+ for highlight in sorted(self.sel_high_list):
+ row = highlight.row()
+ text = self.high_list.item(row).text()
+ clipboard_text += text + "\n"
+ self.copy_text_2clip(clipboard_text)
+
+ def on_delete_highlights(self):
+ """ The delete highlights action was invoked
+ """
+ if not self.db_mode:
+ if self.edit_lua_file_warning:
+ text = _("This is an one-time warning!\n\nIn order to delete highlights "
+ "from a book, its \"metadata\" file must be edited. This "
+ "contains a small risk of corrupting that file and lose all the "
+ "settings and info of that book.\n\nDo you still want to do it?")
+ popup = self.popup(_("Warning!"), text, buttons=2,
+ button_text=(_("Yes"), _("No")))
+ if popup.buttonRole(popup.clickedButton()) == QMessageBox.RejectRole:
+ return
+ else:
+ self.edit_lua_file_warning = False
+ text = _("This will delete the selected highlights!\nAre you sure?")
+ else:
+ text = _("This will remove the selected highlights from the Archive!\n"
+ "Are you sure?")
+ popup = self.popup(_("Warning!"), text, buttons=2,
+ button_text=(_("Yes"), _("No")))
+ if popup.buttonRole(popup.clickedButton()) == QMessageBox.RejectRole:
+ return
+ self.delete_highlights()
+
+ def delete_highlights(self):
+ """ Delete the selected highlights
+ """
+ if self.file_table.isVisible(): # delete comments from Book table
+ row = self.sel_idx.row()
+ data = self.file_table.item(row, TITLE).data(Qt.UserRole)
+ hi_count = int(self.file_table.item(row, HIGH_COUNT).text())
+ annotations = data.get("annotations")
+ if annotations is not None: # new format metadata
+ for high in self.sel_high_list:
+ high_data = self.high_list.item(high.row()).data(Qt.UserRole)
+ idx = high_data["idx"]
+ del annotations[idx] # delete the highlight
+ hi_count -= 1
+ self.finalize_new_highs(data)
+ else: # old format metadata
+ for high in self.sel_high_list:
+ high_data = self.high_list.item(high.row()).data(Qt.UserRole)
+ self.del_old_high(data, high_data)
+ hi_count -= 1
+ self.finalize_old_highs(data)
+ self.update_and_save_meta(row, data, hi_count)
+ self.reload_highlights = True
+ elif self.high_table.isVisible(): # delete comments from Highlights table
+ hi2del = {}
+ idx2del = []
+ for hi_idx in self.sel_high_view: # collect the data from the highlights
+ row = hi_idx.row()
+ high_data = self.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)
+ data = self.get_parent_book_data(row)[0]
+ meta_path = high_data["meta_path"]
+ idx2del.append(row)
+ data_list = hi2del.get(meta_path, [])
+ data_list.append((data, high_data, row))
+ hi2del[meta_path] = data_list
+
+ for meta_path, data_list in hi2del.items():
+ book_row = 0
+ for book_row in range(self.file_table.rowCount()):
+ if self.file_table.item(book_row, PATH).text() == meta_path:
+ break
+ hi_count = self.file_table.item(book_row, HIGH_COUNT).text()
+ hi_count = int(hi_count) if hi_count else 0
+ new_format = False
+ data = None
+ for data, high_data, row in data_list:
+ annotations = data.get("annotations")
+ if annotations is not None: # new format metadata
+ new_format = True
+ idx = high_data["idx"]
+ del annotations[idx] # delete the highlight
+ hi_count -= 1
+ else: # old format metadata
+ self.del_old_high(data, high_data)
+ hi_count -= 1
+ if new_format:
+ self.finalize_new_highs(data)
+ else:
+ self.finalize_old_highs(data) # data is same for same meta_path
+ self.update_and_save_meta(book_row, data, hi_count)
+
+ idx2del = sorted(idx2del, reverse=True)
+ for hi_idx in idx2del:
+ self.high_table.removeRow(hi_idx)
+
+ @staticmethod
+ def finalize_new_highs(data):
+ annotations = data.get("annotations")
+ new_annot = {idx + 1: annotations[i] # renumbering the annotations
+ for idx, i in enumerate(sorted(annotations))}
+ if new_annot:
+ data["annotations"] = new_annot
+
+ @staticmethod
+ def del_old_high(data, hi_data):
+ page = int(hi_data["page"])
+ page_id = hi_data["page_id"]
+ del data["highlight"][page][page_id] # delete the highlight
+ text = hi_data["text"] # delete the associated bookmark
+ for bookmark in list(data["bookmarks"].keys()):
+ if text == data["bookmarks"][bookmark]["notes"]:
+ del data["bookmarks"][bookmark]
+
+ @staticmethod
+ def finalize_old_highs(data):
+ for i in list(data["highlight"].keys()):
+ if not data["highlight"][i]: # delete page dicts with no highlights
+ del data["highlight"][i]
+ else: # renumbering the highlight keys
+ contents = [data["highlight"][i][j] for j in sorted(data["highlight"][i])]
+ if contents:
+ for l in list(data["highlight"][i].keys()):
+ del data["highlight"][i][l] # delete all the items and
+ for k in range(len(contents)): # rewrite with the new keys
+ data["highlight"][i][k + 1] = contents[k]
+ contents = [data["bookmarks"][bookmark] for bookmark in sorted(data["bookmarks"])]
+ if contents: # renumbering the bookmarks keys
+ for bookmark in list(data["bookmarks"].keys()):
+ del data["bookmarks"][bookmark] # delete all the items and
+ for content in range(len(contents)): # rewrite them with the new keys
+ data["bookmarks"][content + 1] = contents[content]
+
+ def update_and_save_meta(self, row, data, hi_count):
+ if not hi_count: # change icon if no highlights
+ self.file_table.item(row, TITLE).setIcon(self.ico_empty)
+ hi_count = ""
+ self.file_table.item(row, HIGH_COUNT).setText(str(hi_count))
+ self.file_table.item(row, HIGH_COUNT).setToolTip(str(hi_count))
+ if not self.db_mode:
+ path = self.file_table.item(row, PATH).text()
+ data["annotations_externally_modified"] = True
+ self.save_book_data(path, data)
+ else:
+ self.update_book2db(data)
+ self.on_file_table_itemClicked(self.file_table.item(row, TITLE), reset=False)
+
+ def save_book_data(self, path, data):
+ """ Saves the data of a book to its lua file
+
+ :type path: str|unicode
+ :param path: The path to the book's data file
+ :type data: dict
+ :param data: The book's data
+ """
+ times = os.stat(path) # read the file's created/modified times
+ encode_data(path, data)
+ if data.get("summary", {}).get("status") in ["abandoned", "complete"]:
+ os.utime(path, (times.st_ctime, times.st_mtime)) # reapply original times
+ if self.file_table.isVisible():
+ self.on_file_table_itemClicked(self.file_table.item(self.sel_idx.row(), 0),
+ reset=False)
+
+ # noinspection PyUnusedLocal
+ def high_list_selection_update(self, selected, deselected):
+ """ When a highlight in gets selected
+
+ :type selected: QModelIndex
+ :parameter selected: The selected highlight
+ :type deselected: QModelIndex
+ :parameter deselected: The deselected highlight
+ """
+ self.sel_high_list = self.high_list_selection.selectedRows()
+
+ def set_highlight_sort(self):
+ """ Sets the sorting method of displayed highlights
+ """
+ self.high_by_page = self.sender().data()
+ try:
+ row = self.sel_idx.row()
+ self.on_file_table_itemClicked(self.file_table.item(row, 0), False)
+ except AttributeError: # no book selected
+ pass
+
+ def sort_high4view(self, data):
+ """ Sets the sorting method of displayed highlights
+
+ :type data: tuple
+ param: data: The highlight's data
+ """
+ if not self.high_by_page:
+ return data["date"]
+ else:
+ return int(data["page"])
+
+ def sort_high4write(self, data):
+ """ Sets the sorting method of written highlights
+
+ :type data: tuple
+ param: data: The highlight's data
+ """
+ if self.high_by_page and self.status.act_page.isChecked():
+ page = data[3]
+ if page.startswith("Page"):
+ page = page[5:]
+ return int(page)
+ else:
+ return data[0]
+
+ # ___ ___________________ HIGHLIGHT TABLE STUFF _________________
+
+ @Slot(QTableWidgetItem)
+ def on_high_table_itemClicked(self, item):
+ """ When an item of the high_table is clicked
+
+ :type item: QTableWidgetItem
+ :param item: The item (cell) that is clicked
+ """
+ # row = item.row()
+ # self.get_parent_book_data(row)
+
+ def get_parent_book_data(self, row):
+ meta_path = self.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)["meta_path"]
+ for row in range(self.file_table.rowCount()): # 2check: need to optimize?
+ if meta_path == self.file_table.item(row, PATH).data(0):
+ parent_book_data = self.file_table.item(row, TITLE).data(Qt.UserRole)
+ return parent_book_data, meta_path
+
+ @Slot(QModelIndex)
+ def on_high_table_doubleClicked(self, index):
+ """ When an item of the high_table is double-clicked
+
+ :type index: QTableWidgetItem
+ :param index: The item (cell) that is clicked
+ """
+ column = index.column()
+ if column == COMMENT_H:
+ self.on_edit_comment()
+
+ @Slot(QPoint)
+ def on_high_table_customContextMenuRequested(self, point):
+ """ When an item of the high_table is right-clicked
+
+ :type point: QPoint
+ :param point: The point where the right-click happened
+ """
+ if not len(self.sel_high_view): # no items selected
+ return
+
+ menu = QMenu(self.high_table)
+ if QT6: # QT6 requires exec() instead of exec_()
+ menu.exec_ = getattr(menu, "exec")
+
+ row = self.high_table.itemAt(point).row()
+ self.act_view_book.setData(row)
+ self.act_view_book.setEnabled(self.toolbar.open_btn.isEnabled())
+ menu.addAction(self.act_view_book)
+
+ highlights, comments = self.get_highlights()
+
+ high_text = _("Copy Highlights")
+ com_text = _("Copy Comments")
+ if len(self.sel_high_view) == 1: # single selection
+ high_text = _("Copy Highlight")
+ com_text = _("Copy Comment")
+
+ text = _("Find in Archive") if self.db_mode else _("Find in Books")
+ action = QAction(text, menu)
+ action.triggered.connect(partial(self.find_in_books, highlights, row))
+ action.setIcon(self.ico_view_books)
+ menu.addAction(action)
+
+ action = QAction(_("Comments"), menu)
+ action.triggered.connect(self.on_edit_comment)
+ action.setIcon(self.ico_file_edit)
+ menu.addAction(action)
+
+ action = QAction(high_text + "\tCtrl+C", menu)
+ action.triggered.connect(partial(self.copy_text_2clip, highlights))
+ action.setIcon(self.ico_copy)
+ menu.addAction(action)
+
+ action = QAction(com_text + "\tAlt+C", menu)
+ action.triggered.connect(partial(self.copy_text_2clip, comments))
+ action.setIcon(self.ico_copy)
+ menu.addAction(action)
+
+ action = QAction(_("Export to file"), menu)
+ action.triggered.connect(self.on_export)
+ action.setData(2)
+ action.setIcon(self.ico_file_save)
+ menu.addAction(action)
+
+ menu.addSeparator()
+ action = QAction(_("Delete") + "\tDel", menu)
+ action.setIcon(self.ico_files_delete)
+ action.triggered.connect(self.toolbar.on_delete_btn_clicked)
+ menu.addAction(action)
+
+ menu.exec_(self.high_table.mapToGlobal(point))
+
+ def get_highlights(self):
+ """ Returns the selected highlights and the comments texts
+ """
+ highlights = ""
+ comments = ""
+ for idx in self.sel_high_view:
+ item_row = idx.row()
+ data = self.high_table.item(item_row, HIGHLIGHT_H).data(Qt.UserRole)
+ highlight = data["text"]
+ if highlight:
+ highlights += highlight + "\n\n"
+ comment = data["comment"]
+ if comment:
+ comments += comment + "\n\n"
+ highlights = highlights.rstrip("\n").replace("\n", os.linesep)
+ comments = comments.rstrip("\n").replace("\n", os.linesep)
return highlights, comments
def scan_highlights_thread(self):
@@ -1173,13 +1797,13 @@ def create_highlight_row(self, data):
text = data["text"]
item = QTableWidgetItem(text)
- item.setToolTip("{}
".format(text))
+ item.setToolTip(f"{text}
")
item.setData(Qt.UserRole, data)
self.high_table.setItem(0, HIGHLIGHT_H, item)
comment = data["comment"]
item = QTableWidgetItem(comment)
- item.setToolTip("{}
".format(comment)) if comment else None
+ item.setToolTip(f"{comment}
") if comment else None
self.high_table.setItem(0, COMMENT_H, item)
date = data["date"]
@@ -1207,434 +1831,311 @@ def create_highlight_row(self, data):
chapter = data["chapter"]
item = XTableWidgetIntItem(chapter)
item.setToolTip(chapter)
- self.high_table.setItem(0, CHAPTER_H, item)
-
- path = data["path"]
- item = QTableWidgetItem(path)
- item.setToolTip(path)
- self.high_table.setItem(0, PATH_H, item)
-
- self.high_table.setSortingEnabled(True)
-
- # noinspection PyUnusedLocal
- def high_view_selection_update(self, selected, deselected):
- """ When a row in high_table gets selected
-
- :type selected: QModelIndex
- :parameter selected: The selected row
- :type deselected: QModelIndex
- :parameter deselected: The deselected row
- """
- try:
- if not self.filter.isVisible():
- self.sel_high_view = self.high_view_selection.selectedRows()
- else:
- self.sel_high_view = [i for i in self.high_view_selection.selectedRows()
- if not self.high_table.isRowHidden(i.row())]
- except IndexError: # empty table
- self.sel_high_view = []
- self.toolbar.activate_buttons()
-
- def on_highlight_column_clicked(self, column):
- """ Sets the current sorting column
-
- :type column: int
- :parameter column: The column where the sorting is applied
- """
- if column == self.col_sort_h:
- self.col_sort_asc_h = not self.col_sort_asc_h
- else:
- self.col_sort_asc_h = True
- self.col_sort_h = column
-
- # noinspection PyUnusedLocal
- def on_highlight_column_resized(self, column, oldSize, newSize):
- """ Gets the column size
-
- :type column: int
- :parameter column: The resized column
- :type oldSize: int
- :parameter oldSize: The old size
- :type newSize: int
- :parameter newSize: The new size
- """
- if column == HIGHLIGHT_H:
- self.highlight_width = newSize
- elif column == COMMENT_H:
- self.comment_width = newSize
-
- def find_in_books(self, highlight):
- """ Finds the current highlight in the "Books View"
-
- :type highlight: str|unicode
- :parameter highlight: The highlight we are searching for
- """
- data = self.parent_book_data
-
- for row in range(self.file_table.rowCount()):
- item = self.file_table.item(row, TITLE)
- row_data = item.data(Qt.UserRole)
- try: # find the book row
- if data["stats"]["title"] == row_data["stats"]["title"]:
- self.views.setCurrentIndex(BOOKS_VIEW)
- self.toolbar.books_view_btn.setChecked(True)
- self.toolbar.setup_buttons()
- self.toolbar.activate_buttons()
- self.file_table.selectRow(row) # select the book
- self.on_file_table_itemClicked(item)
- for high_row in range(self.high_list.count()): # find the highlight
- if (self.high_list.item(high_row)
- .data(Qt.UserRole)["text"] == highlight):
- self.high_list.setCurrentRow(high_row) # select the highlight
- return
- except KeyError: # old metadata with no "stats"
- continue
-
- # ___ ___________________ HIGHLIGHTS LIST STUFF _________________
-
- def populate_high_list(self, data, path=""):
- """ Populates the Highlights list of `Book` view
-
- :type data: dict
- :param data: The item/book's data
- :type path: str|unicode
- :param path: The item/book's path
- """
- space = (" " if self.status.act_page.isChecked() and
- self.status.act_date.isChecked() else "")
- line_break = (":\n" if self.status.act_page.isChecked() or
- self.status.act_date.isChecked() else "")
- def_date_format = self.date_format == DATE_FORMAT
- highlights = self.get_highlights_from_data(data, path)
- for i in sorted(highlights, key=self.sort_high4view):
- chapter_text = i["chapter"]
- if chapter_text and self.status.act_chapter.isChecked():
- chapter_text = "[{0}]\n".format(chapter_text)
- page_text = (_("Page ") + i["page"]
- if self.status.act_page.isChecked() else "")
- date = i["date"] if def_date_format else self.get_date_text(i["date"])
- date_text = "[" + date + "]" if self.status.act_date.isChecked() else ""
- high_text = i["text"] if self.status.act_text.isChecked() else ""
- line_break2 = ("\n" if self.status.act_comment.isChecked() and i["comment"]
- else "")
- high_comment = line_break2 + "● " + i["comment"] if line_break2 else ""
- highlight = (page_text + space + date_text + line_break + chapter_text +
- high_text + high_comment + "\n")
-
- highlight_item = QListWidgetItem(highlight, self.high_list)
- highlight_item.setData(Qt.UserRole, i)
-
- def get_highlights_from_data(self, data, path=""):
- """ Get the HighLights from the .sdr data
-
- :type data: dict
- :param data: The lua converted book data
- :type path: str|unicode
- :param path: The book's path
- """
- authors = data.get("stats", {}).get("authors", NO_AUTHOR)
- title = data.get("stats", {}).get("title", NO_TITLE)
-
- highlights = []
- for page in data["highlight"]:
- for page_id in data["highlight"][page]:
- highlight = self.get_highlight_info(data, page, page_id)
- if highlight:
- # noinspection PyTypeChecker
- highlight.update({"authors": authors, "title": title,
- "path": path})
- highlights.append(highlight)
- return highlights
-
- @staticmethod
- def get_highlight_info(data, page, page_id):
- """ Get the highlight's info (text, comment, date and page)
-
- :type data: dict
- :param data: The highlight's data
- :type page: int
- :param page The page where the highlight starts
- :type page_id: int
- :param page_id The count of this page's highlight
- """
- highlight = {}
- try:
- high_stuf = data["highlight"][page][page_id]
- date = high_stuf["datetime"]
- text4check = high_stuf["text"]
- chapter = high_stuf.get("chapter", "")
- pat = r"Page \d+ (.+?) @ \d+-\d+-\d+ \d+:\d+:\d+"
- text = text4check.replace("\\\n", "\n")
- comment = ""
- for idx in data["bookmarks"]: # check for comment text
- if text4check == data["bookmarks"][idx]["notes"]:
- bkm_text = data["bookmarks"][idx].get("text", "")
- if not bkm_text or (bkm_text == text4check):
- break
- bkm_text = re.sub(pat, r"\1", bkm_text, 1, re.DOTALL | re.MULTILINE)
- if text4check != bkm_text: # there is a comment
- comment = bkm_text.replace("\\\n", "\n")
- break
- highlight["date"] = date
- highlight["text"] = text
- highlight["comment"] = comment
- highlight["chapter"] = chapter
- highlight["page"] = str(page)
- highlight["page_id"] = page_id
- except KeyError: # blank highlight
- return
- return highlight
-
- @Slot(QPoint)
- def on_high_list_customContextMenuRequested(self, point):
- """ When a highlight is right-clicked
-
- :type point: QPoint
- :param point: The point where the right-click happened
- """
- if self.sel_high_list:
- menu = QMenu(self.high_list)
- if QT6: # QT6 requires exec() instead of exec_()
- menu.exec_ = getattr(menu, "exec")
-
- action = QAction(_("Comments"), menu)
- action.triggered.connect(self.on_edit_comment)
- action.setIcon(self.ico_file_edit)
- menu.addAction(action)
-
- action = QAction(_("Copy"), menu)
- action.triggered.connect(self.on_copy_highlights)
- action.setIcon(self.ico_copy)
- menu.addAction(action)
-
- action = QAction(_("Delete"), menu)
- action.triggered.connect(self.on_delete_highlights)
- action.setIcon(self.ico_delete)
- menu.addAction(action)
-
- menu.exec_(self.high_list.mapToGlobal(point))
-
- @Slot()
- def on_high_list_itemDoubleClicked(self):
- """ An item on the Highlight List is double-clicked
- """
- self.on_edit_comment()
-
- def on_edit_comment(self):
- """ Opens a window to edit the selected highlight's comment
- """
- if self.file_table.isVisible(): # edit comments from Book View
- row = self.sel_high_list[-1].row()
- comment = self.high_list.item(row).data(Qt.UserRole)["comment"]
- elif self.high_table.isVisible(): # edit comments from Highlights View
- row = self.sel_high_view[-1].row()
- high_data = self.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)
- comment = high_data["comment"]
- else:
- return
- self.edit_high.high_edit_txt.setText(comment)
- # self.edit_high.high_edit_txt.setFocus()
- self.edit_high.exec_()
-
- def edit_comment_ok(self):
- """ Change the selected highlight's comment
- """
- text = self.edit_high.high_edit_txt.toPlainText()
- if self.file_table.isVisible():
- high_index = self.sel_high_list[-1]
- high_row = high_index.row()
- high_data = self.high_list.item(high_row).data(Qt.UserRole)
- high_text = high_data["text"].replace("\n", "\\\n")
-
- row = self.sel_idx.row()
- item = self.file_table.item
- data = item(row, TITLE).data(Qt.UserRole)
-
- for bookmark in data["bookmarks"].keys():
- if high_text == data["bookmarks"][bookmark]["notes"]:
- data["bookmarks"][bookmark]["text"] = text.replace("\n", "\\\n")
- break
- item(row, TITLE).setData(Qt.UserRole, data)
-
- if not self.db_mode: # Loaded mode
- path = item(row, PATH).text()
- self.save_book_data(path, data)
- else: # Archived mode
- self.update_book2db(data)
- self.on_file_table_itemClicked(item(row, 0), reset=False)
-
- elif self.high_table.isVisible():
- data = self.parent_book_data
- row = self.sel_high_view[-1].row()
- high_data = self.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)
- high_text = high_data["text"].replace("\n", "\\\n")
+ self.high_table.setItem(0, CHAPTER_H, item)
- for bookmark in data["bookmarks"].keys():
- if high_text == data["bookmarks"][bookmark]["notes"]:
- data["bookmarks"][bookmark]["text"] = text.replace("\n", "\\\n")
- high_data["comment"] = text
- break
- self.high_table.item(row, HIGHLIGHT_H).setData(Qt.UserRole, high_data)
- self.high_table.item(row, COMMENT_H).setText(text)
+ # path = data["path"]
+ path = data.get("meta_path", "")
+ item = QTableWidgetItem(path)
+ item.setToolTip(path)
+ self.high_table.setItem(0, PATH_H, item)
- if not self.db_mode: # Loaded mode
- book_path, ext = splitext(high_data["path"])
- path = join(book_path + ".sdr", "metadata{}.lua".format(ext))
- self.save_book_data(path, data)
- else: # Archived mode
- self.update_book2db(data)
- path = self.high_table.item(row, PATH_H).text()
- for row in range(self.file_table.rowCount()):
- if path == self.file_table.item(row, TYPE).data(Qt.UserRole)[0]:
- self.file_table.item(row, TITLE).setData(Qt.UserRole, data)
- break
+ self.high_table.setSortingEnabled(True)
- self.reload_highlights = True
+ # noinspection PyUnusedLocal
+ def high_view_selection_update(self, selected, deselected):
+ """ When a row in high_table gets selected
- def on_copy_highlights(self):
- """ Copy the selected highlights to clipboard
+ :type selected: QModelIndex
+ :parameter selected: The selected row
+ :type deselected: QModelIndex
+ :parameter deselected: The deselected row
"""
- clipboard_text = ""
- for highlight in sorted(self.sel_high_list):
- row = highlight.row()
- text = self.high_list.item(row).text()
- clipboard_text += text + "\n"
- self.copy_text_2clip(clipboard_text)
+ try:
+ if not self.filter.isVisible():
+ self.sel_high_view = self.high_view_selection.selectedRows()
+ else:
+ self.sel_high_view = [i for i in self.high_view_selection.selectedRows()
+ if not self.high_table.isRowHidden(i.row())]
+ except IndexError: # empty table
+ self.sel_high_view = []
+ self.toolbar.activate_buttons()
- def on_delete_highlights(self):
- """ The delete highlights action was invoked
+ def on_highlight_column_clicked(self, column):
+ """ Sets the current sorting column
+
+ :type column: int
+ :parameter column: The column where the sorting is applied
"""
- if not self.db_mode:
- if self.edit_lua_file_warning:
- text = _("This is an one-time warning!\n\nIn order to delete highlights "
- "from a book, its \"metadata\" file must be edited. This "
- "contains a small risk of corrupting that file and lose all the "
- "settings and info of that book.\n\nDo you still want to do it?")
- popup = self.popup(_("Warning!"), text, buttons=2,
- button_text=(_("Yes"), _("No")))
- if popup.buttonRole(popup.clickedButton()) == QMessageBox.RejectRole:
- return
- else:
- self.edit_lua_file_warning = False
- text = _("This will delete the selected highlights!\nAre you sure?")
+ if column == self.col_sort_h:
+ self.col_sort_asc_h = not self.col_sort_asc_h
else:
- text = _("This will remove the selected highlights from the Archive!\n"
- "Are you sure?")
- popup = self.popup(_("Warning!"), text, buttons=2,
- button_text=(_("Yes"), _("No")))
- if popup.buttonRole(popup.clickedButton()) == QMessageBox.RejectRole:
- return
- self.delete_highlights()
+ self.col_sort_asc_h = True
+ self.col_sort_h = column
- def delete_highlights(self):
- """ Delete the selected highlights
+ # noinspection PyUnusedLocal
+ def on_highlight_column_resized(self, column, oldSize, newSize):
+ """ Gets the column size
+
+ :type column: int
+ :parameter column: The resized column
+ :type oldSize: int
+ :parameter oldSize: The old size
+ :type newSize: int
+ :parameter newSize: The new size
"""
- row = self.sel_idx.row()
- data = self.file_table.item(row, TITLE).data(Qt.UserRole)
+ if column == HIGHLIGHT_H:
+ self.highlight_width = newSize
+ elif column == COMMENT_H:
+ self.comment_width = newSize
- for highlight in self.sel_high_list:
- high_row = highlight.row()
- high_data = self.high_list.item(high_row).data(Qt.UserRole)
- pprint(high_data)
- page = high_data["page"]
- page_id = high_data["page_id"]
- del data["highlight"][page][page_id] # delete the highlight
-
- # delete the associated bookmark
- text = high_data["text"]
- for bookmark in data["bookmarks"].keys():
- if text == data["bookmarks"][bookmark]["notes"]:
- del data["bookmarks"][bookmark]
-
- for i in data["highlight"].keys():
- if not data["highlight"][i]: # delete page dicts with no highlights
- del data["highlight"][i]
- else: # renumbering the highlight keys
- contents = [data["highlight"][i][j] for j in sorted(data["highlight"][i])]
- if contents:
- for l in data["highlight"][i].keys(): # delete all the items and
- del data["highlight"][i][l]
- for k in range(len(contents)): # rewrite them with the new keys
- data["highlight"][i][k + 1] = contents[k]
+ def find_in_books(self, highlight, hi_row):
+ """ Finds the current highlight in the "Books View"
- contents = [data["bookmarks"][bookmark] for bookmark in sorted(data["bookmarks"])]
- if contents: # renumbering the bookmarks keys
- for bookmark in data["bookmarks"].keys(): # delete all the items and
- del data["bookmarks"][bookmark]
- for content in range(len(contents)): # rewrite them with the new keys
- data["bookmarks"][content + 1] = contents[content]
+ :type highlight: str|unicode
+ :parameter highlight: The highlight we are searching for
+ """
+ data, meta_path = self.get_parent_book_data(hi_row)
- if not data["highlight"]: # change icon if no highlights
- item = self.file_table.item(row, 0)
- item.setIcon(self.ico_empty)
+ for row in range(self.file_table.rowCount()):
+ item = self.file_table.item(row, TITLE)
+ row_meta_path = self.file_table.item(row, PATH).data(0)
+ try: # find the book row
+ if meta_path == row_meta_path:
+ self.toolbar.books_view_btn.click()
+ self.toolbar.setup_buttons()
+ self.toolbar.activate_buttons()
+ self.file_table.selectRow(row) # select the book
+ self.on_file_table_itemClicked(item)
+ for high_row in range(self.high_list.count()): # find the highlight
+ if (self.high_list.item(high_row)
+ .data(Qt.UserRole)["text"] == highlight):
+ self.high_list.setCurrentRow(high_row) # select the highlight
+ return
+ except KeyError: # old metadata with no "stats"
+ continue
- if not self.db_mode:
- path = self.file_table.item(row, PATH).text()
- self.save_book_data(path, data)
- else:
- self.update_book2db(data)
- item = self.file_table.item
- item(row, TITLE).setData(Qt.UserRole, data)
- self.on_file_table_itemClicked(item(row, 0), reset=False)
- self.reload_highlights = True
+ # ___ ___________________ SYNC GROUPS TABLE STUFF _______________
- def save_book_data(self, path, data):
- """ Saves the data of a book to its lua file
+ @Slot(QTableWidgetItem)
+ def on_sync_table_itemClicked(self, item):
+ """ When an item of the sync_table is clicked
- :type path: str|unicode
- :param path: The path to the book's data file
- :type data: dict
- :param data: The book's data
+ :type item: QTableWidgetItem
+ :param item: The item (cell) that is clicked
"""
- times = os.stat(path) # read the file's created/modified times
- encode_data(path, data)
- os.utime(path, (times.st_ctime, times.st_mtime)) # reapply original times
- if self.file_table.isVisible():
- self.on_file_table_itemClicked(self.file_table.item(self.sel_idx.row(), 0),
- reset=False)
+ # row = item.row()
+ # # path = self.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)["path"]
+ # print(row)
# noinspection PyUnusedLocal
- def high_list_selection_update(self, selected, deselected):
- """ When a highlight in gets selected
+ def sync_view_selection_update(self, selected, deselected):
+ """ When a row in sync_table gets selected
:type selected: QModelIndex
- :parameter selected: The selected highlight
+ :parameter selected: The selected row
:type deselected: QModelIndex
- :parameter deselected: The deselected highlight
- """
- self.sel_high_list = self.high_list_selection.selectedRows()
-
- def set_highlight_sort(self):
- """ Sets the sorting method of displayed highlights
+ :parameter deselected: The deselected row
"""
- self.high_by_page = self.sender().data()
try:
- row = self.sel_idx.row()
- self.on_file_table_itemClicked(self.file_table.item(row, 0), False)
- except AttributeError: # no book selected
- pass
+ self.sel_sync_view = self.sync_view_selection.selectedRows()
+ except IndexError: # empty table
+ self.sel_sync_view = []
+ self.toolbar.activate_buttons()
- def sort_high4view(self, data):
- """ Sets the sorting method of displayed highlights
+ def create_sync_row(self, data, quiet=False):
+ """ Creates a sync_table row from the given data
- :type data: tuple
- param: data: The highlight's data
+ :type data: dict|list
+ :param data: The sync_group data
+ :type quiet: bool
+ :param quiet: Switch to the Sync view
"""
- return int(data["page"]) if self.high_by_page else data["date"]
+ if self.current_view != SYNC_VIEW and not quiet:
+ self.toolbar.sync_view_btn.setChecked(True)
+ self.toolbar.change_view()
- def sort_high4write(self, data):
- """ Sets the sorting method of written highlights
+ self.sync_table.setSortingEnabled(False)
+ if isinstance(data, dict):
+ count = self.sync_table.rowCount()
+ self.sync_table.insertRow(count)
+ wdg = self.create_sync_widget(data)
+ wdg.idx = count
+ self.sync_groups.append(data)
+ self.sync_table.setCellWidget(count, 0, wdg)
+ self.sync_table.setRowHeight(count, wdg.sizeHint().height())
+ wdg.check_data()
+ else:
+ for idx, data in enumerate(data):
+ self.sync_table.insertRow(idx)
+ wdg = self.create_sync_widget(data)
+ self.sync_table.setCellWidget(idx, 0, wdg)
+ self.sync_table.setRowHeight(idx, wdg.sizeHint().height())
+ wdg.on_power_btn_clicked(data.get("enabled", True))
+ wdg.idx = idx
+ self.sync_groups.append(data)
+ wdg.check_data()
+ self.sync_table.setSortingEnabled(True)
+ # noinspection PyTypeChecker
+ QTimer.singleShot(0, self.save_sync_groups)
+
+ def create_sync_widget(self, data):
+ wdg = SyncGroup(self)
+ wdg.data = data
+ wdg.power_btn.setChecked(data.get("enabled", True))
+ wdg.title_lbl.setText(data.get("title", ""))
+ wdg.sync_pos_chk.setChecked(data.get("sync_pos", True))
+ wdg.merge_chk.setChecked(data.get("merge", True))
+ wdg.sync_db_chk.setChecked(data.get("sync_db", False))
+
+ items = deepcopy(data.get("items", [{"path": "", "data": {}}]))
+ for item in items:
+ wdg.add_item(item)
+ return wdg
+
+ def synchronize_group(self, group, multi=False):
+ """ Start the process of syncing/merging the group
+
+ :type group: SyncGroup
+ :param group: The group to be processed
+ """
+ group.on_refresh_btn_clicked()
+ if not group.sync_items[0].ok:
+ self.popup(_(f'Error in group "{group.data["title"]}"!'),
+ _("There is a problem with the first metadata file path!\nCheck "
+ "the Tooltip that appears while hovering the mouse over it."),
+ icon=QMessageBox.Critical)
+ return
+ sync2db = group.sync_db_chk.isChecked()
+ group_changed = sync2db
+ to_process = []
+ for idx, item in enumerate(group.sync_items):
+ if not item.ok:
+ continue
+ info = group.data["items"][idx]
+ mod_time = os.stat(info["path"]).st_mtime
+ to_process.append((info, mod_time))
+ to_process = sorted(to_process, key=lambda x: x[1], reverse=True)
+ to_process = [i[0] for i in to_process]
+
+ if sync2db:
+ book_data = {"data": group.data["items"][0]["data"],
+ "path": group.data["items"][0]["path"]}
+ db_idx = self.check4archive_merge(book_data)
+ if db_idx:
+ db_data = self.books[db_idx]["data"]
+ to_process.append({"data": db_data, "path": ""})
+
+ if len(to_process) > 1:
+ if group.sync_pos_chk.isChecked():
+ self.sync_pos(to_process)
+ group_changed = True
+ if group.merge_chk.isChecked():
+ items = []
+ for book_info in to_process: # book_info: {"path": str, "data": dict}
+ data = book_info["data"]
+ total = data.get("doc_pages", data.get("stats", {}).get("pages", 0))
+ if group.new_format:
+ items.append((data["annotations"], total))
+ else:
+ items.append((data["highlight"], data["bookmarks"], total,
+ book_info["path"]))
+ if group.new_format:
+ self.merge_new_highs(items)
+ else:
+ self.merge_old_highs(items)
+ group_changed = True
+
+ if group_changed:
+ for item in deepcopy(to_process):
+ if item["path"]: # db version has no path
+ item["data"]["annotations_externally_modified"] = True
+ self.save_book_data(item["path"], item["data"])
+
+ if sync2db: # add the newest version of the book to db
+ new_data = deepcopy(to_process[0])
+ path = new_data["path"]
+ data = new_data["data"]
+ date = str(datetime.fromtimestamp(getmtime(path))).split(".")[0]
+ md5 = data["partial_md5_checksum"]
+ self.delete_books_from_db([md5]) # remove the existing version if any
+ try: # clean up data that can be cluttered
+ new_data["stats"]["performance_in_pages"] = {}
+ new_data["page_positions"] = {}
+ new_data.pop("annotations_externally_modified", None)
+ except KeyError:
+ pass
+ self.add_books2db([{"md5": md5, "path": path, "date": date,
+ "data": json.dumps(data)}])
+ self.update_new_values(group)
+ text = _("Synchronization process completed")
+ else:
+ text = _("Nothing to sync")
- :type data: tuple
- param: data: The highlight's data
+ if not multi: # one group Sync
+ self.popup(_("Information"), text, QMessageBox.Information)
+ else: # multiple Sync operations
+ if group_changed:
+ return True # needed for counting the number of groups changed
+
+ def update_new_values(self, group):
+ """ Updates the book table after sync_groups execution
"""
- if self.high_by_page and self.status.act_page.isChecked():
- page = data[3]
- if page.startswith("Page"):
- page = page[5:]
- return int(page)
- else:
- return data[0]
+ self.reload_highlights = True
+ row_infos = {}
+ for idx, item in enumerate(group.data["items"]):
+ if not group.sync_items[idx].ok:
+ continue
+ path = item["path"]
+ for row in range(self.file_table.rowCount()):
+ if self.file_table.item(row, PATH).text() == path:
+ row_infos[row] = item
+ break
+ for row in row_infos:
+ data = row_infos[row]["data"]
+ self.file_table.item(row, TITLE).setData(Qt.UserRole, data)
+ hi_count = self.get_item_stats(data)["high_count"]
+ if hi_count and hi_count != "0":
+ self.file_table.item(row, TITLE).setIcon(self.ico_label_green)
+ self.file_table.item(row, HIGH_COUNT).setText(hi_count)
+ self.file_table.item(row, HIGH_COUNT).setToolTip(hi_count)
+ if group.sync_pos_chk.isChecked():
+ percent = str(int(data.get("percent_finished", 0) * 100)) + "%"
+ self.file_table.item(row, PERCENT).setText(percent)
+ self.file_table.item(row, PERCENT).setToolTip(percent)
+
+ def update_sync_groups(self):
+ """ Update the sync groups in memory and on disk
+ """
+ del self.sync_groups[:]
+ for i in range(self.sync_table.rowCount()):
+ wdg = self.sync_table.cellWidget(i, 0)
+ wdg.idx = i # update the index of widget
+ wdg.check_data()
+ self.sync_groups.append(wdg.data)
+
+ self.save_sync_groups()
+
+ def load_sync_groups(self):
+ """ Load the sync groups from a file
+ """
+ try:
+ with open(SYNC_FILE, "r", encoding="utf-8") as f:
+ sync_groups = json.load(f)
+ except (IOError, ValueError): # no json file exists or corrupted file
+ return
+ self.create_sync_row(sync_groups, quiet=True)
+
+ def save_sync_groups(self):
+ """ Save the sync groups to a file
+ """
+ sync_groups = deepcopy(self.sync_groups)
+ sync_groups = [i for i in sync_groups if i["items"][0].get("path")]
+ with open(SYNC_FILE, "w+", encoding="utf-8") as f:
+ items = [i for grp in sync_groups for i in grp["items"] if i.get("path")]
+ for item in items:
+ item["data"] = {}
+ item["path"] = normpath(item["path"])
+ json.dump(sync_groups, f, indent=4)
# ___ ___________________ MERGING - SYNCING STUFF _______________
@@ -1666,18 +2167,20 @@ def same_book(self, data1, data2, book1="", book2=""):
def wrong_book(self):
""" Shows an info dialog if the book MD5 of two metadata are different
"""
- text = _("It seems that the selected metadata file belongs to a different book..")
+ text = _("It seems that the selected metadata file belongs to a different file..")
self.popup(_("Book mismatch!"), text, icon=QMessageBox.Critical)
@staticmethod
- def same_cre_version(data):
+ def same_cre_version(data1, data2):
""" Check if the supplied metadata have the same CRE version
- :type data: list[dict]
- :param data: The data to get checked
+ :type data1: dict
+ :param data1: The data of the first book
+ :type data2: dict
+ :param data2: The data of the second book
"""
try:
- if data[0]["cre_dom_version"] == data[1]["cre_dom_version"]:
+ if data1["cre_dom_version"] == data2["cre_dom_version"]:
return True
except KeyError: # no "cre_dom_version" key (older metadata)
pass
@@ -1698,21 +2201,33 @@ def wrong_cre_version(self):
"and does not change that often.")
self.popup(_("Version mismatch!"), text, icon=QMessageBox.Critical)
- def check4archive_merge(self):
+ def check4archive_merge(self, book_data):
""" Check if the selected books' highlights can be merged
with its archived version
+
+ :type book_data: dict
+ :param book_data: The data of the book
"""
- idx = self.sel_idx
- data1 = self.file_table.item(idx.row(), idx.column()).data(Qt.UserRole)
- book_path = self.file_table.item(idx.row(), TYPE).data(Qt.UserRole)[0]
+ data1 = book_data["data"]
+ book_path = book_data["path"]
for index, book in enumerate(self.books):
data2 = book["data"]
if self.same_book(data1, data2, book_path):
- if self.same_cre_version([data1, data2]):
+ if self.same_cre_version(data1, data2):
return index
return False
+ def wrong_meta_format(self):
+ """ Shows an info dialog if the format of the two metadata are different
+ """
+ text = _("Can not merge these highlights, because their metadata format are "
+ "different!\nThere was a re-write of the highlight/bookmark "
+ "structure of KOReader that make them incompatible.\n\nRe-open the "
+ "books with a newer version of KOReader to update them and then merge "
+ "them using KOHighlights.")
+ self.popup(_("Metadata format mismatch!"), text, icon=QMessageBox.Critical)
+
def merge_menu(self):
""" Creates the `Merge/Sync` button menu
"""
@@ -1751,18 +2266,8 @@ def on_merge_highlights(self, to_archived=False, filename=""):
:type filename: str|unicode
:param filename: The path to the metadata file to merge the book with
"""
- if self.high_merge_warning:
- text = _("Merging highlights is experimental so, always do backups ;o)\n"
- "Because of the different page formats and sizes, some page "
- "numbers in {} might be inaccurate. "
- "Do you want to continue?").format(APP_NAME)
- popup = self.popup(_("Warning!"), text, buttons=2,
- button_text=(_("Yes"), _("No")),
- check_text=DO_NOT_SHOW)
- self.high_merge_warning = not popup.checked
- if popup.buttonRole(popup.clickedButton()) == QMessageBox.RejectRole:
- return
-
+ if self.merge_warning_stop():
+ return
popup = self.popup(_("Warning!"),
_("The highlights of the selected entries will be merged.\n"
"This can not be undone! Continue?"), buttons=2,
@@ -1771,6 +2276,21 @@ def on_merge_highlights(self, to_archived=False, filename=""):
if popup.buttonRole(popup.clickedButton()) == QMessageBox.AcceptRole:
self.merge_highlights(popup.checked, True, to_archived, filename)
+ def merge_warning_stop(self):
+ """ Stop if the merge warning is answered "No"
+ """
+ if self.high_merge_warning:
+ text = _(f"Merging highlights is experimental so, always do backups ;o)\n"
+ f"Because of the different page formats and sizes, some page "
+ f"numbers in {APP_NAME} might be inaccurate. "
+ f"Do you want to continue?")
+ popup = self.popup(_("Warning!"), text, buttons=2,
+ button_text=(_("Yes"), _("No")),
+ check_text=DO_NOT_SHOW)
+ self.high_merge_warning = not popup.checked
+ if popup.buttonRole(popup.clickedButton()) == QMessageBox.RejectRole:
+ return True
+
def merge_highlights(self, sync, merge, to_archived=False, filename=""):
""" Merge highlights from the same book in two different devices
@@ -1783,160 +2303,314 @@ def merge_highlights(self, sync, merge, to_archived=False, filename=""):
:type filename: str|unicode
:param filename: The path to the metadata file to merge the book with
"""
+ item = self.file_table.item
if to_archived: # Merge/Sync a book with archive
idx1, idx2 = self.sel_idx, None
- data1 = self.file_table.item(idx1.row(), TITLE).data(Qt.UserRole)
- data2 = self.books[self.check4archive_merge()]["data"]
- path1, path2 = self.file_table.item(idx1.row(), PATH).text(), None
+ data1 = item(idx1.row(), TITLE).data(Qt.UserRole)
+ path1, path2 = item(idx1.row(), PATH).text(), None
+ book_data = {"data": data1, "path": path1}
+ db_idx = self.check4archive_merge(book_data)
+ if not db_idx:
+ return
+ data2 = self.books[db_idx]["data"]
elif filename: # Merge/Sync a book with a metadata file
idx1, idx2 = self.sel_idx, None
- data1 = self.file_table.item(idx1.row(), TITLE).data(Qt.UserRole)
- book1 = self.file_table.item(idx1.row(), TYPE).data(Qt.UserRole)[0]
+ data1 = item(idx1.row(), TITLE).data(Qt.UserRole)
+ book1 = item(idx1.row(), TYPE).data(Qt.UserRole)[0]
data2 = decode_data(filename)
name2 = splitext(dirname(filename))[0]
book2 = name2 + splitext(book1)[1]
if not self.same_book(data1, data2, book1, book2):
self.wrong_book()
return
- if not self.same_cre_version([data1, data2]):
+ if not self.same_cre_version(data1, data2):
self.wrong_cre_version()
return
- path1, path2 = self.file_table.item(idx1.row(), PATH).text(), None
+ path1, path2 = item(idx1.row(), PATH).text(), None
else: # Merge/Sync two different book files
idx1, idx2 = self.sel_indexes
- data1, data2 = [self.file_table.item(idx.row(), TITLE).data(Qt.UserRole)
+ data1, data2 = [item(idx.row(), TITLE).data(Qt.UserRole)
for idx in [idx1, idx2]]
- path1, path2 = [self.file_table.item(idx.row(), PATH).text()
+ path1, path2 = [item(idx.row(), PATH).text()
for idx in [idx1, idx2]]
if merge: # merge highlights
- args = (data1["highlight"], data2["highlight"],
- data1["bookmarks"], data2["bookmarks"])
- high1, high2, bkm1, bkm2 = self.get_unique_highlights(*args)
- self.update_data(data1, high2, bkm2)
- self.update_data(data2, high1, bkm1)
- if data1["highlight"] or data2["highlight"]: # since there are highlights
- for index in [idx1, idx2]: # set the green icon
- if index:
- item = self.file_table.item(idx1.row(), TITLE)
- item.setIcon(self.ico_label_green)
+ data1_new = data1.get("annotations") is not None
+ data2_new = data2.get("annotations") is not None
+ new_type = data1_new and data2_new
+ datas = [data1, data2]
+ if path2 and (os.stat(path1).st_mtime < os.stat(path2).st_mtime):
+ datas = [data2, data1] # put the newer metadata first
+ items = []
+ if new_type: # new format metadata
+ for data in datas:
+ total = data.get("doc_pages", data.get("stats", {}).get("pages", 0))
+ items.append([data["annotations"], total])
+ self.merge_new_highs(items)
+ if data1["annotations"]: # update row data for books
+ num = str(len([i for i in data1["annotations"].values()
+ if i.get("pos0")]))
+ for index in [idx1, idx2]:
+ if index:
+ item(index.row(), TITLE).setIcon(self.ico_label_green)
+ item(index.row(), HIGH_COUNT).setText(num)
+ item(index.row(), HIGH_COUNT).setToolTip(num)
+ elif (data1_new and not data2_new) or (data2_new and not data1_new):
+ self.wrong_meta_format()
+ return # different formats metadata - not supported
+ else: # old format metadata
+ for data in datas:
+ total = data.get("doc_pages", data.get("stats", {}).get("pages", 0))
+ items.append([data["highlight"], data["bookmarks"], total])
+ self.merge_old_highs(items)
+ if data1["highlight"]: # update row data for books
+ num = str(len(data1["highlight"]))
+ for index in [idx1, idx2]:
+ if index:
+ item(index.row(), TITLE).setIcon(self.ico_label_green)
+ item(index.row(), HIGH_COUNT).setText(num)
+ item(index.row(), HIGH_COUNT).setToolTip(num)
if sync: # sync position and percent
- if data1["percent_finished"] > data2["percent_finished"]:
- data2["percent_finished"] = data1["percent_finished"]
- data2["last_xpointer"] = data1["last_xpointer"]
- else:
- data1["percent_finished"] = data2["percent_finished"]
- data1["last_xpointer"] = data2["last_xpointer"]
-
- percent = str(int(data1["percent_finished"] * 100)) + "%"
- self.file_table.item(idx1.row(), PERCENT).setText(percent)
- if not to_archived and not filename:
- self.file_table.item(idx2.row(), PERCENT).setToolTip(percent)
-
- self.file_table.item(idx1.row(), TITLE).setData(Qt.UserRole, data1)
+ to_process = [{"data": data1, "path": path1}, {"data": data2, "path": path2}]
+ percent = self.sync_pos(to_process)
+ for index in [idx1, idx2]:
+ if index:
+ item(index.row(), PERCENT).setText(percent)
+ item(index.row(), PERCENT).setToolTip(percent)
+
+ data1["annotations_externally_modified"] = True
self.save_book_data(path1, data1)
if to_archived: # update the db item
self.update_book2db(data2)
elif filename: # do nothing with the loaded file
pass
else: # update the second item
- self.file_table.item(idx2.row(), TITLE).setData(Qt.UserRole, data2)
+ data2["annotations_externally_modified"] = True
self.save_book_data(path2, data2)
self.reload_highlights = True
@staticmethod
- def get_unique_highlights(high1, high2, bkm1, bkm2):
- """ Get the highlights, bookmarks from the first book
- that do not exist in the second book and vice versa
-
- :type high1: dict
- :param high1: The first book's highlights
- :type high2: dict
- :param high2: The second book's highlights
- :type bkm1: dict
- :param bkm1: The first book's bookmarks
- :type bkm2: dict
- :param bkm2: The second book's bookmarks
- """
- unique_high1 = defaultdict(dict)
- for page1 in high1:
- for page_id1 in high1[page1]:
- text1 = high1[page1][page_id1]["text"]
- for page2 in high2:
- for page_id2 in high2[page2]:
- if text1 == high2[page2][page_id2]["text"]:
- break # highlight found in book2
- else: # highlight was not found yet in book2
- continue # no break in the inner loop, keep looping
- break # highlight already exists in book2 (there was a break)
- else: # text not in book2 highlights, add to unique
- unique_high1[page1][page_id1] = high1[page1][page_id1]
- unique_bkm1 = {}
- for page1 in unique_high1:
- for page_id1 in unique_high1[page1]:
- text1 = unique_high1[page1][page_id1]["text"]
- for idx in bkm1:
- if text1 == bkm1[idx]["notes"]: # add highlight's bookmark to unique
- unique_bkm1[idx] = bkm1[idx]
- break
-
- unique_high2 = defaultdict(dict)
- for page2 in high2:
- for page_id2 in high2[page2]:
- text2 = high2[page2][page_id2]["text"]
- for page1 in high1:
- for page_id1 in high1[page1]:
- if text2 == high1[page1][page_id1]["text"]:
- break # highlight found in book1
- else: # highlight was not found yet in book1
- continue # no break in the inner loop, keep looping
- break # highlight already exists in book1 (there was a break)
- else: # text not in book1 highlights, add to unique
- unique_high2[page2][page_id2] = high2[page2][page_id2]
- unique_bkm2 = {}
- for page2 in unique_high2:
- for page_id2 in unique_high2[page2]:
- text2 = unique_high2[page2][page_id2]["text"]
- for idx in bkm2:
- if text2 == bkm2[idx]["notes"]: # add highlight's bookmark to unique
- unique_bkm2[idx] = bkm2[idx]
- break
+ def merge_new_highs(items):
+ """ Merge the highlights of multiple books [new format]
+
+ :type items: [[dict, int], ...]
+ :param items: [[annotations, total_pg], ...]
+ :param items: The list of books to be processed
+ """
+ uni_check_hi = set()
+ uni_highs = []
+ uni_check_bkm = set()
+ uni_bkms = []
+ for book_id, info in enumerate(items): # find all unique highlights
+ source = info[0] # that are not in all books
+ for target_id, target in enumerate(items):
+ if target_id == book_id:
+ continue # don't check self
+ target = target[0]
+ for src_hi in source.values():
+ if src_hi.get("pos0"): # a highlight
+ hi_text = src_hi.get("text", "")
+ for trg_hi in target.values():
+ if trg_hi.get("pos0"): # a highlight not a bookmark
+ if hi_text == trg_hi.get("text", ""):
+ if src_hi["datetime"] == trg_hi["datetime"]:
+ break # it's the exact same annotation
+ src_comm = src_hi.get("note") # try to get the
+ trg_comm = trg_hi.get("note") # newest change
+ if src_hi["datetime"] > trg_hi["datetime"]:
+ if src_comm: # this is the newer comment
+ trg_hi["note"] = src_comm
+ elif trg_comm: # the comment was erased lately
+ trg_hi.pop("note", None)
+ trg_hi["datetime"] = src_hi["datetime"]
+ else:
+ if trg_comm: # this is the newer comment
+ src_hi["note"] = trg_comm
+ elif src_comm: # the comment was erased lately
+ src_hi.pop("note", None)
+ src_hi["datetime"] = trg_hi["datetime"]
+ break # highlight found in target book
+ else: # highlight was not found in target book
+ if src_hi["pos0"] + src_hi["pos1"] not in uni_check_hi:
+ uni_check_hi.add(src_hi["pos0"] + src_hi["pos1"])
+ uni_highs.append((src_hi, info[1]))
+ else: # a bookmark
+ if src_hi["page"] not in uni_check_bkm:
+ uni_check_bkm.add(src_hi["page"])
+ uni_bkms.append((src_hi, info[1]))
+
+ for info in items: # update the annotations with the unique found ones
+ annots = deepcopy([i for i in info[0].values()])
+ total = info[1]
+ hi_pos_check = {i["pos0"] + i["pos1"] for i in annots if i.get("pos0")}
+ bkm_pos_check = {i["page"] for i in annots if i.get("page")}
+ for hi_info in uni_highs:
+ hi, hi_total = hi_info
+ if not hi["pos0"] + hi["pos1"] in hi_pos_check: # new highlight
+ new_hi = deepcopy(hi)
+ if hi_total != total: # re-calculate the page numbers
+ percent = int(new_hi["pageno"]) / hi_total
+ new_hi["pageno"] = str(int(round(percent * total)))
+ annots.append(new_hi)
+ for bkm_info in uni_bkms:
+ bkm, bkm_total = bkm_info
+ if not bkm["page"] in bkm_pos_check: # new bookmark
+ new_bkm = deepcopy(bkm)
+ if bkm_total != total: # re-calculate the page numbers
+ percent = int(new_bkm["pageno"]) / bkm_total
+ new_bkm["pageno"] = str(int(round(percent * total)))
+ annots.append(new_bkm)
+ info[0].clear() # repopulate the annotations
+ annots_upd = {}
+ for i, hi in enumerate(sorted(annots, key=lambda x: x["pageno"])):
+ annots_upd[i + 1] = hi
+ info[0].update(annots_upd)
- return unique_high1, unique_high2, unique_bkm1, unique_bkm2
+ @staticmethod
+ def merge_old_highs(items):
+ """ Merge the highlights of multiple books
+
+ :type items: [[dict, dict, int], ...]
+ :param items: [[highlights, bookmarks, total_pg], ...]
+ :param items: The list of books to be processed
+ """
+ uni_check = set()
+ all_uni_highs = {}
+ all_uni_bkms = {}
+ # Collect the highlights that are missing even from one book
+ for book_id, s_info in enumerate(items):
+ source = s_info[0]
+ uni_highs = defaultdict(dict)
+ uni_bkms = defaultdict(dict)
+ for target_id, t_info in enumerate(items):
+ if target_id == book_id:
+ continue # don't check self
+ target = t_info[0]
+ for src_pg in source:
+ for src_pg_id in source[src_pg]:
+ high = source[src_pg][src_pg_id]
+ src_text = high["text"]
+ for target_pg in target:
+ for target_pg_id in target[target_pg]:
+ targ = target[target_pg][target_pg_id]
+ if src_text == targ["text"]: # same annotation
+ if high["datetime"] == targ["datetime"]:
+ break
+ src_comm = "" # if one comment is newer then the
+ trg_comm = "" # other, we keep the newer for both
+ src_bkm = {}
+ trg_bkm = {}
+ for bk_idx in s_info[1]:
+ src_bkm = s_info[1][bk_idx]
+ if src_bkm["notes"] == src_text:
+ src_comm = src_bkm.get("text", "")
+ break
+ for bk_idx in t_info[1]:
+ trg_bkm = t_info[1][bk_idx]
+ if trg_bkm["notes"] == targ["text"]:
+ trg_comm = trg_bkm.get("text", "")
+ break
+ if src_bkm["datetime"] > trg_bkm["datetime"]:
+ if src_comm: # this is the newer comment
+ trg_bkm["text"] = src_comm
+ elif trg_comm: # the comment was erased lately
+ trg_bkm.pop("text", None)
+ trg_bkm["datetime"] = src_bkm["datetime"]
+ targ["datetime"] = high["datetime"]
+ else:
+ if trg_comm: # this is the newer comment
+ src_bkm["text"] = trg_comm
+ elif src_comm: # the comment was erased lately
+ src_bkm.pop("text", None)
+ src_bkm["datetime"] = trg_bkm["datetime"]
+ high["datetime"] = targ["datetime"]
+ break # highlight found in target book
+ else: # highlight was not found yet in target book
+ continue # no break in the inner loop, keep checking
+ break # highlight exists in target (there was a break)
+ else: # text not in the target book highlights, add to unique
+ # but not if already added
+ if high["pos0"] + high["pos1"] not in uni_check:
+ uni_check.add(high["pos0"] + high["pos1"])
+ uni_highs[src_pg][src_pg_id] = high
+ for pg in uni_highs:
+ for pg_id in uni_highs[pg]:
+ text = uni_highs[pg][pg_id]["text"]
+ for bkm_idx in s_info[1]: # get the associated bookmarks
+ if text == s_info[1][bkm_idx]["notes"]:
+ uni_bkms[pg][pg_id] = s_info[1][bkm_idx]
+ break
+ if uni_highs:
+ all_uni_highs[book_id] = dict(uni_highs)
+ all_uni_bkms[book_id] = dict(uni_bkms)
+
+ # Merge the highlights that are not on all books
+ for book_id in all_uni_highs:
+ uni_highs = all_uni_highs[book_id]
+ uni_bkms = all_uni_bkms[book_id]
+ source_total = items[book_id][2]
+ for item_id, target_item in enumerate(items):
+ target_total = target_item[2]
+ recalculate = source_total != target_total
+ all_highs = [target_item[0][pg][pg_id]
+ for pg in target_item[0] for pg_id in target_item[0][pg]]
+ all_bkms = [i for i in target_item[1].values()]
+ renumber = False # no change made to the highlights/bookmarks
+ for pg in uni_highs:
+ new_pg = pg
+ for pg_id in uni_highs[pg]:
+ if uni_highs[pg][pg_id] not in all_highs: # add this highlight
+ renumber = True # mark for renumbering the bookmarks
+ if recalculate: # diff total pages, recalculate page number
+ percent = int(pg) / target_total
+ new_pg = int(round(percent * source_total))
+
+ if new_pg in target_item[0]: # sort same page highlights
+ contents = target_item[0][new_pg]
+ contents = [contents[i] for i in contents]
+ extras = uni_highs[pg]
+ extras = [extras[i] for i in extras
+ if extras[i] not in contents]
+ highs = sorted(contents + extras, key=lambda x: x["pos0"])
+ target_item[0][new_pg] = {i + 1: h
+ for i, h in enumerate(highs)}
+ else: # single highlight in page, just add it
+ target_item[0][new_pg] = uni_highs[pg]
+ all_bkms.append(uni_bkms[pg][pg_id])
+ if renumber: # bookmarks added, so we must merge all and renumber them
+ bkm_list = target_item[1].copy()
+ bkm_list = [bkm_list[i] for i in bkm_list]
+ bkm_check = {i["pos0"] + i["pos1"] for i in bkm_list}
+ for bkm in all_bkms:
+ if bkm["pos0"] + bkm["pos1"] not in bkm_check:
+ bkm_list.append(bkm)
+ bkm_list = sorted(bkm_list, key=lambda x: x["page"])
+ target_item[1].clear()
+ offset = 1
+ for bkm in bkm_list:
+ target_item[1][offset] = bkm
+ offset += 1
@staticmethod
- def update_data(data, extra_highlights, extra_bookmarks):
- """ Adds the new highlights to the book's data
+ def sync_pos(to_process):
+ """ Sync the reading position of multiple books
- :type data: dict
- :param data: The book's data
- :type extra_highlights: dict
- :param extra_highlights: The other book's highlights
- :type extra_bookmarks: dict
- :param extra_bookmarks: The other book's bookmarks
- """
- highlights = data["highlight"]
- for page in extra_highlights:
- if page in highlights: # change page number if already exists
- new_page = page
- while new_page in highlights:
- new_page += 1
- highlights[new_page] = extra_highlights[page]
- else:
- highlights[page] = extra_highlights[page]
-
- bookmarks = data["bookmarks"]
- original = bookmarks.copy()
- bookmarks.clear()
- counter = 1
- for key in original.keys():
- bookmarks[counter] = original[key]
- counter += 1
- for key in extra_bookmarks.keys():
- bookmarks[counter] = extra_bookmarks[key]
- counter += 1
+ :type to_process: list
+ :param to_process: The list of books to be processed
+ """
+ percents = [i["data"]["percent_finished"] for i in to_process
+ if i and i.get("data", {}).get("percent_finished", 0) > 0]
+ max_idx = percents.index(max(percents))
+ max_percent = percents[max_idx]
+ max_pointer = to_process[max_idx]["data"]["last_xpointer"]
+
+ for item in to_process:
+ item["data"]["percent_finished"] = max_percent
+ item["data"]["last_xpointer"] = max_pointer
+
+ return str(int(max_percent * 100)) + "%"
def use_meta_files(self):
""" Selects a metadata files to sync/merge
@@ -2067,26 +2741,37 @@ def get_sdr_folder(self, row):
# ___ ___________________ SAVING STUFF __________________________
- def get_export_menu(self):
+ def create_export_menu(self):
""" Creates the `Export Files` button menu
"""
- menu = QMenu(self)
- for idx, item in enumerate([(_("To individual text files"), MANY_TEXT),
- (_("Combined to one text file"), ONE_TEXT),
- (_("To individual html files"), MANY_HTML),
- (_("Combined to one html file"), ONE_HTML),
- (_("To individual csv files"), MANY_CSV),
- (_("Combined to one csv file"), ONE_CSV),
- (_("To individual markdown files"), MANY_MD),
- (_("Combined to one markdown file"), ONE_MD)]):
- action = QAction(item[0], menu)
- action.triggered.connect(self.export_actions)
- action.setData(item[1])
- action.setIcon(self.ico_file_save)
- if idx and (idx % 2 == 0):
- menu.addSeparator()
- menu.addAction(action)
- return menu
+ self.export_menu.clear()
+ single = len(self.sel_indexes) == 1
+ if single:
+ for idx, item in enumerate([(_("To text file"), MANY_TEXT),
+ (_("To html file"), MANY_HTML),
+ (_("To csv file"), MANY_CSV),
+ (_("To md file"), MANY_MD)]):
+ action = QAction(item[0], self.export_menu)
+ action.triggered.connect(self.export_actions)
+ action.setData(item[1])
+ action.setIcon(self.ico_file_save)
+ self.export_menu.addAction(action)
+ else:
+ for idx, item in enumerate([(_("To individual text files"), MANY_TEXT),
+ (_("Combined to one text file"), ONE_TEXT),
+ (_("To individual html files"), MANY_HTML),
+ (_("Combined to one html file"), ONE_HTML),
+ (_("To individual csv files"), MANY_CSV),
+ (_("Combined to one csv file"), ONE_CSV),
+ (_("To individual md files"), MANY_MD),
+ (_("Combined to one md file"), ONE_MD)]):
+ action = QAction(item[0], self.export_menu)
+ action.triggered.connect(self.export_actions)
+ action.setData(item[1])
+ action.setIcon(self.ico_file_save)
+ if idx and (idx % 2 == 0):
+ self.export_menu.addSeparator()
+ self.export_menu.addAction(action)
# noinspection PyCallByClass
def on_export(self):
@@ -2095,13 +2780,12 @@ def on_export(self):
if self.current_view == BOOKS_VIEW:
if not self.sel_indexes:
return
+ self.toolbar.export_btn.showMenu()
elif self.current_view == HIGHLIGHTS_VIEW: # Save from high_table,
if self.save_sel_highlights(): # combine to one file
self.popup(_("Finished!"),
_("The Highlights were exported successfully!"),
icon=QMessageBox.Information)
- return
- self.toolbar.export_btn.showMenu()
def export_actions(self):
""" An `Export as...` menu item is clicked
@@ -2146,9 +2830,8 @@ def export(self, idx):
ext = "md"
else:
return
- filename = QFileDialog.getSaveFileName(self,
- _("Export to {} file").format(ext),
- self.last_dir, "*.{}".format(ext))[0]
+ filename = QFileDialog.getSaveFileName(self, _(f"Export to {ext} file"),
+ self.last_dir, f"*.{ext}")[0]
if not filename:
return
self.last_dir = dirname(filename)
@@ -2156,9 +2839,9 @@ def export(self, idx):
self.status.animation(False)
all_files = len(self.sel_indexes)
- self.popup(_("Finished!"), _("{} texts were exported from the {} processed.\n"
- "{} files with no highlights.")
- .format(saved, all_files, all_files - saved),
+ self.popup(_("Finished!"),
+ _(f"{saved} texts were exported from the {all_files} processed.\n"
+ f"{all_files - saved} files with no highlights."),
icon=QMessageBox.Information)
def save_multi_files(self, dir_path, format_, line_break, space):
@@ -2185,8 +2868,7 @@ def save_multi_files(self, dir_path, format_, line_break, space):
format_, line_break, space, sort_by)
saved += 1
except IOError as err: # any problem when writing (like long filename, etc.)
- self.popup(_("Warning!"),
- _("Could not save the file to disk!\n{}").format(err))
+ self.popup(_("Warning!"), _(f"Could not save the file to disk!\n{err}"))
return saved
def save_merged_file(self, filename, format_, line_break, space):
@@ -2222,21 +2904,31 @@ def save_merged_file(self, filename, format_, line_break, space):
text_file.write(text)
return saved
- def get_item_data(self, idx, format_):
+ def get_item_data(self, index, format_):
""" Get the highlight data for an item
- :type idx: QModelIndex
- :param idx: The item's index
+ :type index: QModelIndex
+ :param index: The item's index
:type format_: int
:param format_: The output format idx
"""
- row = idx.row()
+ row = index.row()
data = self.file_table.item(row, 0).data(Qt.UserRole)
highlights = []
- for page in data["highlight"]:
- for page_id in data["highlight"][page]:
- highlights.append(self.get_formatted_high(data, page, page_id, format_))
+ annotations = data.get("annotations")
+ if annotations: # new format metadata
+ for idx in annotations:
+ highlight = self.get_new_highlight_info(data, idx)
+ if highlight:
+ formatted_high = self.get_formatted_high(highlight, format_)
+ highlights.append(formatted_high)
+ else: # old format metadata
+ for page in data["highlight"]:
+ for page_id in data["highlight"][page]:
+ highlight = self.get_old_highlight_info(data, page, page_id)
+ if highlight:
+ highlights.append(self.get_formatted_high(highlight, format_))
title = self.file_table.item(row, TITLE).data(0)
authors = self.file_table.item(row, AUTHOR).data(0)
if authors in [OLD_TYPE, NO_AUTHOR]:
@@ -2244,19 +2936,14 @@ def get_item_data(self, idx, format_):
return authors, title, highlights
# noinspection PyTypeChecker
- def get_formatted_high(self, data, page, page_id, format_):
+ def get_formatted_high(self, highlight, format_):
""" Create the highlight's texts
- :type data: dict
- :param data: The highlight's data
- :type page: int
- :param page The page where the highlight starts
- :type page_id: int
- :param page_id The idx of this page's highlight
+ :type highlight: dict
+ :param highlight: The highlight's data
:type format_: int
:param format_ The output format idx
"""
- highlight = self.get_highlight_info(data, page, page_id)
linesep = "
" if format_ in [ONE_HTML, MANY_HTML] else os.linesep
comment = highlight["comment"].replace("\n", linesep)
chapter = (highlight["chapter"].replace("\n", linesep)
@@ -2267,12 +2954,13 @@ def get_formatted_high(self, data, page, page_id, format_):
date = date if self.date_format == DATE_FORMAT else self.get_date_text(date)
line_break2 = (os.linesep if self.status.act_text.isChecked() and comment else "")
if format_ in [ONE_CSV, MANY_CSV]:
- page_text = str(page) if self.status.act_page.isChecked() else ""
+ page_text = highlight["page"] if self.status.act_page.isChecked() else ""
date_text = date if self.status.act_date.isChecked() else ""
high_comment = (comment if self.status.act_comment.isChecked()
and comment else "")
else:
- page_text = "Page " + str(page) if self.status.act_page.isChecked() else ""
+ page_txt = "Page " + highlight["page"]
+ page_text = page_txt if self.status.act_page.isChecked() else ""
date_text = "[" + date + "]" if self.status.act_date.isChecked() else ""
high_comment = (line_break2 + "● " + comment
if self.status.act_comment.isChecked() and comment else "")
@@ -2293,7 +2981,7 @@ def save_sel_highlights(self):
text_out = extra.startswith("text")
html_out = extra.startswith("html")
csv_out = extra.startswith("csv")
- md_out = extra.startswith("mark")
+ md_out = extra.startswith("md")
if text_out:
ext = ".txt"
text = ""
@@ -2327,13 +3015,12 @@ def save_sel_highlights(self):
comment = comment.replace("\n", " \n")
if text_out:
- txt = ("{} [{}]\nPage {} [{}]\n[{}]\n{}{}"
- .format(data["title"], data["authors"], data["page"],
- data["date"], data["chapter"], data["text"], comment))
+ txt = (f"{data['title']} [{data['authors']}]\nPage {data['page']} "
+ f"[{data['date']}]\n[{data['chapter']}]\n{data['text']}{comment}")
text += txt + "\n\n"
elif html_out:
- left = "{} [{}]".format(data["title"], data["authors"])
- right = "Page {} [{}]".format(data["page"], data["date"])
+ left = f"{data['title']} [{data['authors']}]"
+ right = f"Page {data['page']} [{data['date']}]"
text += HIGH_BLOCK % {"page": left, "date": right, "comment": comment,
"highlight": data["text"],
"chapter": data["chapter"]}
@@ -2344,10 +3031,10 @@ def save_sel_highlights(self):
txt = data["text"].replace("\n", " \n")
chapter = data["chapter"]
if chapter:
- chapter = "***{0}***\n\n".format(chapter).replace("\n", " \n")
- text += ("\n---\n### {} [{}] \n*Page {} [{}]* \n{}{}{}\n"
- .format(data["title"], data["authors"], data["page"],
- data["date"], chapter, txt, comment))
+ chapter = f"***{chapter}***\n\n".replace("\n", " \n")
+ text += (f'\n---\n### {data["title"]} [{data["authors"]}] \n'
+ f'*Page {data["page"]} [{data["date"]}]* \n'
+ f'{chapter}{txt}{comment}\n')
else:
print("Unknown format export!")
return
@@ -2389,27 +3076,27 @@ def settings_load(self):
self.skip_version = app_config.get("skip_version", None)
self.date_vacuumed = app_config.get("date_vacuumed", self.date_vacuumed)
self.date_format = app_config.get("date_format", DATE_FORMAT)
+ self.theme = app_config.get("theme", THEME_NONE_OLD)
+ self.status.theme_box.setCurrentIndex(self.theme)
self.archive_warning = app_config.get("archive_warning", True)
self.exit_msg = app_config.get("exit_msg", True)
self.high_merge_warning = app_config.get("high_merge_warning", True)
self.edit_lua_file_warning = app_config.get("edit_lua_file_warning", True)
- checked = app_config.get("show_items", (True, True, True, True, True))
- if len(checked) != 5: # settings from older versions
- checked = (True, True, True, True, True)
- self.status.act_page.setChecked(checked[0])
- self.status.act_date.setChecked(checked[1])
- self.status.act_chapter.setChecked(checked[2])
- self.status.act_text.setChecked(checked[3])
- self.status.act_comment.setChecked(checked[4])
+ self.show_items = app_config.get("show_items", [True, True, True, True, True])
+ if len(self.show_items) != 5: # settings from older versions
+ self.show_items = [True, True, True, True, True]
self.high_by_page = app_config.get("high_by_page", False)
else:
+ self.status.theme_box.setCurrentIndex(self.theme)
self.resize(800, 600)
if self.highlight_width:
self.header_high_view.resizeSection(HIGHLIGHT_H, self.highlight_width)
if self.comment_width:
self.header_high_view.resizeSection(COMMENT_H, self.comment_width)
self.toolbar.set_btn_size(self.toolbar_size)
+ for idx, act in enumerate(self.status.show_actions):
+ act.setChecked(self.show_items[idx])
def settings_save(self):
""" Saves the jason based configuration settings
@@ -2427,22 +3114,16 @@ def settings_save(self):
"current_view": self.current_view, "db_mode": self.db_mode,
"high_by_page": self.high_by_page, "date_vacuumed": self.date_vacuumed,
"show_info": self.fold_btn.isChecked(), "date_format": self.date_format,
- "show_items": (self.status.act_page.isChecked(),
- self.status.act_date.isChecked(),
- self.status.act_chapter.isChecked(),
- self.status.act_text.isChecked(),
- self.status.act_comment.isChecked()),
+ "theme": self.theme, "show_items": self.show_items,
"skip_version": self.skip_version, "opened_times": self.opened_times,
"edit_lua_file_warning": self.edit_lua_file_warning,
"high_merge_warning": self.high_merge_warning,
}
try:
- if not PYTHON2:
- # noinspection PyUnresolvedReferences
- for k, v in config.items():
- if type(v) == bytes:
- # noinspection PyArgumentList
- config[k] = str(v, encoding="latin")
+ for k, v in config.items():
+ if type(v) == bytes:
+ # noinspection PyArgumentList
+ config[k] = str(v, encoding="latin")
config_json = json.dumps(config, sort_keys=True, indent=4)
with gzip.GzipFile(join(SETTINGS_DIR, str("settings.json.gz")),
"w+") as gz_file:
@@ -2473,17 +3154,9 @@ def unpickle(key):
value = app_config.get(key)
if not value:
return
- if PYTHON2:
- try:
- # noinspection PyTypeChecker
- value = pickle.loads(str(value))
- except UnicodeEncodeError: # settings from Python 3.x
- return
- else:
- # noinspection PyUnresolvedReferences
- value = value.encode("latin1")
- # noinspection PyTypeChecker,PyArgumentList
- value = pickle.loads(value, encoding="bytes")
+ value = value.encode("latin1")
+ # noinspection PyTypeChecker,PyArgumentList
+ value = pickle.loads(value, encoding="bytes")
except pickle.UnpicklingError as err:
print("While unPickling:", err)
return
@@ -2522,7 +3195,7 @@ def popup(self, title, text, icon=QMessageBox.Warning, buttons=1,
popup.setWindowIcon(self.ico_app)
if type(icon) == QMessageBox.Icon:
popup.setIcon(icon)
- elif type(icon) == unicode:
+ elif type(icon) == str:
popup.setIconPixmap(QPixmap(icon))
elif type(icon) == QPixmap:
popup.setIconPixmap(icon)
@@ -2550,6 +3223,9 @@ def popup(self, title, text, icon=QMessageBox.Warning, buttons=1,
popup.exec_()
return popup
+ def error(self, error_txt):
+ self.popup(_("Error!"), error_txt, icon=QMessageBox.Critical)
+
def passed_files(self):
""" Command line parameters that are passed to the program.
"""
@@ -2573,8 +3249,7 @@ def open_file(self, path):
opener = "open" if sys.platform == "darwin" else "xdg-open"
subprocess.call([opener, path])
except OSError:
- self.popup(_("Error opening target!"),
- _('"{}" does not exists!').format(path))
+ self.popup(_("Error opening target!"), _(f'"{path}" does not exists!'))
def copy_text_2clip(self, text):
""" Copy a text to clipboard
@@ -2609,9 +3284,8 @@ def recalculate_md5(self, file_path):
data["stats"]["md5"] = md5
if old_md5:
- text = _("The MD5 was originally\n{}\nA recalculation produces\n{}\n"
- "The MD5 was replaced and saved!").format(old_md5, md5)
- self.file_table.item(row, TITLE).setData(Qt.UserRole, data)
+ text = _(f"The MD5 was originally\n{old_md5}\nA recalculation produces\n"
+ f"{md5}\nThe MD5 was replaced and saved!")
self.save_book_data(path, data)
else:
text = _("Metadata file has no MD5 information!")
@@ -2659,10 +3333,10 @@ def auto_check4update(self):
self.opened_times += 1
if self.opened_times == 20:
- text = _("Since you are using {} for some time now, perhaps you find it "
- "useful enough to consider a donation.\nWould you like to visit "
- "the PayPal donation page?\n\nThis is a one-time message. "
- "It will never appear again!").format(APP_NAME)
+ text = _(f"Since you are using {APP_NAME} for some time now, perhaps you find"
+ f" it useful enough to consider a donation.\nWould you like to visit"
+ f" the PayPal donation page?\n\nThis is a one-time message. It will "
+ f"never appear again!")
popup = self.popup(_("A reminder..."), text,
icon=":/stuff/paypal76.png", buttons=3)
@@ -2684,9 +3358,8 @@ def auto_check4update(self):
skip_version = version_parse(self.skip_version)
if version_new > current_version and version_new != skip_version:
popup = self.popup(_("Newer version exists!"),
- _("There is a newer version (v.{}) online.\n"
- "Open the site to download it now?")
- .format(version_new),
+ _(f"There is a newer version (v.{version_new}) online.\n"
+ f"Open the site to download it now?"),
icon=QMessageBox.Information, buttons=2,
check_text=_("Don\"t alert me for this version again"))
if popup.checked:
@@ -2760,10 +3433,10 @@ def __init__(self, *args, **kwargs):
del argv[1]
sys.argv = argv
self.parser = argparse.ArgumentParser(prog=APP_NAME,
- description=_("{} v{} - A KOReader's "
- "highlights converter")
- .format(APP_NAME, __version__),
- epilog=_("Thanks for using %s!") % APP_NAME)
+ description=_(f"{APP_NAME} v{__version__} -"
+ f" A KOReader's highlights "
+ f"converter"),
+ epilog=_(f"Thanks for using {APP_NAME}!"))
self.base = Base()
if compiled: # the app is compiled
if not on_windows: # no cli in windows
@@ -2859,7 +3532,7 @@ def parse_args(self):
""" Parse the command line parameters that are passed to the program.
"""
self.parser.add_argument("-v", "--version", action="version",
- version="%(prog)s v{}".format(__version__))
+ version=f"%(prog)s v{__version__}")
self.parser.add_argument("paths", nargs="*",
help="The paths to input files or folder")
@@ -2902,9 +3575,15 @@ def parse_args(self):
help="The filename of the file (in merge mode) or "
"the directory for saving the highlight files")
+ self.parser.add_argument("-p", "--portable", action="store_true", default=False,
+ help="Just run the program in portable mode "
+ "(Windows only)")
+
# args, paths = self.parser.parse_known_args()
args = self.parser.parse_args()
- if args.use_cli:
+ if args.portable:
+ print("Running in portable mode...")
+ elif args.use_cli:
self.cli_save_highlights(args)
sys.exit(0) # quit the app if cli execution
@@ -2933,15 +3612,14 @@ def cli_save_highlights(self, args):
if isdir(path):
ext = ("an .html" if args.html else "a .csv" if args.csv
else "an .md" if args.markdown else "a .txt")
- self.parser.error("The output path (-o/--output) must be {} filename "
- "not a directory!".format(ext))
+ self.parser.error(f"The output path (-o/--output) must be {ext} filename "
+ f"not a directory!")
return
saved = self.cli_save_merged_file(args, files, line_break, space)
all_files = len(files)
- sys.stdout.write(_("\n{} files were exported from the {} processed.\n"
- "{} files with no highlights.\n").format(saved, all_files,
- all_files - saved))
+ sys.stdout.write(_(f"\n{saved} files were exported from the {all_files} processed"
+ f".\n{all_files - saved} files with no highlights.\n"))
def cli_save_multi_files(self, args, files, line_break, space):
""" Save each selected book's highlights to a different file
@@ -2964,7 +3642,7 @@ def cli_save_multi_files(self, args, files, line_break, space):
format_ = MANY_MD
else:
format_ = MANY_TEXT
- sort_by = self.cli_sort
+ sort_by = partial(self.cli_sort, args)
path = abspath(args.output)
for file_ in files:
authors, title, highlights = self.cli_get_item_data(file_, args)
@@ -2973,8 +3651,9 @@ def cli_save_multi_files(self, args, files, line_break, space):
try:
save_file(title, authors, highlights, path,
format_, line_break, space, sort_by)
+ saved += 1
except IOError as err: # any problem when writing (like long filename, etc.)
- sys.stdout.write(str("Could not save the file to disk!\n{}").format(err))
+ sys.stdout.write(str(f"Could not save the file to disk!\n{err}"))
return saved
def cli_save_merged_file(self, args, files, line_break, space):
@@ -3024,7 +3703,7 @@ def cli_save_merged_file(self, args, files, line_break, space):
path = name + new_ext
with open(path, "w+", encoding=encoding, newline="") as text_file:
text_file.write(text)
- sys.stdout.write(str("Created {}\n\n").format(path))
+ sys.stdout.write(f"Created {path}\n\n")
return saved
def cli_get_item_data(self, file_, args):
@@ -3037,13 +3716,25 @@ def cli_get_item_data(self, file_, args):
"""
data = decode_data(file_)
highlights = []
- for page in data["highlight"]:
- for page_id in data["highlight"][page]:
- highlights.append(self.cli_get_formatted_high(data, page, page_id, args))
+
+ annotations = data.get("annotations")
+ if annotations: # new format metadata
+ for idx in annotations:
+ highlight = self.base.get_new_highlight_info(data, idx)
+ if highlight:
+ formatted_high = self.cli_get_formatted_high(highlight, args)
+ highlights.append(formatted_high)
+ else: # old format metadata
+ for page in data["highlight"]:
+ for page_id in data["highlight"][page]:
+ highlight = self.base.get_old_highlight_info(data, page, page_id)
+ if highlight:
+ highlights.append(self.cli_get_formatted_high(highlight, args))
authors = ""
+ stats = "doc_props" if "doc_props" in data else "stats" if "stats" in data else ""
try:
- title = data["stats"]["title"]
- authors = data["stats"]["authors"]
+ title = data[stats]["title"]
+ authors = data[stats]["authors"]
except KeyError: # older type file
title = splitext(basename(file_))[0]
try:
@@ -3059,20 +3750,15 @@ def cli_get_item_data(self, file_, args):
title = NO_TITLE
return authors, title, highlights
- # noinspection PyTypeChecker
- def cli_get_formatted_high(self, data, page, page_id, args):
- """ Get the highlight's info (text, comment, date and page)
+ @staticmethod
+ def cli_get_formatted_high(highlight, args):
+ """ Return the highlight's info in a formatted way
- :type data: dict
- :param data: The highlight's data
- :type page: int
- :param page The page where the highlight starts
- :type page_id: int
- :param page_id The count of this page's highlight
+ :type highlight: dict
+ :param highlight: The highlight's data
:type args: argparse.Namespace
:param args: The parsed cli args
"""
- highlight = self.base.get_highlight_info(data, page, page_id)
nl = "
" if args.html else os.linesep
chapter = highlight["chapter"].replace("\n", nl) if not args.no_chapter else ""
high_text = highlight["text"]
@@ -3081,11 +3767,11 @@ def cli_get_formatted_high(self, data, page, page_id, args):
date = highlight["date"]
line_break2 = os.linesep if not args.no_highlight and comment else ""
if args.csv:
- page_text = str(page) if not args.no_page else ""
+ page_text = highlight["page"] if not args.no_page else ""
date_text = date if not args.no_date else ""
high_comment = comment if not args.no_comment and comment else ""
else:
- page_text = "Page " + str(page) if not args.no_page else ""
+ page_text = "Page " + highlight["page"] if not args.no_page else ""
date_text = "[" + date + "]" if not args.no_date else ""
high_comment = (line_break2 + "● " + comment
if not args.no_comment and comment else "")
@@ -3163,10 +3849,11 @@ def get_name(data, meta_path, title_counter):
:type title_counter: list
:param title_counter: A list with the current NO TITLE counter
"""
+ stats = "doc_props" if "doc_props" in data else "stats" if "stats" in data else ""
authors = ""
try:
- title = data["stats"]["title"]
- authors = data["stats"]["authors"]
+ title = data[stats]["title"]
+ authors = data[stats]["authors"]
except KeyError: # older type file
title = splitext(basename(meta_path))[0]
try:
@@ -3183,7 +3870,7 @@ def get_name(data, meta_path, title_counter):
title_counter[0] += 1
name = title
if authors:
- name = "{} - {}".format(authors, title)
+ name = f"{authors} - {title}"
return name
diff --git a/requirements.txt b/requirements.txt
index 39b5618..439f897 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
beautifulsoup4
future
-PySide==1.2.4
+PySide2
requests
-pypiwin32
-pywin32
\ No newline at end of file
+packaging
+pypiwin32; sys_platform == 'win32'
+pywin32; sys_platform == 'win32'
\ No newline at end of file
diff --git a/screens/screen d1.png b/screens/screen d1.png
index 0a5fde9..b51e793 100644
Binary files a/screens/screen d1.png and b/screens/screen d1.png differ
diff --git a/screens/screen l1.png b/screens/screen l1.png
index f2c8f3b..fad78fd 100644
Binary files a/screens/screen l1.png and b/screens/screen l1.png differ
diff --git a/secondary.py b/secondary.py
index 3de81bf..542bf7a 100644
--- a/secondary.py
+++ b/secondary.py
@@ -1,41 +1,36 @@
# coding=utf-8
-from __future__ import absolute_import, division, print_function, unicode_literals
from boot_config import *
from boot_config import _
import re
import webbrowser
from functools import partial
+from ntpath import normpath
from os.path import join, basename, splitext, isfile
-from pprint import pprint
-
-if QT4: # ___ ______________ DEPENDENCIES __________________________
- from PySide.QtCore import Qt, Slot, QObject, Signal, QSize, QPoint, QEvent
- from PySide.QtGui import (QApplication, QMessageBox, QIcon, QFileDialog, QLineEdit,
- QDialog, QWidget, QMovie, QFont, QMenu, QAction, QCursor,
- QTableWidget, QCheckBox, QToolButton, QActionGroup,
- QTableWidgetItem)
-elif QT5:
- from PySide2.QtCore import QObject, Qt, Signal, QPoint, Slot, QSize, QEvent
- from PySide2.QtGui import QFont, QMovie, QIcon, QCursor
+
+if QT5: # ___ ______________ DEPENDENCIES __________________________
+ from PySide2.QtCore import (QObject, Qt, Signal, QPoint, Slot, QSize, QEvent, QRect,
+ QTimer)
+ from PySide2.QtGui import (QFont, QMovie, QIcon, QCursor, QPalette, QColor, QPixmap,
+ QPainter, QPen)
from PySide2.QtWidgets import (QTableWidgetItem, QTableWidget, QMessageBox, QLineEdit,
QApplication, QWidget, QDialog, QFileDialog,
- QActionGroup, QMenu, QAction, QToolButton, QCheckBox)
+ QStyleFactory, QActionGroup, QMenu, QAction,
+ QToolButton, QCheckBox)
else: # Qt6
- from PySide6.QtCore import QObject, Qt, Signal, QEvent, QPoint, Slot, QSize
- from PySide6.QtGui import QFont, QActionGroup, QAction, QCursor, QMovie, QIcon
+ from PySide6.QtCore import (QObject, Qt, Signal, QEvent, QPoint, Slot, QSize, QRect,
+ QTimer)
+ from PySide6.QtGui import (QFont, QActionGroup, QAction, QCursor, QMovie, QIcon,
+ QPalette, QColor, QPixmap, QPainter, QPen)
from PySide6.QtWidgets import (QTableWidgetItem, QTableWidget, QApplication,
QLineEdit, QToolButton, QWidget, QMenu, QFileDialog,
- QDialog, QMessageBox, QCheckBox)
-
-if PYTHON2: # ___ __________ PYTHON 2/3 COMPATIBILITY ______________
- from distutils.version import LooseVersion as version_parse
-else:
- from packaging.version import parse as version_parse
-
+ QDialog, QMessageBox, QCheckBox, QStyleFactory)
import requests
from bs4 import BeautifulSoup
+from packaging.version import parse as version_parse
from slppu import slppu as lua # https://github.com/noembryo/slppu
+__author__ = "noEmbryo"
+
def decode_data(path):
""" Converts a lua table to a Python dict
@@ -105,12 +100,11 @@ def get_book_text(title, authors, highlights, format_, line_break, space, text):
elif format_ == ONE_TEXT:
name = title
if authors:
- name = "{} - {}".format(authors, title)
+ name = f"{authors} - {title}"
line = "-" * 80
text += line + nl + name + nl + line + nl
- highlights = [i[3] + space + i[0] + line_break +
- ("[{}]{}".format(i[4], nl) if i[4] else "") +
- i[2] + i[1] for i in highlights]
+ highlights = [i[3] + space + i[0] + line_break + (f"[{i[4]}]{nl}" if i[4] else "")
+ + i[2] + i[1] for i in highlights]
text += (nl * 2).join(highlights) + nl * 2
elif format_ == ONE_CSV:
for high in highlights:
@@ -121,7 +115,7 @@ def get_book_text(title, authors, highlights, format_, line_break, space, text):
# data = {k.encode("utf8"): v.encode("utf8") for k, v in data.items()}
text += get_csv_row(data) + "\n"
elif format_ == ONE_MD:
- text += "\n---\n## {} \n##### {} \n---\n".format(title, authors)
+ text += f"\n---\n## {title} \n##### {authors} \n---\n"
highs = []
for i in highlights:
comment = i[1].replace(nl, " " + nl)
@@ -129,7 +123,7 @@ def get_book_text(title, authors, highlights, format_, line_break, space, text):
comment = " " + comment
chapter = i[4]
if chapter:
- chapter = "***{0}***{1}{1}".format(chapter, nl).replace(nl, " " + nl)
+ chapter = f"***{chapter}***{nl}{nl}".replace(nl, " " + nl)
high = i[2].replace(nl, " " + nl)
h = ("*" + i[3] + space + i[0] + line_break + chapter +
high + comment + " \n \n")
@@ -147,7 +141,7 @@ def save_file(title, authors, highlights, path, format_, line_break, space, sort
encoding = "utf-8"
name = title
if authors:
- name = "{} - {}".format(authors, title)
+ name = f"{authors} - {title}"
if format_ == MANY_TEXT:
ext = ".txt"
line = "-" * 80
@@ -161,7 +155,7 @@ def save_file(title, authors, highlights, path, format_, line_break, space, sort
encoding = "utf-8-sig"
elif format_ == MANY_MD:
ext = ".md"
- text = "\n---\n## {} \n##### {} \n---\n".format(title, authors)
+ text = f"\n---\n## {title} \n##### {authors} \n---\n"
filename = join(path, sanitize_filename(name))
if _("NO TITLE FOUND") in title: # don't overwrite unknown title files
@@ -182,7 +176,7 @@ def save_file(title, authors, highlights, path, format_, line_break, space, sort
"chapter": chapter}
elif format_ == MANY_TEXT:
text += (page_text + space + date_text + line_break +
- ("[{}]{}".format(chapter, nl) if chapter else "") +
+ (f"[{chapter}]{nl}" if chapter else "") +
high_text + high_comment)
text += 2 * nl
elif format_ == MANY_CSV:
@@ -196,7 +190,7 @@ def save_file(title, authors, highlights, path, format_, line_break, space, sort
if high_comment:
high_comment = " " + high_comment
if chapter:
- chapter = "***{0}***{1}{1}".format(chapter, nl).replace(nl, " " + nl)
+ chapter = f"***{chapter}***{nl}{nl}".replace(nl, " " + nl)
text += ("*" + page_text + space + date_text + line_break +
chapter + high_text + high_comment +
" \n \n\n").replace("-", "\\-")
@@ -210,7 +204,8 @@ def save_file(title, authors, highlights, path, format_, line_break, space, sort
"get_book_text", "save_file", "XTableWidgetIntItem", "XTableWidgetPercentItem",
"XTableWidgetTitleItem", "DropTableWidget", "XMessageBox", "About", "AutoInfo",
"ToolBar", "TextDialog", "Status", "LogStream", "Scanner", "HighlightScanner",
- "ReLoader", "DBLoader", "XToolButton", "Filter")
+ "ReLoader", "DBLoader", "XToolButton", "Filter", "XThemes", "XIconGlyph",
+ "SyncGroup", "SyncItem", "XTableWidget")
# ___ _______________________ SUBCLASSING ___________________________
@@ -296,6 +291,76 @@ def dropEvent(self, event):
return False
+class XTableWidget(QTableWidget):
+ """ QTableWidget with support for drag and drop move of rows that contain widgets
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(XTableWidget, self).__init__(*args, **kwargs)
+ self.viewport().setAcceptDrops(True)
+ self.selection = self.selectionModel()
+ self.base = None
+
+ def dropEvent(self, event):
+ if not event.isAccepted() and event.source() == self:
+ drop_row = self.drop_on(event)
+ # rows = sorted(set(i.row() for i in self.selectedItems()))
+ rows = sorted(set(i.row() for i in self.selection.selectedRows()))
+ rows_to_move = []
+ for row in rows:
+ items = dict()
+ for col in range(self.columnCount()):
+ # get the widget or item of current cell
+ widget = self.cellWidget(row, col)
+ if isinstance(widget, type(None)): # a normal QTableWidgetItem
+ items[col] = {"kind": "QTableWidgetItem",
+ "item": QTableWidgetItem(self.item(row, col))}
+ else: # another kind of widget.
+ # So we catch the widget's unique characteristics
+ items[col] = {"kind": "QWidget", "item": widget.data}
+ rows_to_move.append(items)
+
+ for row in reversed(rows):
+ self.removeRow(row)
+ # if row < drop_row:
+ # drop_row -= 1
+ for row, data in enumerate(rows_to_move):
+ row += drop_row
+ self.insertRow(row)
+
+ for col, info in data.items():
+ if info["kind"] == "QTableWidgetItem":
+ # for QTableWidgetItem we can re-create the item directly
+ self.setItem(row, col, info["item"])
+ else: # for other widgets we call
+ # the parent's callback function to get them
+ widget = self.base.create_sync_widget(info["item"])
+ widget.idx = row
+ self.base.sync_table.setRowHeight(row, widget.sizeHint().height())
+ self.setCellWidget(row, col, widget)
+
+ self.base.update_sync_groups()
+ event.accept()
+ super(XTableWidget, self).dropEvent(event)
+
+ def drop_on(self, event):
+ index = self.indexAt(event.pos())
+ if not index.isValid():
+ return self.rowCount() - 1
+ return index.row() + 1 if self.is_below(event.pos(), index) else index.row()
+
+ def is_below(self, pos, index):
+ rect = self.visualRect(index)
+ margin = 2
+ if pos.y() - rect.top() < margin:
+ return False
+ elif rect.bottom() - pos.y() < margin:
+ return True
+ # noinspection PyTypeChecker
+ return rect.contains(pos, True) and not (int(self.model().flags(
+ index)) & Qt.ItemIsDropEnabled) and pos.y() >= rect.center().y()
+
+
class XMessageBox(QMessageBox):
""" A QMessageBox with a QCheckBox
"""
@@ -353,7 +418,7 @@ def add_to_layout(self, widget):
:param widget: The widget to be added
"""
# noinspection PyArgumentList
- self.layout().addWidget(widget, 1, 1 if PYTHON2 else 2)
+ self.layout().addWidget(widget, 1, 2)
class XToolButton(QToolButton):
@@ -396,7 +461,7 @@ def write(self, text):
class Scanner(QObject):
- found = Signal(unicode)
+ found = Signal(str)
finished = Signal()
def __init__(self, path):
@@ -404,10 +469,6 @@ def __init__(self, path):
self.path = path
def process(self):
- self.start_scan()
- self.finished.emit()
-
- def start_scan(self):
try:
for dir_path, dirs, files in os.walk(self.path):
if dir_path.lower().endswith(".sdr"): # a book's metadata folder
@@ -425,10 +486,11 @@ def start_scan(self):
continue
except UnicodeDecodeError: # os.walk error
pass
+ self.finished.emit()
class ReLoader(QObject):
- found = Signal(unicode)
+ found = Signal(str)
finished = Signal()
def __init__(self, paths):
@@ -436,13 +498,14 @@ def __init__(self, paths):
self.paths = paths
def process(self):
+ # print("Loading data from files")
for path in self.paths:
self.found.emit(path)
self.finished.emit()
class DBLoader(QObject):
- found = Signal(unicode, dict, unicode)
+ found = Signal(str, dict, str)
finished = Signal()
def __init__(self, books):
@@ -468,20 +531,319 @@ def process(self):
for row in range(self.base.file_table.rowCount()):
data = self.base.file_table.item(row, TITLE).data(Qt.UserRole)
path = self.base.file_table.item(row, TYPE).data(Qt.UserRole)[0]
- self.get_book_highlights(data, path)
+ meta_path = self.base.file_table.item(row, PATH).data(0)
+ highlights = self.base.get_highlights_from_data(data, path, meta_path)
+ for highlight in highlights:
+ self.found.emit(highlight)
self.finished.emit()
- def get_book_highlights(self, data, path):
- """ Finds all the highlights from a book
- :type data: dict
- :param data: The book data (converted from the lua file)
- :type path: str|unicode
- :param path: The book path
+class XThemes(QObject):
+ """ Dark and light theme palettes
+ """
+
+ def __init__(self, parent=None):
+ super(XThemes, self).__init__(parent)
+
+ # noinspection PyArgumentList
+ self.app = QApplication.instance()
+ self.def_style = str(self.app.style())
+ # noinspection PyArgumentList
+ themes = QStyleFactory.keys()
+ if "Fusion" in themes:
+ self.app_style = "Fusion"
+ elif "Plastique" in themes:
+ self.app_style = "Plastique"
+ else:
+ self.app_style = self.def_style
+ self.def_colors = self.get_current()
+ # self.def_palette = self.app.palette()
+
+ def dark(self):
+ """ Apply a dark theme
"""
- highlights = self.base.get_highlights_from_data(data, path)
- for highlight in highlights:
- self.found.emit(highlight)
+
+ dark_palette = QPalette()
+
+ # base
+ text = 250
+ dark_palette.setColor(QPalette.WindowText, QColor(text, text, text))
+ dark_palette.setColor(QPalette.Button, QColor(53, 53, 53))
+ dark_palette.setColor(QPalette.Light, QColor(text, text, text))
+ dark_palette.setColor(QPalette.Midlight, QColor(90, 90, 90))
+ dark_palette.setColor(QPalette.Dark, QColor(35, 35, 35))
+ dark_palette.setColor(QPalette.Text, QColor(text, text, text))
+ dark_palette.setColor(QPalette.BrightText, QColor(text, text, text))
+ dark_palette.setColor(QPalette.ButtonText, QColor(text, text, text))
+ dark_palette.setColor(QPalette.Base, QColor(25, 25, 25))
+ # dark_palette.setColor(QPalette.Text, QColor(180, 180, 180))
+ # dark_palette.setColor(QPalette.BrightText, QColor(180, 180, 180))
+ # dark_palette.setColor(QPalette.ButtonText, QColor(180, 180, 180))
+ # dark_palette.setColor(QPalette.Base, QColor(42, 42, 42))
+ dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
+ dark_palette.setColor(QPalette.Shadow, QColor(10, 10, 10))
+ # dark_palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
+ dark_palette.setColor(QPalette.Highlight, QColor(20, 50, 80))
+ dark_palette.setColor(QPalette.HighlightedText, QColor(text, text, text))
+ dark_palette.setColor(QPalette.Link, QColor(56, 252, 196))
+ dark_palette.setColor(QPalette.AlternateBase, QColor(66, 66, 66))
+ dark_palette.setColor(QPalette.ToolTipBase, QColor(53, 53, 53))
+ dark_palette.setColor(QPalette.ToolTipText, QColor(text, text, text))
+ dark_palette.setColor(QPalette.LinkVisited, QColor(80, 80, 80))
+
+ # disabled
+ gray = QColor(100, 100, 100)
+ dark_palette.setColor(QPalette.Disabled, QPalette.WindowText, gray)
+ dark_palette.setColor(QPalette.Disabled, QPalette.Text, gray)
+ dark_palette.setColor(QPalette.Disabled, QPalette.ButtonText, gray)
+ dark_palette.setColor(QPalette.Disabled, QPalette.HighlightedText, gray)
+ dark_palette.setColor(QPalette.Disabled, QPalette.Highlight,
+ QColor(80, 80, 80))
+
+ self.app.style().unpolish(self.app)
+ self.app.setPalette(dark_palette)
+ # self.app.setStyle("Fusion")
+ self.app.setStyle(self.app_style)
+
+ def light(self):
+ """ Apply a light theme
+ """
+
+ light_palette = QPalette()
+
+ # base
+ light_palette.setColor(QPalette.WindowText, QColor(0, 0, 0))
+ light_palette.setColor(QPalette.Button, QColor(240, 240, 240))
+ # light_palette.setColor(QPalette.Light, QColor(180, 180, 180))
+ # light_palette.setColor(QPalette.Midlight, QColor(200, 200, 200))
+ # light_palette.setColor(QPalette.Dark, QColor(225, 225, 225))
+ light_palette.setColor(QPalette.Dark, QColor(180, 180, 180))
+ light_palette.setColor(QPalette.Midlight, QColor(200, 200, 200))
+ light_palette.setColor(QPalette.Light, QColor(250, 250, 250))
+ light_palette.setColor(QPalette.Text, QColor(0, 0, 0))
+ light_palette.setColor(QPalette.BrightText, QColor(0, 0, 0))
+ light_palette.setColor(QPalette.ButtonText, QColor(0, 0, 0))
+ light_palette.setColor(QPalette.Base, QColor(237, 237, 237))
+ light_palette.setColor(QPalette.Window, QColor(240, 240, 240))
+ light_palette.setColor(QPalette.Shadow, QColor(20, 20, 20))
+ # light_palette.setColor(QPalette.Highlight, QColor(76, 163, 224))
+ light_palette.setColor(QPalette.Highlight, QColor(200, 230, 255))
+ light_palette.setColor(QPalette.HighlightedText, QColor(0, 0, 0))
+ light_palette.setColor(QPalette.Link, QColor(0, 162, 232))
+ light_palette.setColor(QPalette.AlternateBase, QColor(225, 225, 225))
+ light_palette.setColor(QPalette.ToolTipBase, QColor(240, 240, 240))
+ light_palette.setColor(QPalette.ToolTipText, QColor(0, 0, 0))
+ light_palette.setColor(QPalette.LinkVisited, QColor(222, 222, 222))
+
+ # disabled
+ light_palette.setColor(QPalette.Disabled, QPalette.WindowText,
+ QColor(115, 115, 115))
+ light_palette.setColor(QPalette.Disabled, QPalette.Text,
+ QColor(115, 115, 115))
+ light_palette.setColor(QPalette.Disabled, QPalette.ButtonText,
+ QColor(115, 115, 115))
+ light_palette.setColor(QPalette.Disabled, QPalette.Highlight,
+ QColor(190, 190, 190))
+ light_palette.setColor(QPalette.Disabled, QPalette.HighlightedText,
+ QColor(115, 115, 115))
+
+ self.app.style().unpolish(self.app)
+ self.app.setPalette(light_palette)
+ # self.app.setStyle("Fusion")
+ self.app.setStyle(self.app_style)
+
+ def normal(self):
+ """ Apply the normal theme
+ """
+ normal_palette = QPalette()
+
+ normal_palette.setColor(QPalette.WindowText, self.def_colors["WindowText"])
+ normal_palette.setColor(QPalette.Button, self.def_colors["Button"])
+ normal_palette.setColor(QPalette.Light, self.def_colors["Light"])
+ normal_palette.setColor(QPalette.Midlight, self.def_colors["Midlight"])
+ normal_palette.setColor(QPalette.Dark, self.def_colors["Dark"])
+ normal_palette.setColor(QPalette.Text, self.def_colors["Text"])
+ normal_palette.setColor(QPalette.BrightText, self.def_colors["BrightText"])
+ normal_palette.setColor(QPalette.ButtonText, self.def_colors["ButtonText"])
+ normal_palette.setColor(QPalette.Base, self.def_colors["Base"])
+ normal_palette.setColor(QPalette.Window, self.def_colors["Window"])
+ normal_palette.setColor(QPalette.Shadow, self.def_colors["Shadow"])
+ normal_palette.setColor(QPalette.Highlight, self.def_colors["Highlight"])
+ normal_palette.setColor(QPalette.HighlightedText,
+ self.def_colors["HighlightedText"])
+ normal_palette.setColor(QPalette.Link, self.def_colors["Link"])
+ normal_palette.setColor(QPalette.AlternateBase,
+ self.def_colors["AlternateBase"])
+ normal_palette.setColor(QPalette.ToolTipBase, self.def_colors["ToolTipBase"])
+ normal_palette.setColor(QPalette.ToolTipText, self.def_colors["ToolTipText"])
+ normal_palette.setColor(QPalette.LinkVisited, self.def_colors["LinkVisited"])
+
+ # # disabled
+ # normal_palette.setColor(QPalette.Disabled, QPalette.WindowText,
+ # QColor(115, 115, 115))
+ # normal_palette.setColor(QPalette.Disabled, QPalette.Text,
+ # QColor(115, 115, 115))
+ # normal_palette.setColor(QPalette.Disabled, QPalette.ButtonText,
+ # QColor(115, 115, 115))
+ # normal_palette.setColor(QPalette.Disabled, QPalette.Highlight,
+ # QColor(190, 190, 190))
+ # normal_palette.setColor(QPalette.Disabled, QPalette.HighlightedText,
+ # QColor(115, 115, 115))
+
+ self.app.style().unpolish(self.app.base)
+ self.app.setPalette(normal_palette)
+ # self.app.setPalette(self.def_palette)
+ self.app.setStyle(self.def_style)
+
+ @staticmethod
+ def get_current():
+ """ Return the current theme's data
+ """
+ light_palette = QPalette()
+ data = {'WindowText': (light_palette.color(QPalette.WindowText)),
+ 'Button': (light_palette.color(QPalette.Button)),
+ 'Light': (light_palette.color(QPalette.Light)),
+ 'Midlight': (light_palette.color(QPalette.Midlight)),
+ 'Dark': (light_palette.color(QPalette.Dark)),
+ 'Text': (light_palette.color(QPalette.Text)),
+ 'BrightText': (light_palette.color(QPalette.BrightText)),
+ 'ButtonText': (light_palette.color(QPalette.ButtonText)),
+ 'Base': (light_palette.color(QPalette.Base)),
+ 'Window': (light_palette.color(QPalette.Window)),
+ 'Shadow': (light_palette.color(QPalette.Shadow)),
+ 'Highlight': (light_palette.color(QPalette.Highlight)),
+ 'HighlightedText': (light_palette.color(QPalette.HighlightedText)),
+ 'Link': (light_palette.color(QPalette.Link)),
+ 'AlternateBase': (light_palette.color(QPalette.AlternateBase)),
+ 'ToolTipBase': (light_palette.color(QPalette.ToolTipBase)),
+ 'ToolTipText': (light_palette.color(QPalette.ToolTipText)),
+ 'LinkVisited': (light_palette.color(QPalette.LinkVisited))}
+ return data
+
+
+class XIconGlyph(QObject):
+ """ A Font char to QIcon converter
+
+ * Usage in Base:
+ fdb = QFontDatabase()
+ fdb.addApplicationFont(":/stuff/font.ttf") # add a custom font or use existing
+ # pprint(fdb.families())
+
+ self.font_ico = XIconGlyph(self, glyph=None)
+
+ ico = self.font_ico.get_icon({"char": "✓",
+ "size": (32, 32),
+ "size_ratio": 1.2,
+ "offset": (0, -2),
+ "family": "XFont",
+ "color": "#FF0000",
+ "active": "orange",
+ "hover": (160, 50, 255),
+ })
+ self.tool_btn.setIcon(ico)
+ """
+
+ def __init__(self, parent, glyph=None):
+ super(XIconGlyph, self).__init__(parent)
+ self.char = ""
+ self.family = ""
+ self.color = parent.palette().text().color().name() # use the default
+ self.active = None
+ self.hover = None
+ self.disabled = parent.palette().dark().color().name()
+ self.icon_size = 16, 16
+ self.size_ratio = 1
+ self.offset = 0, 0
+ self.glyph = glyph
+ if glyph:
+ self._parse_glyph(glyph)
+
+ def _parse_glyph(self, glyph):
+ """ Set the glyph options
+
+ :type glyph: dict
+ """
+ if self.glyph:
+ self.glyph.update(glyph)
+
+ family = glyph.get("family")
+ if family:
+ self.family = family
+ char = glyph.get("char")
+ if char:
+ self.char = char
+ icon_size = glyph.get("size")
+ if icon_size:
+ self.icon_size = icon_size
+ offset = glyph.get("offset")
+ if offset:
+ self.offset = offset
+ size_ratio = glyph.get("size_ratio")
+ if size_ratio:
+ self.size_ratio = size_ratio
+ active = glyph.get("active")
+ if active:
+ self.active = active
+ hover = glyph.get("hover")
+ if hover:
+ self.hover = hover
+ color = glyph.get("color")
+ if color:
+ self.color = color
+ disabled = glyph.get("disabled")
+ if disabled:
+ self.disabled = disabled
+
+ def _get_char_pixmap(self, color):
+ """ Create an icon from a font character
+
+ :type color: str|tuple
+ :param color: The color of the icon
+ :return: QPixmap
+ """
+ if isinstance(color, tuple):
+ color = QColor(*color)
+ else:
+ color = QColor(color)
+ font = QFont()
+ if self.family:
+ font.setFamily(self.family)
+ font.setPixelSize(self.icon_size[1] * self.size_ratio)
+
+ pixmap = QPixmap(*self.icon_size)
+ pixmap.fill(QColor(0, 0, 0, 0)) # fill with transparency
+
+ painter = QPainter(pixmap)
+ painter.setFont(font)
+ pen = QPen()
+ pen.setColor(color)
+ painter.setPen(pen)
+ painter.drawText(QRect(QPoint(*self.offset), QSize(*self.icon_size)),
+ Qt.AlignCenter | Qt.AlignVCenter, self.char)
+ painter.end()
+ return pixmap
+
+ def get_icon(self, glyph=None):
+ """ Get the icon from the glyph
+
+ :type glyph: dict
+ :return: QIcon
+ """
+ if glyph:
+ self._parse_glyph(glyph)
+
+ icon = QIcon()
+ icon.addPixmap(self._get_char_pixmap(self.color), QIcon.Normal, QIcon.Off)
+ if self.active: # the checkable down state icon
+ icon.addPixmap(self._get_char_pixmap(self.active),
+ QIcon.Active, QIcon.On)
+ if self.hover: # the mouse hover state icon
+ icon.addPixmap(self._get_char_pixmap(self.hover),
+ QIcon.Active, QIcon.Off)
+ if self.disabled: # the disabled state icon
+ icon.addPixmap(self._get_char_pixmap(self.disabled),
+ QIcon.Disabled, QIcon.Off)
+ return icon
# ___ _______________________ GUI STUFF _____________________________
@@ -492,6 +854,8 @@ def get_book_highlights(self, data, path):
from gui_status import Ui_Status
from gui_edit import Ui_TextDialog
from gui_filter import Ui_Filter
+from gui_sync_group import Ui_SyncGroup
+from gui_sync_item import Ui_SyncItem
class ToolBar(QWidget, Ui_ToolBar):
@@ -505,20 +869,22 @@ def __init__(self, parent=None):
self.setupUi(self)
self.base = parent
- self.buttons = (self.check_btn, self.scan_btn, self.export_btn, self.open_btn,
- self.merge_btn, self.delete_btn, self.clear_btn, self.about_btn,
- self.books_view_btn, self.high_view_btn, self.filter_btn)
- self.size_menu = self.create_size_menu()
- self.db_menu = self.create_db_menu()
+ self.buttons = (self.scan_btn, self.export_btn, self.open_btn, self.merge_btn,
+ self.delete_btn, self.clear_btn, self.about_btn, self.filter_btn,
+ self.books_view_btn, self.high_view_btn, self.sync_view_btn,
+ self.add_btn)
+
+ self.size_menu = QMenu(self)
+ # self.size_menu.aboutToShow.connect(self.create_size_menu)
+
+ self.db_menu = QMenu()
+ self.db_menu.aboutToShow.connect(self.create_db_menu)
self.db_btn.setMenu(self.db_menu)
- for btn in [self.loaded_btn, self.db_btn,
- self.books_view_btn, self.high_view_btn]:
+ for btn in [self.books_view_btn, self.high_view_btn, self.sync_view_btn,
+ self.loaded_btn, self.db_btn]:
btn.clicked.connect(self.change_view)
- self.check_btn.clicked.connect(parent.on_check_btn)
- self.check_btn.hide()
-
@Slot(QPoint)
def on_tool_frame_customContextMenuRequested(self, point):
""" The Toolbar is right-clicked
@@ -526,25 +892,20 @@ def on_tool_frame_customContextMenuRequested(self, point):
:type point: QPoint
:param point: The point where the right-click happened
"""
- self.size_menu.exec_(self.tool_frame.mapToGlobal(point))
-
- def create_size_menu(self):
- """ Create the toolbar's buttons size menu
- """
- menu = QMenu(self)
+ self.size_menu.clear()
group = QActionGroup(self)
sizes = (_("Tiny"), 16), (_("Small"), 32), (_("Medium"), 48), (_("Big"), 64)
for name, size in sizes:
- action = QAction(name, menu)
+ action = QAction(name, self.size_menu)
action.setCheckable(True)
if size == self.base.toolbar_size:
action.setChecked(True)
action.triggered.connect(partial(self.set_btn_size, size))
group.addAction(action)
- menu.addAction(action)
+ self.size_menu.addAction(action)
if QT6: # QT6 requires exec() instead of exec_()
- menu.exec_ = getattr(menu, "exec")
- return menu
+ self.size_menu.exec_ = getattr(self.size_menu, "exec")
+ self.size_menu.exec_(self.tool_frame.mapToGlobal(point))
def set_btn_size(self, size):
""" Changes the Toolbar's icons size
@@ -559,6 +920,7 @@ def set_btn_size(self, size):
for btn in self.buttons:
btn.setMinimumWidth(size + 10)
btn.setIconSize(button_size)
+ # btn.setStyleSheet("QToolButton:disabled {background-color: rgb(0, 0, 0);}")
for btn in [self.loaded_btn, self.db_btn]:
# btn.setMinimumWidth(size + 10)
@@ -566,6 +928,9 @@ def set_btn_size(self, size):
# noinspection PyArgumentList
QApplication.processEvents()
+ if self.base.theme in [THEME_NONE_NEW, THEME_DARK_NEW, THEME_LIGHT_NEW]:
+ self.base.set_new_icons(menus=False)
+
@Slot()
def on_scan_btn_clicked(self):
""" The `Scan Directory` button is pressed
@@ -606,6 +971,19 @@ def on_open_btn_clicked(self):
data = self.base.high_table.item(idx.row(), HIGHLIGHT_H).data(Qt.UserRole)
self.base.open_file(data["path"])
+ @Slot()
+ def on_add_btn_clicked(self):
+ """ The `Add` sync group button is pressed
+ """
+ if self.base.current_view == SYNC_VIEW:
+ info = {"title": "",
+ "sync_pos": False,
+ "merge": False,
+ "sync_db": True,
+ "items": [{"path": "", "data": {}}],
+ "enabled": True}
+ self.base.create_sync_row(info)
+
@Slot(bool)
def on_filter_btn_toggled(self, state):
""" The `Find` button is pressed
@@ -623,9 +1001,27 @@ def on_filter_btn_toggled(self, state):
def on_merge_btn_clicked(self):
""" The `Merge` button is pressed
"""
+ if self.base.current_view == SYNC_VIEW:
+ if self.base.merge_warning_stop():
+ return
+ text = _("Synchronize all active Sync groups?")
+ popup = self.base.popup(_("Sync"), text, icon=QMessageBox.Question,
+ buttons=2)
+ if popup.buttonRole(popup.clickedButton()) == QMessageBox.AcceptRole:
+ changed_total = 0
+ for idx in range(self.base.sync_table.rowCount()):
+ group = self.base.sync_table.cellWidget(idx, 0)
+ if group.power_btn.isChecked():
+ changed = self.base.synchronize_group(group, multi=True)
+ if changed:
+ changed_total += 1
+ text = _(f"Synchronization process completed\n"
+ f"{changed_total} groups were synchronized")
+ self.base.popup(_("Information"), text, QMessageBox.Information)
+ return
data = [self.base.file_table.item(idx.row(), idx.column()).data(Qt.UserRole)
for idx in self.base.sel_indexes]
- if self.base.same_cre_version(data):
+ if self.base.same_cre_version(*data):
self.base.on_merge_highlights()
else:
self.base.wrong_cre_version()
@@ -634,18 +1030,31 @@ def on_merge_btn_clicked(self):
def on_delete_btn_clicked(self):
""" The `Delete` button is pressed
"""
- self.base.delete_actions(0)
+ if self.base.current_view == BOOKS_VIEW:
+ # self.base.delete_actions(0)
+ if not self.base.db_mode:
+ self.delete_btn.showMenu()
+ else:
+ self.base.delete_actions(0)
+ elif self.base.current_view == HIGHLIGHTS_VIEW:
+ self.base.on_delete_highlights()
+ elif self.base.current_view == SYNC_VIEW:
+ for index in sorted(self.base.sel_sync_view)[::-1]:
+ row = index.row()
+ del self.base.sync_groups[row]
+ self.base.sync_table.model().removeRow(row)
+ self.base.update_sync_groups()
@Slot()
def on_clear_btn_clicked(self):
""" The `Clear List` button is pressed
"""
- if self.base.current_view == HIGHLIGHTS_VIEW:
- (self.base.high_table.model() # clear Books view too
- .removeRows(0, self.base.high_table.rowCount()))
+ if self.base.current_view == SYNC_VIEW:
+ return
self.base.loaded_paths.clear()
self.base.reload_highlights = True
self.base.file_table.model().removeRows(0, self.base.file_table.rowCount())
+ self.base.high_table.model().removeRows(0, self.base.high_table.rowCount())
self.activate_buttons()
@Slot()
@@ -658,44 +1067,52 @@ def on_db_btn_right_clicked(self):
def create_db_menu(self):
""" Create the database menu
"""
- menu = QMenu(self)
-
- action = QAction(_("Create new database"), menu)
+ self.db_menu.clear()
+ action = QAction(_("Create new database"), self.db_menu)
action.setIcon(self.base.ico_db_add)
action.triggered.connect(partial(self.base.change_db, NEW_DB))
- menu.addAction(action)
+ self.db_menu.addAction(action)
- action = QAction(_("Reload database"), menu)
+ action = QAction(_("Reload database"), self.db_menu)
action.setIcon(self.base.ico_refresh)
action.triggered.connect(partial(self.base.change_db, RELOAD_DB))
- menu.addAction(action)
+ self.db_menu.addAction(action)
- action = QAction(_("Change database"), menu)
+ action = QAction(_("Change database"), self.db_menu)
action.setIcon(self.base.ico_db_open)
action.triggered.connect(partial(self.base.change_db, CHANGE_DB))
- menu.addAction(action)
+ self.db_menu.addAction(action)
if QT6: # QT6 requires exec() instead of exec_()
- menu.exec_ = getattr(menu, "exec")
- return menu
+ self.db_menu.exec_ = getattr(self.db_menu, "exec")
def change_view(self):
""" Changes what is shown in the app
"""
- new = self.update_archived() if self.db_btn.isChecked() else self.update_loaded()
+ reloaded = False
+ if not self.sync_view_btn.isChecked(): # don't reload when coming from Sync view
+ reloaded = (self.update_archived()
+ if self.db_btn.isChecked() else self.update_loaded())
if self.books_view_btn.isChecked(): # Books view
- # self.add_btn_menu(self.base.toolbar.export_btn)
+ self.base.current_view = BOOKS_VIEW
+ self.merge_btn.setToolTip(TOOLTIP_MERGE)
+ self.merge_btn.setStatusTip(TOOLTIP_MERGE)
if self.base.sel_idx:
item = self.base.file_table.item(self.base.sel_idx.row(),
self.base.sel_idx.column())
self.base.on_file_table_itemClicked(item, reset=False)
- else: # Highlights view
- for btn in [self.base.toolbar.export_btn, self.base.toolbar.delete_btn]:
- self.remove_btn_menu(btn)
- if self.base.reload_highlights and not new:
+ elif self.high_view_btn.isChecked(): # Highlights view
+ self.base.current_view = HIGHLIGHTS_VIEW
+ if self.base.reload_highlights and not reloaded:
self.base.scan_highlights_thread()
+ else: # Sync view
+ self.base.current_view = SYNC_VIEW
+ self.merge_btn.setToolTip(TOOLTIP_SYNC)
+ self.merge_btn.setStatusTip(TOOLTIP_SYNC)
+ if not self.base.sync_groups_loaded:
+ # noinspection PyTypeChecker
+ QTimer.singleShot(0, self.base.load_sync_groups)
+ self.base.sync_groups_loaded = True
- self.base.current_view = (BOOKS_VIEW if self.books_view_btn.isChecked()
- else HIGHLIGHTS_VIEW)
self.base.views.setCurrentIndex(self.base.current_view)
self.setup_buttons()
self.activate_buttons()
@@ -720,7 +1137,7 @@ def update_archived(self):
self.base.db_mode = True
self.base.reload_highlights = True
self.base.read_books_from_db()
- text = _("Loading {} database").format(APP_NAME)
+ text = _(f"Loading {APP_NAME} database")
self.base.loading_thread(DBLoader, self.base.books, text)
if not len(self.base.books): # no books in the db
text = _('There are no books currently in the archive.\nTo add/'
@@ -733,74 +1150,84 @@ def setup_buttons(self):
""" Shows/Hides toolbar's buttons based on the view selected
"""
books_view = self.books_view_btn.isChecked()
+ high_view = self.high_view_btn.isChecked()
+ sync_view = self.sync_view_btn.isChecked()
db_mode = self.db_btn.isChecked()
- self.scan_btn.setVisible(not db_mode)
- self.merge_btn.setVisible(books_view and not db_mode)
- self.delete_btn.setVisible(books_view)
- self.clear_btn.setVisible(not db_mode)
+ self.scan_btn.setVisible(not (db_mode or sync_view))
+ self.export_btn.setVisible(not sync_view)
+ self.open_btn.setVisible(not sync_view)
+ self.add_btn.setVisible(sync_view)
+ self.filter_btn.setVisible(not sync_view)
+ self.merge_btn.setVisible(sync_view or not (db_mode or high_view))
+ # self.delete_btn.setVisible(books_view or sync_view)
+ self.clear_btn.setVisible(not (db_mode or sync_view))
- if self.base.db_mode:
- self.remove_btn_menu(self.base.toolbar.delete_btn)
- else:
- self.add_btn_menu(self.base.toolbar.delete_btn)
- self.base.status.setVisible(books_view)
+ self.mode_grp.setEnabled(not sync_view)
+ self.base.status.show_items_btn.setVisible(books_view)
+
+ self.set_btn_menu(self.export_btn, books_view)
+ self.set_btn_menu(self.merge_btn, books_view)
+ self.set_btn_menu(self.delete_btn, books_view and not db_mode)
def activate_buttons(self):
""" Enables/Disables toolbar's buttons based on selection/view
"""
- if self.base.high_table.isVisible(): # Highlights view
+ count = 0
+ sync_enable = False
+ book_exists = False
+ if self.base.current_view == HIGHLIGHTS_VIEW: # Highlights view
try:
idx = self.base.sel_high_view[-1]
except IndexError:
idx = None
count = self.base.high_table.rowCount()
- else:
+ elif self.base.current_view == BOOKS_VIEW: # Books view
idx = self.base.sel_idx
count = self.base.file_table.rowCount()
+ if len(self.base.sel_indexes) == 2: # check if we can sync/merge
+ idx1, idx2 = self.base.sel_indexes
+ data1 = self.base.file_table.item(idx1.row(),
+ idx1.column()).data(Qt.UserRole)
+ path1 = self.base.file_table.item(idx1.row(), TYPE).data(Qt.UserRole)[0]
+ data2 = self.base.file_table.item(idx2.row(),
+ idx2.column()).data(Qt.UserRole)
+ path2 = self.base.file_table.item(idx2.row(), TYPE).data(Qt.UserRole)[0]
+ sync_enable = self.base.same_book(data1, data2, path1, path2)
+ else: # Sync view
+ try:
+ idx = self.base.sel_sync_view[-1]
+ except IndexError:
+ idx = None
+ sync_enable = True
+
if idx:
row = idx.row()
- if self.base.high_table.isVisible(): # Highlights view
+ if self.base.file_table.isVisible(): # Books view
+ book_exists = self.base.file_table.item(row, TYPE).data(Qt.UserRole)[1]
+ elif self.base.high_table.isVisible(): # Highlights view
data = self.base.high_table.item(row, HIGHLIGHT_H).data(Qt.UserRole)
book_exists = isfile(data["path"])
- else:
- book_exists = self.base.file_table.item(row, TYPE).data(Qt.UserRole)[1]
- else:
- book_exists = False
- self.export_btn.setEnabled(bool(idx))
+ self.export_btn.setEnabled(bool(idx) and not self.base.sync_table.isVisible())
self.open_btn.setEnabled(book_exists)
self.delete_btn.setEnabled(bool(idx))
self.clear_btn.setEnabled(bool(count))
-
- self.merge_btn.setEnabled(False)
- if len(self.base.sel_indexes) == 2: # check if we can sync/merge
- idx1, idx2 = self.base.sel_indexes
- data1 = self.base.file_table.item(idx1.row(), idx1.column()).data(Qt.UserRole)
- path1 = self.base.file_table.item(idx1.row(), TYPE).data(Qt.UserRole)[0]
- data2 = self.base.file_table.item(idx2.row(), idx2.column()).data(Qt.UserRole)
- path2 = self.base.file_table.item(idx2.row(), TYPE).data(Qt.UserRole)[0]
- self.merge_btn.setEnabled(self.base.same_book(data1, data2, path1, path2))
+ self.merge_btn.setEnabled(sync_enable)
@staticmethod
- def add_btn_menu(btn):
+ def set_btn_menu(btn, status=True):
""" Adds a menu arrow to a toolbar button
:type btn: QToolButton
:param btn: The button to change
"""
- btn.setStyleSheet("")
- btn.setPopupMode(QToolButton.MenuButtonPopup)
-
- @staticmethod
- def remove_btn_menu(btn):
- """ Removes the menu arrow from a toolbar button
-
- :type btn: QToolButton
- :param btn: The button to change
- """
- btn.setStyleSheet("QToolButton::menu-indicator{width:0px;}")
- btn.setPopupMode(QToolButton.DelayedPopup)
+ if status:
+ btn.setStyleSheet("")
+ btn.setPopupMode(QToolButton.MenuButtonPopup)
+ else:
+ btn.setStyleSheet("QToolButton::menu-indicator{width:0px;}")
+ btn.setPopupMode(QToolButton.DelayedPopup)
@Slot()
def on_about_btn_clicked(self):
@@ -819,10 +1246,7 @@ def __init__(self, parent=None):
"""
super(Filter, self).__init__(parent)
self.setupUi(self)
- if QT4: # Remove the question mark widget from dialog
- # noinspection PyUnresolvedReferences
- self.setWindowFlags(self.windowFlags() ^ Qt.WindowContextHelpButtonHint)
- self.setWindowTitle(_("Filter").format(APP_NAME))
+ self.setWindowTitle(_("Filter"))
self.base = parent
def keyPressEvent(self, event):
@@ -899,7 +1323,7 @@ def on_filter(self):
else:
self.base.file_table.setRowHidden(row, True)
filtered += 1
- else:
+ elif self.base.toolbar.high_view_btn.isChecked():
row_count = self.base.high_table.rowCount()
for row in range(row_count):
title = self.base.high_table.item(row, TITLE_H).data(0)
@@ -934,8 +1358,10 @@ def on_filter(self):
else:
self.base.high_table.setRowHidden(row, True)
filtered += 1
- self.filtered_lbl.setText(_("Showing {}/{}").format(row_count - filtered,
- row_count))
+ else:
+ print("SYNC FILTERRRRRRRRRRRRRRRRR")
+ return
+ self.filtered_lbl.setText(_(f"Showing {row_count - filtered}/{row_count}"))
@Slot()
def on_clear_filter_btn_clicked(self):
@@ -976,10 +1402,7 @@ def __init__(self, parent=None):
"""
super(About, self).__init__(parent)
self.setupUi(self)
- if QT4: # Remove the question mark widget from dialog
- # noinspection PyUnresolvedReferences
- self.setWindowFlags(self.windowFlags() ^ Qt.WindowContextHelpButtonHint)
- self.setWindowTitle(_("About {}").format(APP_NAME))
+ self.setWindowTitle(_(f"About {APP_NAME}"))
self.base = parent
@Slot()
@@ -1007,24 +1430,21 @@ def check_for_updates(self):
current_version = version_parse(self.base.version)
if version_new > current_version:
popup = self.base.popup(_("Newer version exists!"),
- _("There is a newer version (v.{}) online.\n"
- "Open the site to download it now?")
- .format(version_new),
+ _(f"There is a newer version (v.{version_new}) online"
+ f".\nOpen the site to download it now?"),
icon=QMessageBox.Information, buttons=2)
if popup.clickedButton().text() == "OK":
webbrowser.open("http://www.noembryo.com/apps.php?katalib")
self.close()
elif version_new == current_version:
self.base.popup(_("No newer version exists!"),
- _("{} is up to date (v.{})").format(APP_NAME,
- current_version),
+ _(f"{APP_NAME} is up to date (v.{current_version})"),
icon=QMessageBox.Information, buttons=1)
elif version_new < current_version:
self.base.popup(_("No newer version exists!"),
- _("It seems that you are using a newer version ({0})\n"
- "than the one online ({1})!").format(current_version,
- version_new),
- icon=QMessageBox.Question, buttons=1)
+ _(f"It seems that you are using a newer version "
+ f"({current_version})\nthan the one online "
+ f"({version_new})!"), icon=QMessageBox.Question, buttons=1)
@staticmethod
def get_online_version():
@@ -1051,30 +1471,30 @@ def create_text(self):
# color = self.palette().color(QPalette.WindowText).name() # for links
splash = ":/stuff/logo.png"
paypal = ":/stuff/paypal.png"
- info = _("""
+ info = _(f"""
-
- {3} is a utility for viewing
- Koreader's
+
+ {APP_NAME} is a utility for viewing
+ KOReader's
highlights
and/or export them to simple text
- Version {1}
+ Version {self.base.version}
Visit
- {3} page at GitHub, or
+ {APP_NAME} page at GitHub, or
noEmbryo's page with more Apps and stuff...
Use it and if you like it, consider to
-
- """).format(splash, self.base.version, paypal, APP_NAME)
+ """)
self.text_lbl.setText(info)
@@ -1101,19 +1521,16 @@ class TextDialog(QDialog, Ui_TextDialog):
def __init__(self, parent=None):
super(TextDialog, self).__init__(parent)
- if QT4: # Remove the question mark widget from dialog
- # noinspection PyUnresolvedReferences
- self.setWindowFlags(self.windowFlags() ^ Qt.WindowContextHelpButtonHint)
self.setupUi(self)
self.base = parent
- self.on_ok = None
+ # self.on_ok = None
@Slot()
def on_ok_btn_clicked(self):
""" The OK button is pressed
"""
- self.on_ok()
+ self.base.edit_comment_ok()
class Status(QWidget, Ui_Status):
@@ -1126,26 +1543,38 @@ def __init__(self, parent=None):
super(Status, self).__init__(parent)
self.setupUi(self)
self.base = parent
+ self.themes = XThemes(parent)
self.wait_anim = QMovie(":/stuff/wait.gif")
self.anim_lbl.setMovie(self.wait_anim)
self.anim_lbl.hide()
+ self.show_actions = [self.act_page, self.act_date, self.act_text,
+ self.act_chapter, self.act_comment]
+ for idx, act in enumerate(self.show_actions):
+ act.setData(idx)
+ act.triggered.connect(partial(self.on_show_items, act))
+
self.show_menu = QMenu(self)
- for i in [self.act_page, self.act_date, self.act_text, self.act_chapter,
- self.act_comment]:
- self.show_menu.addAction(i)
- # noinspection PyUnresolvedReferences
- i.triggered.connect(self.on_show_items)
- i.setChecked(True)
+ self.show_menu.aboutToShow.connect(self.get_show_menu)
+ self.show_items_btn.setMenu(self.show_menu)
+
+ def get_show_menu(self):
+ """ Returns the menu with the items to show
+ """
+ self.show_menu.clear()
+ for idx, act in enumerate(self.show_actions):
+ act.setChecked(self.base.show_items[idx])
+ self.show_menu.addAction(act)
action = QAction(_("Date Format"), self.show_menu)
- action.setIcon(QIcon(":/stuff/calendar.png"))
+ action.setIcon(self.base.ico_calendar)
action.triggered.connect(self.set_date_format)
self.show_menu.addAction(action)
sort_menu = QMenu(self)
- ico_sort = QIcon(":/stuff/sort.png")
+ sort_menu.setIcon(self.base.ico_sort)
+ sort_menu.setTitle(_("Sort by"))
group = QActionGroup(self)
action = QAction(_("Date"), sort_menu)
@@ -1164,20 +1593,57 @@ def __init__(self, parent=None):
group.addAction(action)
sort_menu.addAction(action)
- sort_menu.setIcon(ico_sort)
- sort_menu.setTitle(_("Sort by"))
self.show_menu.addMenu(sort_menu)
- self.show_items_btn.setMenu(self.show_menu)
-
- def on_show_items(self):
+ @Slot(int)
+ def on_theme_box_currentIndexChanged(self, idx):
+ """ Selects the app's theme style
+ """
+ if idx == THEME_NONE_OLD:
+ self.themes.normal()
+ self.base.set_old_icons()
+ if self.base.theme not in [THEME_NONE_OLD, THEME_NONE_NEW]:
+ self.no_theme_popup(idx)
+ return
+ elif idx == THEME_NONE_NEW:
+ self.themes.normal()
+ self.base.set_new_icons()
+ if self.base.theme not in [THEME_NONE_OLD, THEME_NONE_NEW]:
+ self.no_theme_popup(idx)
+ return
+ elif idx == THEME_DARK_OLD:
+ self.themes.dark()
+ self.base.set_old_icons()
+ elif idx == THEME_DARK_NEW:
+ self.themes.dark()
+ self.base.set_new_icons()
+ elif idx == THEME_LIGHT_OLD:
+ self.themes.light()
+ self.base.set_old_icons()
+ elif idx == THEME_LIGHT_NEW:
+ self.themes.light()
+ self.base.set_new_icons()
+
+ self.base.theme = idx
+ self.base.reset_theme_colors()
+
+ def no_theme_popup(self, idx):
+ self.base.theme = idx
+ self.base.reset_theme_colors()
+ self.base.popup(_("Warning"), _("The theme will be fully reset after "
+ "the application is restarted."))
+
+ def on_show_items(self, action=None):
""" Show/Hide elements of the highlight info
"""
+ if action:
+ act_idx = action.data()
+ self.base.show_items[act_idx] = action.isChecked()
try:
- idx = self.base.file_table.selectionModel().selectedRows()[-1]
+ table_idx = self.base.file_table.selectionModel().selectedRows()[-1]
except IndexError: # nothing selected
return
- item = self.base.file_table.item(idx.row(), 0)
+ item = self.base.file_table.item(table_idx.row(), 0)
self.base.on_file_table_itemClicked(item)
def set_date_format(self):
@@ -1215,6 +1681,407 @@ def animation(self, run):
self.wait_anim.stop()
+class SyncGroup(QWidget, Ui_SyncGroup):
+
+ def __init__(self, parent=None):
+ """ Initializes the StatusBar
+
+ :type parent: Base
+ """
+ super(SyncGroup, self).__init__(parent)
+ self.setupUi(self)
+ self.base = parent
+ self.sync_items = []
+ self.items_layout = self.items_frm.layout()
+
+ self.idx = None
+ self.data = None
+ self.new_format = True
+ self.def_btn_icos = []
+ self.buttons = [(self.power_btn, "Y"),
+ (self.sync_btn, "E"),
+ (self.refresh_btn, "Z")]
+ self.setup_buttons()
+ self.setup_icons()
+
+ font = QFont()
+ font.setBold(True)
+ font.setPointSize(QFont.pointSize(QFont()) + 3)
+ self.title_lbl.setFont(font)
+
+ power_color = self.base.palette().button().color().name()
+ self.css = 'QFrame#items_frm {background-color: "%s";}'
+ self.setStyleSheet(self.css % power_color)
+
+ self.sync_pos_chk.stateChanged.connect(self.update_data)
+ self.merge_chk.stateChanged.connect(self.update_data)
+ self.sync_db_chk.stateChanged.connect(self.update_data)
+
+ @Slot(QPoint)
+ def on_group_frm_customContextMenuRequested(self, point):
+ """ When the context menu of the SyncGroup is requested
+
+ :type point: QPoint
+ :param point: The point of the click
+ """
+ menu = QMenu(self)
+ if QT6: # QT6 requires exec() instead of exec_()
+ menu.exec_ = getattr(menu, "exec")
+
+ action = QAction(_("Rename group"), menu)
+ action.setIcon(self.base.ico_file_edit)
+ action.triggered.connect(self.on_rename)
+ menu.addAction(action)
+
+ action = QAction(_("Sync group"), menu)
+ action.setIcon(self.base.ico_files_merge)
+ action.triggered.connect(self.on_sync_btn_clicked)
+ menu.addAction(action)
+
+ menu.addSeparator()
+
+ action = QAction(_("Delete selected"), menu)
+ action.setIcon(self.base.ico_delete)
+ action.triggered.connect(self.base.toolbar.on_delete_btn_clicked)
+ menu.addAction(action)
+
+ menu.exec_(self.mapToGlobal(point))
+
+ def on_rename(self):
+ """ Renames the SyncGroup
+ """
+ title = self.title_lbl.text()
+ title = title if title else True
+ popup = self.base.popup(_("Rename SyncGroup"),
+ _("Enter the new name of the SyncGroup:"),
+ icon=QMessageBox.Question, buttons=2,
+ input_text=title, button_text=(_("OK"), _("Cancel")))
+ if popup.buttonRole(popup.clickedButton()) == QMessageBox.AcceptRole:
+ text = popup.typed_text
+ self.title_lbl.setText(text)
+ self.data["title"] = text
+ self.update_data()
+
+ def setup_buttons(self):
+ for btn, char in self.buttons:
+ self.def_btn_icos.append(btn.icon())
+ size = btn.iconSize().toTuple()
+ btn.xig = XIconGlyph(self, {"family": "XFont", "size": size, "char": char})
+ for item in self.sync_items:
+ item.setup_buttons()
+
+ def setup_icons(self):
+ if self.base.theme in [THEME_NONE_NEW, THEME_DARK_NEW, THEME_LIGHT_NEW]:
+ # noinspection PyTypeChecker
+ QTimer.singleShot(0, self.set_new_icons)
+ else:
+ self.set_old_icons()
+ for item in self.sync_items:
+ item.setup_icons()
+
+ # noinspection DuplicatedCode
+ def set_new_icons(self):
+ """ Get the font icons with the new color palette
+ """
+ color = self.palette().text().color().name()
+ for btn, _ in self.buttons:
+ size = btn.iconSize().toTuple()
+ btn.xig.color = color
+ btn.setIcon(btn.xig.get_icon({"size": size}))
+
+ def set_old_icons(self):
+ """ Reload the old icons
+ """
+ for idx, item in enumerate(self.buttons):
+ btn = item[0]
+ btn.setIcon(self.def_btn_icos[idx])
+
+ @Slot(bool)
+ def on_power_btn_clicked(self, state):
+ """ Enables the Group
+ """
+ if state:
+ power_color = self.base.palette().button().color().name()
+ else:
+ power_color = self.base.palette().dark().color().name()
+ self.setStyleSheet(self.css % power_color)
+
+ self.data["enable"] = state
+ self.update_data()
+
+ @Slot()
+ def on_refresh_btn_clicked(self, ):
+ """ The `Refresh` button is pressed
+ """
+ items_paths = [i["path"] for i in self.data.get("items", [])]
+ for item in self.sync_items:
+ self.items_layout.removeWidget(item)
+ self.sync_items = []
+ for path in items_paths:
+ self.add_item({"path": path})
+ self.check_data()
+
+ @Slot()
+ def on_sync_btn_clicked(self, ):
+ """ The `Sync this group` button is pressed
+ """
+ if self.base.merge_warning_stop():
+ return
+ self.base.synchronize_group(self)
+
+ def add_item(self, data):
+ """ Adds a new sync item
+
+ :type data: dict
+ :param data: The sync item data
+ """
+ item = SyncItem(self.base)
+ item.group = self
+ self.sync_items.append(item)
+ item.idx = len(self.sync_items) - 1
+ path = data["path"]
+ if path:
+ item.sync_path_txt.setText(path)
+ try:
+ data = decode_data(path)
+ except FileNotFoundError: # path doesn't exist
+ self.data["items"][item.idx]["data"] = {}
+ self.set_erroneous(item, _("Could not access the book's metadata file"))
+ except PermissionError:
+ self.set_erroneous(item, _("Could not access the book's metadata file"))
+ self.base.error(_(f"Could not access the book's metadata file\n{path}\n\n"
+ f"Merging this group will produce unpredictable "
+ f"results."))
+ self.data["items"][item.idx]["data"] = {}
+ else:
+ self.data["items"][item.idx]["data"] = data
+ if not item.idx:
+ self.new_format = data.get("annotations") is not None
+ self.items_layout.addWidget(item)
+
+ def remove_item(self, item):
+ """ Removes a sync item
+
+ :type item: SyncItem
+ """
+ del self.data["items"][item.idx]
+ self.items_layout.removeWidget(item)
+ self.sync_items.remove(item)
+ item.deleteLater()
+ for idx, item in enumerate(self.sync_items):
+ item.idx = idx
+
+ def reset_group_height(self):
+ """ Reset the height of the group row
+ """
+ height = self.sizeHint().height()
+ self.base.sync_table.setRowHeight(self.idx, height)
+
+ def check_data(self):
+ """ Checks if the data is valid for syncing
+ """
+ source = {"path:": "", "data": {}}
+ for idx, sync_item in enumerate(self.sync_items):
+ self.set_txt_normal(sync_item)
+ path = self.data["items"][idx]["path"]
+ if path and not isfile(path): # file doesn't exist
+ text = _("The path to the book's metadata file does not exist")
+ self.set_erroneous(sync_item, text)
+ if idx:
+ continue # check the next path
+ else:
+ return # missing source file
+ elif not path:
+ continue # empty item
+
+ data = self.data["items"][idx]["data"]
+ if not idx: # source is the first item
+ source["path"] = path
+ source["data"] = data
+ if not data.get("cre_dom_version"):
+ text = _("The metadata file is of an older, not supported version.\n"
+ "No syncing is possible for this Sync group.")
+ self.set_erroneous(sync_item, text)
+ return
+ continue # check source with the rest
+
+ # check if the data format is the same
+ data1_new = source["data"].get("annotations") is not None
+ try:
+ data2_new = data.get("annotations") is not None
+ except AttributeError:
+ self.set_erroneous(sync_item,
+ _("Could not access the book's metadata file"))
+ self.base.error(_(f"Could not access the book's metadata file\n{path}"))
+ continue
+ if (data1_new and not data2_new) or (data2_new and not data1_new):
+ text = _("The book's metadata files are in different format")
+ self.set_erroneous(sync_item, text)
+ continue
+
+ # check if the book's md5 is the same
+ if not self.base.same_book(source["data"], data, source["path"], path):
+ text = _("The book file is different from the rest")
+ self.set_erroneous(sync_item, text)
+ continue
+
+ # check if the books have the same cre version
+ if not self.base.same_cre_version(source["data"], data):
+ text = _("The metadata files were produced with a different version "
+ "of the reader engine")
+ self.set_erroneous(sync_item, text)
+ continue
+
+ def set_txt_normal(self, item):
+ """ Sets the normal state of the item's text
+
+ :type item: SyncItem
+ """
+ item.ok = True
+ tooltip = _("The path to the book's metadata file")
+ item.sync_path_txt.setToolTip(tooltip)
+ item.sync_path_txt.setStatusTip(tooltip)
+ item.sync_path_txt.setStyleSheet(self.styleSheet())
+
+ def set_erroneous(self, item, tooltip=""):
+ """ Sets the erroneous state of the item
+
+ :type item: SyncItem
+ """
+ item.ok = False
+ item.sync_path_txt.setToolTip(tooltip)
+ item.sync_path_txt.setStatusTip(tooltip)
+
+ if self.base.theme in (THEME_DARK_NEW, THEME_DARK_OLD):
+ color = "#DD0000"
+ else:
+ color = "#990000"
+ style = self.styleSheet() + 'QLineEdit {color: "%s";}' % color
+ item.sync_path_txt.setStyleSheet(style)
+
+ def update_data(self):
+ """ Saves and updates the sync group data when something is changed
+ """
+ if self.idx is None: # on first load on startup
+ return
+ data = {"title": self.title_lbl.text(),
+ "sync_pos": self.sync_pos_chk.isChecked(),
+ "merge": self.merge_chk.isChecked(),
+ "sync_db": self.sync_db_chk.isChecked(),
+ "items": self.data["items"],
+ "enabled": self.power_btn.isChecked()
+ }
+ self.base.sync_groups[self.idx] = data
+ self.data = data
+ self.base.save_sync_groups()
+
+
+class SyncItem(QWidget, Ui_SyncItem):
+
+ def __init__(self, parent=None):
+ """ Initializes the StatusBar
+
+ :type parent: Base
+ """
+ super(SyncItem, self).__init__(parent)
+ self.setupUi(self)
+ self.base = parent
+ self.group = SyncGroup(self.base)
+ self.def_btn_icos = []
+ self.buttons = [(self.add_btn, "F"),
+ (self.del_btn, "J")]
+ self.setup_buttons()
+ self.setup_icons()
+ self.ok = True
+
+ def setup_buttons(self):
+ for btn, char in self.buttons:
+ self.def_btn_icos.append(btn.icon())
+ size = btn.iconSize().toTuple()
+ btn.xig = XIconGlyph(self, {"family": "XFont", "size": size, "char": char})
+
+ def setup_icons(self):
+ if self.base.theme in [THEME_NONE_NEW, THEME_DARK_NEW, THEME_LIGHT_NEW]:
+ # noinspection PyTypeChecker
+ QTimer.singleShot(0, self.set_new_icons)
+ else:
+ self.set_old_icons()
+
+ # noinspection DuplicatedCode
+ def set_new_icons(self):
+ """ Get the font icons with the new color palette
+ """
+ color = self.palette().text().color().name()
+ for btn, _ in self.buttons:
+ size = btn.iconSize().toTuple()
+ btn.xig.color = color
+ btn.setIcon(btn.xig.get_icon({"size": size}))
+
+ def set_old_icons(self):
+ """ Reload the old icons
+ """
+ for idx, item in enumerate(self.buttons):
+ btn = item[0]
+ btn.setIcon(self.def_btn_icos[idx])
+
+ @Slot()
+ def on_sync_path_btn_clicked(self, ):
+ """ The `Select` path button is pressed
+ """
+ last_dir = self.base.last_dir
+ text = self.sync_path_txt.text().strip()
+ if text:
+ last_dir = dirname(text)
+ path = QFileDialog.getOpenFileName(self.base, _("Select the metadata file"),
+ last_dir, "metadata files (*.lua)")[0]
+ if path:
+ path = normpath(path)
+ self.base.last_dir = dirname(path)
+ for item in self.group.data["items"]: # check existence
+ if item["path"] == path:
+ self.base.popup(_("!"),
+ _("This metadata file already exists in the group!"),)
+ return
+ idx = self.group.sync_items.index(self)
+ self.group.data["items"][idx]["path"] = path
+ data = decode_data(path)
+ self.group.data["items"][idx]["data"] = data
+ if idx == 0: # first item
+ self.group.new_format = data.get("annotations") is not None
+ if not self.group.title_lbl.text().strip():
+ title = data.get("doc_props", data.get("stats", {})).get("title", "")
+ self.group.title_lbl.setText(title)
+ self.sync_path_txt.setText(path)
+ self.group.update_data()
+ self.group.check_data()
+
+ @Slot()
+ def on_add_btn_clicked(self, ):
+ """ Add a new item to the group
+ """
+ first_item = self.group.sync_items.index(self) == 0
+ if first_item and not self.sync_path_txt.text().strip(): # no first sync path
+ self.base.error(_("The first metadata file path must not be empty!"))
+ return
+ item_data = {"path": "", "data": {}}
+ self.group.add_item(item_data)
+ self.group.data["items"].append(item_data)
+ self.group.update_data()
+ # noinspection PyTypeChecker
+ QTimer.singleShot(200, self.group.reset_group_height)
+
+ @Slot()
+ def on_del_btn_clicked(self, ):
+ """ Delete this item from the group
+ """
+ if not self.idx: # the first item can't be deleted
+ self.base.error(_("Can't delete the first metadata file path!"))
+ return
+ self.group.remove_item(self)
+ self.group.update_data()
+ # noinspection PyTypeChecker
+ QTimer.singleShot(100, self.group.reset_group_height)
+
# if __name__ == "__main__":
# with open("secondary.py", str("r")) as py_text:
# import re
diff --git a/slppu.py b/slppu.py
index e72bb38..1ec1731 100644
--- a/slppu.py
+++ b/slppu.py
@@ -1,13 +1,5 @@
-from __future__ import print_function
import re
import sys
-try: # ___ _______ PYTHON 2/3 COMPATIBILITY ________________________
- # noinspection PyCompatibility
- basestring
-except NameError: # python 3.x
- # noinspection PyShadowingBuiltins
- basestring, unicode, long = str, str, int
-from future.utils import iteritems
# https://github.com/noembryo/slppu
@@ -30,13 +22,13 @@ def __init__(self):
self.at = 0
self.len = 0
self.depth = 0
- self.space = re.compile('\s', re.M)
- self.alnum = re.compile('\w', re.M)
+ self.space = re.compile(r'\s', re.M)
+ self.alnum = re.compile(r'\w', re.M)
self.newline = '\n'
self.tab = ' ' # or '\t'
def decode(self, text):
- if not text or not isinstance(text, basestring):
+ if not text or not isinstance(text, str):
return
# FIXME: only short comments removed
reg = re.compile('--.*$', re.M)
@@ -57,9 +49,9 @@ def __encode(self, obj):
tab = self.tab
newline = self.newline
tp = type(obj)
- if tp in [str, unicode]:
+ if tp == str:
s += '"%s"' % obj.replace(r'"', r'\"')
- elif tp in [int, float, long, complex]:
+ elif tp in [int, float, complex]:
s += str(obj)
elif tp is bool:
s += str(obj).lower()
@@ -68,16 +60,16 @@ def __encode(self, obj):
elif tp in [list, tuple, dict]:
self.depth += 1
if len(obj) == 0 or (tp is not dict and
- len(filter(lambda x: type(x) in (int, float, long) or
- (isinstance(x, basestring) and
+ len(filter(lambda x: type(x) in (int, float, str) or
+ (isinstance(x, str) and
len(x) < 10), obj)) == len(obj)):
newline = tab = ''
dp = tab * self.depth
s += "%s{%s" % (tab * (self.depth - 2), newline)
if tp is dict:
contents = []
- for k, v in iteritems(obj):
- k = ('[{}]'.format(k) if type(k) in [int, float, long, complex]
+ for k, v in obj.items():
+ k = ('[{}]'.format(k) if type(k) in [int, float, complex]
else '["{}"]'.format(k))
contents.append(dp + '%s = %s' % (k, (self.__encode(v))))
s += (',%s' % newline).join(contents)
@@ -161,8 +153,8 @@ def object(self):
if k is not None:
o[idx] = k
if not numeric_keys and len([key for key in o
- if isinstance(key, (str, unicode, float,
- bool, tuple))]) == 0:
+ if isinstance(key, (str, float, bool,
+ tuple))]) == 0:
ar = []
for key in o:
ar.insert(key, o[key])
diff --git a/stuff/add.png b/stuff/add.png
new file mode 100644
index 0000000..388f8ff
Binary files /dev/null and b/stuff/add.png differ
diff --git a/stuff/del.png b/stuff/del.png
new file mode 100644
index 0000000..9b5b793
Binary files /dev/null and b/stuff/del.png differ
diff --git a/stuff/files_add.png b/stuff/files_add.png
new file mode 100644
index 0000000..e107e83
Binary files /dev/null and b/stuff/files_add.png differ
diff --git a/stuff/font.ttf b/stuff/font.ttf
new file mode 100644
index 0000000..3812d43
Binary files /dev/null and b/stuff/font.ttf differ
diff --git a/stuff/power32gray.png b/stuff/power32gray.png
new file mode 100644
index 0000000..820a4d9
Binary files /dev/null and b/stuff/power32gray.png differ
diff --git a/stuff/power32red.png b/stuff/power32red.png
new file mode 100644
index 0000000..275a0db
Binary files /dev/null and b/stuff/power32red.png differ
diff --git a/stuff/sync.png b/stuff/sync.png
new file mode 100644
index 0000000..097aa46
Binary files /dev/null and b/stuff/sync.png differ
diff --git a/stuff/view-sync.png b/stuff/view-sync.png
new file mode 100644
index 0000000..c46e768
Binary files /dev/null and b/stuff/view-sync.png differ