diff --git a/gui_main.py b/gui_main.py
index 7c25352..c6cc618 100644
--- a/gui_main.py
+++ b/gui_main.py
@@ -3,7 +3,7 @@
# 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: Tue May 7 14:53:08 2024
+# Created: Mon Aug 26 13:41:15 2024
# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
@@ -339,5 +339,5 @@ def retranslateUi(self, Base):
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, XTableWidget
+from secondary import XTableWidget, DropTableWidget
import images_rc
diff --git a/gui_status.py b/gui_status.py
index 941ddbc..9388e4f 100644
--- a/gui_status.py
+++ b/gui_status.py
@@ -3,7 +3,7 @@
# 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 May 2 17:29:33 2024
+# Created: Mon Aug 26 13:41:16 2024
# by: pyside2-uic running on PySide2 5.13.2
#
# WARNING! All changes made in this file will be lost!
@@ -80,10 +80,14 @@ def retranslateUi(self, Status):
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_page.setToolTip(QtWidgets.QApplication.translate("Status", "Show the highlight\'s page number", None, -1))
self.act_date.setText(QtWidgets.QApplication.translate("Status", "Date", None, -1))
+ self.act_date.setToolTip(QtWidgets.QApplication.translate("Status", "Show the highlight\'s date", None, -1))
self.act_text.setText(QtWidgets.QApplication.translate("Status", "Highlight", None, -1))
+ self.act_text.setToolTip(QtWidgets.QApplication.translate("Status", "Show the highlight\'s text", None, -1))
self.act_comment.setText(QtWidgets.QApplication.translate("Status", "Comment", None, -1))
+ self.act_comment.setToolTip(QtWidgets.QApplication.translate("Status", "Show the highlight\'s 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))
+ self.act_chapter.setToolTip(QtWidgets.QApplication.translate("Status", "Show the highlight\'s chapter", None, -1))
import images_rc
diff --git a/gui_status.ui b/gui_status.ui
index b2e4ce5..e7128bc 100644
--- a/gui_status.ui
+++ b/gui_status.ui
@@ -114,6 +114,9 @@ what will be saved to the text/html files.
Page
+
+ Show the highlight's page number
+
@@ -122,6 +125,9 @@ what will be saved to the text/html files.
Date
+
+ Show the highlight's date
+
@@ -130,6 +136,9 @@ what will be saved to the text/html files.
Highlight
+
+ Show the highlight's text
+
@@ -138,6 +147,9 @@ what will be saved to the text/html files.
Comment
+
+ Show the highlight's comment
+
@@ -147,7 +159,7 @@ what will be saved to the text/html files.
Chapter
- Chapter
+ Show the highlight's chapter
diff --git a/images.qrc b/images.qrc
index e4379c0..45d44d2 100644
--- a/images.qrc
+++ b/images.qrc
@@ -1,43 +1,44 @@
+stuff/power32gray.png
+stuff/view-sync.png
+stuff/db_open.png
+stuff/folder_open.png
+stuff/refresh16.png
+stuff/font.ttf
+stuff/paypal.png
+stuff/db.png
+stuff/sync.png
+stuff/folder_reader.png
+stuff/del.png
stuff/calendar.png
+stuff/view_books.png
stuff/file_exists.png
stuff/show_pages.png
-stuff/folder_reader.png
+stuff/wait.gif
+stuff/trash.png
+stuff/power32red.png
+stuff/description.png
stuff/files_merge.png
+stuff/delete.png
stuff/logo64.png
-stuff/file_edit.png
-stuff/sync.png
-stuff/refresh16.png
-stuff/folder_open.png
-stuff/trans32.png
-stuff/file_save.png
-stuff/power32gray.png
-stuff/trash.png
-stuff/books.png
stuff/files_add.png
-stuff/power32red.png
+stuff/file_missing.png
+stuff/trans32.png
+stuff/paypal76.png
+stuff/file_edit.png
stuff/filter.png
-stuff/add.png
-stuff/files_delete.png
-stuff/delete.png
-stuff/wait.gif
-stuff/db_open.png
+stuff/file_save.png
stuff/files_view.png
-stuff/description.png
-stuff/label_green.png
-stuff/font.ttf
+stuff/db_compact.png
+stuff/copy.png
stuff/logo.png
-stuff/db.png
-stuff/paypal76.png
-stuff/paypal.png
-stuff/db_add.png
-stuff/sort.png
-stuff/view-sync.png
-stuff/view_books.png
-stuff/del.png
+stuff/files_delete.png
+stuff/add.png
stuff/view-highlights.png
-stuff/copy.png
-stuff/file_missing.png
+stuff/sort.png
+stuff/db_add.png
+stuff/label_green.png
+stuff/books.png
\ No newline at end of file
diff --git a/main.py b/main.py
index b494a16..e52234e 100644
--- a/main.py
+++ b/main.py
@@ -44,7 +44,7 @@
__author__ = "noEmbryo"
-__version__ = "2.0.4.0"
+__version__ = "2.1.0.0"
class Base(QMainWindow, Ui_Base):
@@ -74,7 +74,8 @@ def __init__(self, parent=None):
self.db_mode = False
self.toolbar_size = 48
self.alt_title_sort = False
- self.high_by_page = False
+ self.high_by_page = True
+ self.show_ref_pg = False
self.high_merge_warning = True
self.archive_warning = True
self.exit_msg = True
@@ -100,6 +101,7 @@ def __init__(self, parent=None):
self.parent_book_data = {}
self.custom_book_data = {}
self.reload_highlights = True
+ self.reload_from_sync = False
self.threads = []
self.query = None
@@ -136,6 +138,7 @@ def __init__(self, parent=None):
self.ico_files_delete = QIcon(":/stuff/files_delete.png")
self.ico_db_add = QIcon(":/stuff/db_add.png")
self.ico_db_open = QIcon(":/stuff/db_open.png")
+ self.ico_db_compact = QIcon(":/stuff/db_compact.png")
self.ico_refresh = QIcon(":/stuff/refresh16.png")
self.ico_folder_open = QIcon(":/stuff/folder_open.png")
self.ico_calendar = QIcon(":/stuff/calendar.png")
@@ -160,6 +163,7 @@ def __init__(self, parent=None):
self.ico_file_edit,
self.ico_copy,
self.ico_delete,
+ self.ico_db_compact,
]
self.ico_file_exists = QIcon(":/stuff/file_exists.png")
self.ico_file_missing = QIcon(":/stuff/file_missing.png")
@@ -205,24 +209,31 @@ def __init__(self, parent=None):
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.custom_btn, "Q"), (self.filter.filter_btn, "D"),
- (self.description_btn, "V"), (self.filter.clear_filter_btn, "G")]
-
- # noinspection PyTypeChecker,PyCallByClass
+ 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.custom_btn, "Q"),
+ (self.filter.filter_btn, "D"),
+ (self.description_btn, "V"),
+ (self.filter.clear_filter_btn, "G")]
+
QTimer.singleShot(10000, self.auto_check4update) # check for updates
main_timer = QTimer(self) # cleanup threads forever
main_timer.timeout.connect(self.thread_cleanup)
main_timer.start(2000)
- # noinspection PyTypeChecker,PyCallByClass
QTimer.singleShot(0, self.on_load)
def on_load(self):
@@ -231,6 +242,7 @@ def on_load(self):
QFontDatabase.addApplicationFont(":/stuff/font.ttf")
# QFontDatabase.removeApplicationFont(0)
+ self.setup_buttons()
self.settings_load()
self.init_db()
if FIRST_RUN: # on first run
@@ -248,25 +260,28 @@ 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 = _(f"Loading {APP_NAME} database")
- self.loading_thread(DBLoader, self.books, text)
+ QTimer.singleShot(0, self.load_db_rows)
+ # self.load_db_rows()
self.read_books_from_db() # always load db on start
if self.current_view == BOOKS_VIEW:
self.toolbar.books_view_btn.click() # open in Books view
+ elif self.current_view == SYNC_VIEW:
+ self.toolbar.sync_view_btn.click() # open in Sync view
else:
self.toolbar.high_view_btn.click() # open in Highlights view
- self.setup_buttons()
- self.show()
-
if app_config:
- # noinspection PyTypeChecker,PyCallByClass
QTimer.singleShot(0, self.restore_windows)
else:
self.resize(800, 600)
- # noinspection PyTypeChecker
- QTimer.singleShot(0, self.load_sync_groups)
+ QTimer.singleShot(0, self.show)
+ QTimer.singleShot(250, self.load_sync_groups)
+
+ def load_db_rows(self):
+ """ Load the rows from the database
+ """
+ self.loading_thread(DBLoader, self.books, _(f"Loading {APP_NAME} database"))
def setup_buttons(self):
for btn, char in self.buttons:
@@ -281,7 +296,6 @@ def set_new_icons(self, menus=True):
: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):
@@ -311,6 +325,7 @@ def delayed_set_new_icons(self, menus=True):
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"})
+ self.ico_db_compact = xig.get_icon({"char": "J"})
def set_old_icons(self):
""" Reload the old icons
@@ -333,6 +348,7 @@ def set_old_icons(self):
self.ico_file_edit = self.def_icons[11]
self.ico_copy = self.def_icons[12]
self.ico_delete = self.def_icons[13]
+ self.ico_db_compact = self.def_icons[14]
def reset_theme_colors(self):
""" Resets the widget colors after a theme change
@@ -453,8 +469,9 @@ def bye_bye_stuff(self):
def init_db(self):
""" Initialize the database tables
"""
- # noinspection PyTypeChecker,PyCallByClass
self.db = QSqlDatabase.addDatabase("QSQLITE")
+ if not isfile(self.db_path):
+ self.db_path = join(SETTINGS_DIR, "data.db")
self.db.setDatabaseName(self.db_path)
if not self.db.open():
print("Could not open database!")
@@ -519,7 +536,6 @@ def change_db(self, mode):
self.init_db()
self.read_books_from_db()
if self.toolbar.db_btn.isChecked():
- # noinspection PyTypeChecker,PyCallByClass
QTimer.singleShot(0, self.toolbar.update_archived)
def delete_data(self):
@@ -613,7 +629,7 @@ def get_db_book_count(self):
def vacuum_db(self, info=True):
self.query.exec_("""VACUUM""")
if info:
- self.popup(_("Information"), _("The database is compacted!"),
+ self.popup(_("Information"), _("The database has been compacted!"),
QMessageBox.Information)
# ___ ___________________ FILE TABLE STUFF ______________________
@@ -665,6 +681,7 @@ def on_file_table_itemClicked(self, item, reset=True):
self.high_list.setCurrentRow(0) if reset else None
+ # noinspection PyTestUnpassedFixture
def populate_book_info(self, data, row):
""" Fill in the `Book Info` fields
@@ -692,6 +709,14 @@ def populate_book_info(self, data, row):
value = data["doc_pages"]
else: # older type file
value = data[stats][key]
+
+ # no total pages if reference pages are used
+ annotations = data.get("annotations") # new type metadata
+ if self.show_ref_pg and annotations is not None and len(annotations):
+ annot = annotations[1] # first annotation
+ ref_page = annot.get("pageref")
+ if ref_page and ref_page.isdigit(): # there is a ref page number
+ value = _("|Ref|")
elif key == "keywords":
keywords = data["doc_props"][key].split("\n")
value = "; ".join([i.rstrip("\\") for i in keywords])
@@ -839,6 +864,7 @@ def get_book_path(meta_path, data):
doc_path = data.get("doc_path")
if doc_path:
drive = splitdrive(meta_path)[0]
+ # noinspection PyTestUnpassedFixture
doc_path = join(drive, os.sep, *(doc_path.split("/")[3:]))
if isfile(doc_path):
book_path = doc_path
@@ -1030,8 +1056,9 @@ def on_archive(self):
def loading_thread(self, worker, args, text, clear=True):
""" Populates the file_table with different contents
"""
- if clear and self.current_view != SYNC_VIEW:
+ if clear and (self.current_view != SYNC_VIEW or self.reload_from_sync):
self.toolbar.on_clear_btn_clicked()
+ self.reload_from_sync = False
self.file_table.setSortingEnabled(False) # re-enable it after populating table
self.status.animation(True)
@@ -1165,27 +1192,11 @@ def get_item_stats(data, filename=None):
:type filename: str|unicode
:param filename: The filename to get the stats for
"""
- stats = "doc_props" if "doc_props" in data else "stats" if "stats" in data else ""
if filename: # stats from a file
- try:
- 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
+ title, authors = Base.get_title_authors(data, filename)
else: # stats from a db entry
+ stats = ("doc_props" if "doc_props" in data
+ else "stats" if "stats" in data else "")
title = data[stats]["title"]
authors = data[stats]["authors"]
@@ -1214,6 +1225,36 @@ def get_item_stats(data, filename=None):
return {"title": title, "authors": authors, "percent": percent,
"rating": rating, "status": status, "high_count": high_count}
+ @staticmethod
+ def get_title_authors(data, filename):
+ """ Returns the title and authors of a metadata file
+
+ :type data: dict
+ :param data: The dict converted lua file
+ :type filename: str|unicode
+ :param filename: The filename to get the stats for
+ """
+ stats = "doc_props" if "doc_props" in data else "stats" if "stats" in data else ""
+ try:
+ 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
+ return title, authors
+
# ___ ___________________ HIGHLIGHTS LIST STUFF _________________
def populate_high_list(self, data, path=""):
@@ -1230,12 +1271,15 @@ def populate_high_list(self, data, path=""):
self.status.act_date.isChecked() else "")
def_date_format = self.date_format == DATE_FORMAT
highlights = self.get_highlights_from_data(data, path)
+ new = data.get("annotations") is not None
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 "")
+ page = i["page"]
+ if new and self.show_ref_pg and i.get("ref_page"):
+ page = i["ref_page"]
+ page_text = _(f"Page {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 ""
@@ -1297,11 +1341,16 @@ def get_new_highlight_info(data, idx):
return # this is a bookmark not a highlight
pages = data["doc_pages"]
page = high_stuff.get("pageno", 0)
+ ref_page = high_stuff.get("pageref")
+ if ref_page and ref_page.isdigit():
+ ref_page = int(ref_page)
+ else:
+ ref_page = None
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}
+ "page": page, "ref_page": ref_page, "pages": pages, "new": True}
return highlight
@staticmethod
@@ -1647,6 +1696,17 @@ def set_highlight_sort(self):
except AttributeError: # no book selected
pass
+ def set_show_ref_pg(self):
+ """ Prefer reference page numbers if exists
+ """
+ self.show_ref_pg = not self.show_ref_pg
+ self.reload_highlights = True
+ 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
@@ -1656,7 +1716,10 @@ def sort_high4view(self, data):
if not self.high_by_page:
return data["date"]
else:
- return int(data["page"])
+ if data["new"] and self.show_ref_pg:
+ return int(data.get("ref_page", "") or data["page"])
+ else:
+ return int(data["page"])
def sort_high4write(self, data):
""" Sets the sorting method of written highlights
@@ -1724,10 +1787,8 @@ def on_high_table_customContextMenuRequested(self, point):
highlights, comments, values = self.get_highlights(col)
- high_text = _("Copy Highlights")
- com_text = _("Copy Comments")
- val_text = _("Copy {} values")
- if len(self.sel_high_view) == 1: # single selection
+ single = len(self.sel_high_view) == 1
+ if single: # single selection
high_text = _("Copy Highlight")
com_text = _("Copy Comment")
val_text = _("Copy {} value")
@@ -1742,6 +1803,10 @@ def on_high_table_customContextMenuRequested(self, point):
action.triggered.connect(self.on_edit_comment)
action.setIcon(self.ico_file_edit)
menu.addAction(action)
+ else:
+ high_text = _("Copy Highlights")
+ com_text = _("Copy Comments")
+ val_text = _("Copy {} values")
action = QAction(high_text + "\tCtrl+C", menu)
action.triggered.connect(partial(self.copy_text_2clip, highlights))
@@ -1753,10 +1818,11 @@ def on_high_table_customContextMenuRequested(self, point):
action.setIcon(self.ico_copy)
menu.addAction(action)
- action = QAction(val_text.format(HIGH_COL_NAMES[col]), menu)
- action.triggered.connect(partial(self.copy_text_2clip, values))
- action.setIcon(self.ico_copy)
- menu.addAction(action)
+ if col not in [HIGHLIGHT_H, COMMENT_H]:
+ action = QAction(val_text.format(HIGH_COL_NAMES[col]), menu)
+ action.triggered.connect(partial(self.copy_text_2clip, values))
+ action.setIcon(self.ico_copy)
+ menu.addAction(action)
action = QAction(_("Export to file"), menu)
action.triggered.connect(self.on_export)
@@ -1865,7 +1931,10 @@ def create_highlight_row(self, data):
item.setToolTip(authors)
self.high_table.setItem(0, AUTHOR_H, item)
- page = data["page"]
+ if data["new"] and self.show_ref_pg:
+ page = str(data.get("ref_page", "") or data["page"])
+ else:
+ page = str(data["page"])
item = XTableWidgetIntItem(page)
item.setToolTip(page)
item.setTextAlignment(Qt.AlignRight)
@@ -2021,7 +2090,6 @@ def create_sync_row(self, data, quiet=False):
wdg.on_fold_btn_toggled(folded)
wdg.check_data()
self.sync_table.setSortingEnabled(True)
- # noinspection PyTypeChecker
QTimer.singleShot(0, self.save_sync_groups)
def create_sync_widget(self, data):
@@ -2498,16 +2566,15 @@ def merge_new_highs(items):
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)))
+ new_hi["pageno"] = 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)
+ percent = new_bkm["pageno"] / bkm_total
+ new_bkm["pageno"] = int(round(percent * total))
info[0].clear() # repopulate the annotations
annots_upd = {}
for i, hi in enumerate(sorted(annots, key=lambda x: int(x["pageno"]))):
@@ -2904,14 +2971,13 @@ def save_multi_files(self, dir_path, format_, line_break, space):
"""
self.status.animation(True)
saved = 0
- sort_by = self.sort_high4write
for idx in self.sel_indexes:
authors, title, highlights = self.get_item_data(idx, format_)
if not highlights: # no highlights in book
continue
try:
- save_file(title, authors, highlights, dir_path,
- format_, line_break, space, sort_by)
+ save_file(title, authors, highlights, dir_path, format_,
+ line_break, space, self.sort_high4write)
saved += 1
except IOError as err: # any problem when writing (like long filename, etc.)
self.popup(_("Warning!"), _(f"Could not save the file to disk!\n{err}"))
@@ -2960,56 +3026,81 @@ def get_item_data(self, index, format_):
"""
row = index.row()
data = self.file_table.item(row, 0).data(Qt.UserRole)
+ args = {"page": self.status.act_page.isChecked(),
+ "date": self.status.act_date.isChecked(),
+ "text": self.status.act_text.isChecked(),
+ "chapter": self.status.act_chapter.isChecked(),
+ "comment": self.status.act_comment.isChecked(),
+ "ref_pg": self.show_ref_pg,
+ "html": format_ in [ONE_HTML, MANY_HTML],
+ "csv": format_ in [ONE_CSV, MANY_CSV],
+ }
+ highlights = self.get_formatted_highlights(data, args)
+ 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]:
+ authors = ""
+ return authors, title, highlights
+
+ def get_formatted_highlights(self, data, args):
+ """ Get the highlight texts for an item
+ :type data: dict
+ :param data: The item's data
+ :type args: dict
+ :param args: The arguments for the highlight texts
+ """
+ if not data: # no highlights
+ return []
highlights = []
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_)
+ if not args["ref_pg"]:
+ highlight["page"] = str(highlight["page"])
+ else:
+ highlight["page"] = str(highlight.get("ref_page", "")
+ or highlight["page"])
+ # highlight["page"] = str(highlight["page"])
+ if self.date_format != DATE_FORMAT:
+ highlight["date"] = self.get_date_text(highlight["date"])
+ formatted_high = self.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.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]:
- authors = ""
- return authors, title, highlights
+ highlights.append(self.get_formatted_high(highlight, args))
+ return highlights
- # noinspection PyTypeChecker
- def get_formatted_high(self, highlight, format_):
+ @staticmethod
+ def get_formatted_high(highlight, args):
""" Create the highlight's texts
:type highlight: dict
:param highlight: The highlight's data
- :type format_: int
- :param format_ The output format idx
- """
- linesep = "
" if format_ in [ONE_HTML, MANY_HTML] else os.linesep
- comment = highlight["comment"].replace("\n", linesep)
- chapter = (highlight["chapter"].replace("\n", linesep)
- if self.status.act_chapter.isChecked() else "")
- high_text = (highlight["text"].replace("\n", linesep)
- if self.status.act_text.isChecked() else "")
+ :type args: dict
+ :param args: The arguments for the highlight texts
+ """
+ line_break = "
" if args["html"] else os.linesep
+ chapter = (highlight["chapter"].replace("\n", line_break)
+ if args["chapter"] else "")
+ high_text = highlight["text"].replace("\n", line_break) if args["text"] else ""
+ comment = highlight["comment"].replace("\n", line_break)
date = highlight["date"]
- 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 = 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 "")
+ line_break2 = os.linesep if args["text"] and comment else ""
+ if args["csv"]:
+ page_text = highlight["page"] if args["page"] else ""
+ date_text = date if args["date"] else ""
+ high_comment = comment if args["comment"] and comment else ""
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 ""
+ page_text = "Page " + str(highlight["page"]) if args["page"] else ""
+ date_text = "[" + date + "]" if args["date"] else ""
high_comment = (line_break2 + "● " + comment
- if self.status.act_comment.isChecked() and comment else "")
+ if args["comment"] and comment else "")
return date_text, high_comment, high_text, page_text, chapter
def save_sel_highlights(self):
@@ -3027,7 +3118,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("md")
+ md_out = extra.startswith("markdown")
if text_out:
ext = ".txt"
text = ""
@@ -3060,26 +3151,33 @@ def save_sel_highlights(self):
if md_out and comment:
comment = comment.replace("\n", " \n")
+ if data["new"] and self.show_ref_pg and data.get("ref_page"):
+ page = data["ref_page"]
+ else:
+ page = data["page"]
+
if text_out:
- txt = (f"{data['title']} [{data['authors']}]\nPage {data['page']} "
+ txt = (f"{data['title']} [{data['authors']}]\nPage {page} "
f"[{data['date']}]\n[{data['chapter']}]\n{data['text']}{comment}")
text += txt + "\n\n"
elif html_out:
left = f"{data['title']} [{data['authors']}]"
- right = f"Page {data['page']} [{data['date']}]"
+ right = f"Page {page} [{data['date']}]"
text += HIGH_BLOCK % {"page": left, "date": right, "comment": comment,
"highlight": data["text"],
"chapter": data["chapter"]}
text += "\n"
elif csv_out:
- text += get_csv_row(data) + "\n"
+ csv_data = data.copy()
+ csv_data["page"] = str(page)
+ text += get_csv_row(csv_data) + "\n"
elif md_out:
txt = data["text"].replace("\n", " \n")
chapter = data["chapter"]
if chapter:
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'*Page {page} [{data["date"]}]* \n'
f'{chapter}{txt}{comment}\n')
else:
print("Unknown format export!")
@@ -3129,6 +3227,7 @@ def settings_load(self):
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)
+ self.show_ref_pg = app_config.get("show_ref_pg", True)
else:
self.status.theme_box.setCurrentIndex(self.theme)
if self.highlight_width:
@@ -3146,6 +3245,7 @@ def restore_windows(self):
# self.restoreState(self.unpickle("state")) # 2fix makes window wider (if small)
self.splitter.restoreState(self.unpickle("splitter"))
self.header_main.restoreState(self.unpickle("header_main"))
+ self.header_high_view.restoreState(self.unpickle("header_high_view"))
self.filter.restoreGeometry(self.unpickle("filter_geometry"))
self.about.restoreGeometry(self.unpickle("about_geometry"))
QTimer.singleShot(200, self.get_header_width)
@@ -3165,6 +3265,7 @@ def settings_save(self):
"state": self.pickle(self.saveState()),
"splitter": self.pickle(self.splitter.saveState()),
"header_main": self.pickle(self.header_main.saveState()),
+ "header_high_view": self.pickle(self.header_high_view.saveState()),
"filter_geometry": self.pickle(self.filter.saveGeometry()),
"about_geometry": self.pickle(self.about.saveGeometry()),
"col_sort_asc": self.col_sort_asc, "col_sort": self.col_sort,
@@ -3174,7 +3275,8 @@ def settings_save(self):
"last_dir": self.last_dir, "alt_title_sort": self.alt_title_sort,
"archive_warning": self.archive_warning, "exit_msg": self.exit_msg,
"current_view": self.current_view, "db_mode": self.db_mode,
- "high_by_page": self.high_by_page, "date_vacuumed": self.date_vacuumed,
+ "high_by_page": self.high_by_page, "show_ref_pg": self.show_ref_pg,
+ "date_vacuumed": self.date_vacuumed,
"show_info": self.fold_btn.isChecked(), "date_format": self.date_format,
"theme": self.theme, "show_items": self.show_items,
"skip_version": self.skip_version, "opened_times": self.opened_times,
@@ -3183,16 +3285,17 @@ def settings_save(self):
}
try:
for k, v in config.items():
- if type(v) == bytes:
- # noinspection PyArgumentList
- config[k] = str(v, encoding="latin")
+
+ if isinstance(v, bytes):
+ try:
+ config[k] = v.decode("latin1")
+ except UnicodeDecodeError as e:
+ print(f"Error decoding bytes to string: {e}")
+ config[k] = v.decode("latin1", errors="ignore")
config_json = json.dumps(config, sort_keys=True, indent=4)
with gzip.GzipFile(join(SETTINGS_DIR, str("settings.json.gz")),
"w+") as gz_file:
- try:
- gz_file.write(config_json)
- except TypeError: # Python3
- gz_file.write(config_json.encode("utf8"))
+ gz_file.write(config_json.encode("utf8"))
except IOError as error:
print("On saving settings:", error)
@@ -3255,11 +3358,11 @@ def popup(self, title, text, icon=QMessageBox.Warning, buttons=1,
"""
popup = XMessageBox(self)
popup.setWindowIcon(self.ico_app)
- if type(icon) == QMessageBox.Icon:
+ if isinstance(icon, QMessageBox.Icon):
popup.setIcon(icon)
- elif type(icon) == str:
+ elif isinstance(icon, str):
popup.setIconPixmap(QPixmap(icon))
- elif type(icon) == QPixmap:
+ elif isinstance(icon, QPixmap):
popup.setIconPixmap(icon)
else:
raise TypeError("Wrong icon type!")
@@ -3291,12 +3394,10 @@ def error(self, error_txt):
def passed_files(self):
""" Command line parameters that are passed to the program.
"""
- # args = QApplication.instance().arguments()
- try:
- if sys.argv[1]:
- self.on_file_table_fileDropped(sys.argv[1:])
- except IndexError:
- pass
+ if len(sys.argv) > 1 and sys.argv[1].endswith("Portable.exe"):
+ del sys.argv[1]
+ if len(sys.argv) > 1:
+ self.on_file_table_fileDropped(sys.argv[1:])
def open_file(self, path):
""" Opens a file with its associated app
@@ -3605,6 +3706,8 @@ def parse_args(self):
default=False)
self.parser.add_argument("-s", "--sort_page", action="store_true", default=False,
help="Sort highlights by page, otherwise sort by date")
+ self.parser.add_argument("-r", "--ref_page", action="store_true", default=True,
+ help="Use reference page numbers if they exist")
self.parser.add_argument("-m", "--merge", action="store_true", default=False,
help="Merge the highlights of all input books in a "
"single file, otherwise exports every book's "
@@ -3777,68 +3880,19 @@ def cli_get_item_data(self, file_, args):
:param args: The item's arguments
"""
data = decode_data(file_)
- highlights = []
-
- 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"]
- except KeyError: # older type file
- title = splitext(basename(file_))[0]
- try:
- name = title.split("#] ")[1]
- title = splitext(name)[0]
- except IndexError: # no "#] " in filename
- pass
- if not title:
- try:
- name = file_.split("#] ")[1]
- title = splitext(name)[0]
- except IndexError: # no "#] " in filename
- title = NO_TITLE
+ args_ = {"page": not args.no_page,
+ "date": not args.no_date,
+ "text": not args.no_highlight,
+ "chapter": not args.no_chapter,
+ "comment": not args.no_comment,
+ "ref_pg": args.ref_page,
+ "html": args.html,
+ "csv": args.csv,
+ }
+ highlights = self.base.get_formatted_highlights(data, args_)
+ title, authors = self.base.get_title_authors(data, file_)
return authors, title, highlights
- @staticmethod
- def cli_get_formatted_high(highlight, args):
- """ Return the highlight's info in a formatted way
-
- :type highlight: dict
- :param highlight: The highlight's data
- :type args: argparse.Namespace
- :param args: The parsed cli args
- """
- nl = "
" if args.html else os.linesep
- chapter = highlight["chapter"].replace("\n", nl) if not args.no_chapter else ""
- high_text = highlight["text"]
- high_text = high_text.replace("\n", nl) if not args.no_highlight else ""
- comment = highlight["comment"].replace("\n", nl)
- date = highlight["date"]
- line_break2 = os.linesep if not args.no_highlight and comment else ""
- if args.csv:
- 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 " + 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 "")
- return date_text, high_comment, high_text, page_text, chapter
-
@staticmethod
def get_lua_files(dropped):
""" Return the paths to the .lua metadata files
@@ -3900,41 +3954,6 @@ def cli_sort(args, data):
else:
return data[0]
- @staticmethod
- def get_name(data, meta_path, title_counter):
- """ Return the name of the book entry
-
- :type data: dict
- :param data: The book's metadata
- :type meta_path: str|unicode
- :param meta_path: The book's metadata path
- :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"]
- except KeyError: # older type file
- title = splitext(basename(meta_path))[0]
- try:
- name = title.split("#] ")[1]
- title = splitext(name)[0]
- except IndexError: # no "#] " in filename
- pass
- if not title:
- try:
- name = meta_path.split("#] ")[1]
- title = splitext(name)[0]
- except IndexError: # no "#] " in filename
- title = NO_TITLE + str(title_counter[0])
- title_counter[0] += 1
- name = title
- if authors:
- name = f"{authors} - {title}"
- return name
-
if __name__ == "__main__":
app = KOHighlights(sys.argv)
diff --git a/secondary.py b/secondary.py
index 9e3257a..08ea974 100644
--- a/secondary.py
+++ b/secondary.py
@@ -1,4 +1,7 @@
# coding=utf-8
+from copy import deepcopy
+from pprint import pprint
+
from boot_config import *
from boot_config import _
import re
@@ -41,7 +44,7 @@ def decode_data(path):
with open(path, "r", encoding="utf8", newline="\n") as txt_file:
header, data = txt_file.read().split("\n", 1)
data = lua.decode(data[7:].replace("--", "—"))
- if type(data) == dict:
+ if type(data) is dict:
data["original_header"] = header
return data
@@ -879,6 +882,7 @@ def __init__(self, parent=None):
# self.size_menu.aboutToShow.connect(self.create_size_menu)
self.db_menu = QMenu()
+ self.db_menu.setToolTipsVisible(True)
self.db_menu.aboutToShow.connect(self.create_db_menu)
self.db_btn.setMenu(self.db_menu)
@@ -1050,7 +1054,7 @@ def on_delete_btn_clicked(self):
def on_clear_btn_clicked(self):
""" The `Clear List` button is pressed
"""
- if self.base.current_view == SYNC_VIEW:
+ if self.base.current_view == SYNC_VIEW and not self.base.reload_from_sync:
return
self.base.loaded_paths.clear()
self.base.reload_highlights = True
@@ -1071,18 +1075,28 @@ def create_db_menu(self):
self.db_menu.clear()
action = QAction(_("Create new database"), self.db_menu)
action.setIcon(self.base.ico_db_add)
+ action.setToolTip(_("Create a new database file"))
action.triggered.connect(partial(self.base.change_db, NEW_DB))
self.db_menu.addAction(action)
action = QAction(_("Reload database"), self.db_menu)
action.setIcon(self.base.ico_refresh)
+ action.setToolTip(_("Reload the current database"))
action.triggered.connect(partial(self.base.change_db, RELOAD_DB))
self.db_menu.addAction(action)
action = QAction(_("Change database"), self.db_menu)
action.setIcon(self.base.ico_db_open)
+ action.setToolTip(_("Load a different database file"))
action.triggered.connect(partial(self.base.change_db, CHANGE_DB))
self.db_menu.addAction(action)
+
+ action = QAction(_("Compact database"), self.db_menu)
+ action.setIcon(self.base.ico_db_compact)
+ action.setToolTip(_("Compact the database to minimize file size"))
+ action.triggered.connect(partial(self.base.vacuum_db, True))
+ self.db_menu.addAction(action)
+
if QT6: # QT6 requires exec() instead of exec_()
self.db_menu.exec_ = getattr(self.db_menu, "exec")
@@ -1090,7 +1104,8 @@ def change_view(self):
""" Changes what is shown in the app
"""
reloaded = False
- if not self.sync_view_btn.isChecked(): # don't reload when coming from Sync view
+ if self.base.reload_from_sync or not self.sync_view_btn.isChecked():
+ self.base.reload_from_sync = False
reloaded = (self.update_archived()
if self.db_btn.isChecked() else self.update_loaded())
if self.books_view_btn.isChecked(): # Books view
@@ -1134,8 +1149,9 @@ def update_archived(self):
self.base.db_mode = True
self.base.reload_highlights = True
self.base.read_books_from_db()
- text = _(f"Loading {APP_NAME} database")
- self.base.loading_thread(DBLoader, self.base.books, text)
+ self.base.load_db_rows()
+ # 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/'
'update one or more books, select them in the "Loaded" '
@@ -1553,6 +1569,7 @@ def __init__(self, parent=None):
act.triggered.connect(partial(self.on_show_items, act))
self.show_menu = QMenu(self)
+ self.show_menu.setToolTipsVisible(True)
self.show_menu.aboutToShow.connect(self.get_show_menu)
self.show_items_btn.setMenu(self.show_menu)
@@ -1564,14 +1581,26 @@ def get_show_menu(self):
act.setChecked(self.base.show_items[idx])
self.show_menu.addAction(act)
+ self.show_menu.addSeparator()
+ self.show_menu.addSeparator()
+ self.show_menu.addSeparator()
+
action = QAction(_("Date Format"), self.show_menu)
action.setIcon(self.base.ico_calendar)
action.triggered.connect(self.set_date_format)
+ action.setToolTip(_("Change the way the date is displayed"))
+ self.show_menu.addAction(action)
+
+ action = QAction(_("Reference page numbers"), self.show_menu)
+ action.setCheckable(True)
+ action.setChecked(self.base.show_ref_pg)
+ action.triggered.connect(self.base.set_show_ref_pg)
+ action.setToolTip(_("Use reference page numbers\nif they are available"))
self.show_menu.addAction(action)
sort_menu = QMenu(self)
sort_menu.setIcon(self.base.ico_sort)
- sort_menu.setTitle(_("Sort by"))
+ sort_menu.setTitle(_("Sort Highlights by"))
group = QActionGroup(self)
action = QAction(_("Date"), sort_menu)
@@ -1723,23 +1752,39 @@ def on_group_frm_customContextMenuRequested(self, point):
:param point: The point of the click
"""
menu = QMenu(self)
+ menu.setToolTipsVisible(True)
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.setToolTip(_("Change the name of the Sync group"))
action.triggered.connect(self.on_rename)
menu.addAction(action)
action = QAction(_("Sync group"), menu)
action.setIcon(self.base.ico_files_merge)
+ action.setToolTip(_("Sync the Sync group items"))
action.triggered.connect(self.on_sync_btn_clicked)
menu.addAction(action)
+ action = QAction(_("Load group items"), menu)
+ action.setIcon(self.base.ico_folder_open)
+ action.setToolTip(_("Load the Sync group items to the Books View"))
+ action.triggered.connect(self.load_group_items)
+ menu.addAction(action)
+
+ action = QAction(_("Copy Archived to group"), menu)
+ action.setIcon(self.base.ico_files_merge)
+ action.setToolTip(_("Copy the archived version to all Sync group items"))
+ action.triggered.connect(self.archived_to_group)
+ menu.addAction(action)
+
menu.addSeparator()
action = QAction(_("Delete selected"), menu)
action.setIcon(self.base.ico_delete)
+ action.setToolTip(_("Delete the selected Sync group items"))
action.triggered.connect(self.base.toolbar.on_delete_btn_clicked)
menu.addAction(action)
@@ -1770,7 +1815,6 @@ def setup_buttons(self):
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()
@@ -1991,6 +2035,74 @@ def update_data(self):
self.data = data
self.base.save_sync_groups()
+ def load_group_items(self):
+ """ Loads the group's items in the Books view
+ """
+ self.base.toolbar.reload_from_sync = True
+ self.base.toolbar.books_view_btn.setChecked(True)
+ self.base.toolbar.change_view()
+ self.base.toolbar.loaded_btn.click()
+ self.base.on_file_table_fileDropped([item["path"] for item in self.data["items"]])
+
+ def archived_to_group(self):
+ """ Copies the archived data to all the items of the group
+ """
+ path = self.data["items"][0]["path"]
+ data = decode_data(path)
+ idx = self.base.check4archive_merge({"path": path, "data": data})
+ no_archive_txt = _("Could not find an archived version of the book's metadata")
+ if idx is False:
+ self.base.popup(_("Error"), no_archive_txt, icon=QMessageBox.Critical)
+ return
+ old_format_txt = _("The metadata file is of an older, not supported version.")
+ if not self.new_format:
+ self.base.popup(_("Error"), old_format_txt, icon=QMessageBox.Critical)
+ return
+ warn_txt = _("All the Group versions will be overwritten with the archived "
+ "version!\n\nAre you sure you want to continue?")
+ popup = self.base.popup(_("Warning"), warn_txt, icon=QMessageBox.Warning,
+ buttons=2, button_text=(_("Yes"), _("Cancel")))
+ if popup.buttonRole(popup.clickedButton()) == QMessageBox.RejectRole:
+ return
+
+ arch_data = self.base.books[idx]["data"] # archived data
+ arch_total = arch_data.get("doc_pages",
+ arch_data.get("stats", {}).get("pages", 0))
+ for item in self.data["items"]:
+ item_data = item["data"]
+ item_total = item_data.get("doc_pages",
+ item_data.get("stats", {}).get("pages", 0))
+ item_data["annotations"] = deepcopy(arch_data["annotations"])
+ if arch_total != item_total:
+ self.recalculate_pages(item_data, item_total, arch_total)
+ item_data["annotations_externally_modified"] = True
+ self.base.save_book_data(item["path"], item_data)
+
+ self.base.reload_from_sync = True
+ if not self.base.db_mode:
+ self.base.db_mode = True # need this to trigger the reload of the files
+ self.base.books2reload = self.base.loaded_paths.copy()
+ self.base.toolbar.update_loaded()
+
+ self.base.popup(_("Info"), _("The metadata was successfully updated!"),
+ icon=QMessageBox.Information)
+
+ @staticmethod
+ def recalculate_pages(item_data, item_total, arch_total):
+ """ Recalculates the page number of the annotations
+ based on the total pages number
+
+ :type item_data: dict
+ :param item_data: The item's data
+ :type item_total: int
+ :param item_total: The total number of pages of the item
+ :type arch_total: int
+ :param arch_total: The total number of pages of the archived item
+ """
+ for annot in item_data["annotations"].values():
+ percent = int(annot["pageno"]) / arch_total
+ annot["pageno"] = int(round(percent * item_total))
+
class SyncItem(QWidget, Ui_SyncItem):
@@ -2004,8 +2116,8 @@ def __init__(self, parent=None):
self.base = parent
self.group = SyncGroup(self.base)
self.def_btn_icos = []
- self.buttons = [(self.add_btn, "F"),
- (self.del_btn, "J")]
+ self.buttons = [(self.add_btn, "+"),
+ (self.del_btn, "-")]
self.setup_buttons()
self.setup_icons()
self.ok = True
@@ -2018,7 +2130,6 @@ def setup_buttons(self):
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()
@@ -2083,7 +2194,6 @@ def on_add_btn_clicked(self, ):
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()
@@ -2095,7 +2205,6 @@ def on_del_btn_clicked(self, ):
return
self.group.remove_item(self)
self.group.update_data()
- # noinspection PyTypeChecker
QTimer.singleShot(100, self.group.reset_group_height)
# if __name__ == "__main__":
diff --git a/stuff/font.ttf b/stuff/font.ttf
index 3812d43..3a8944b 100644
Binary files a/stuff/font.ttf and b/stuff/font.ttf differ