diff --git a/TODO b/TODO index d5836a8..297da8a 100644 --- a/TODO +++ b/TODO @@ -79,7 +79,6 @@ TODO ✓ Define every widget in code Bugs: Deselecting all directories in the settings dialog also filters out manually added books - PDF year Secondary: Graphical themes diff --git a/lector/__main__.py b/lector/__main__.py index 35fcea7..bcdddb5 100755 --- a/lector/__main__.py +++ b/lector/__main__.py @@ -262,8 +262,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): if self.settings['main_window_headers']: for count, i in enumerate(self.settings['main_window_headers']): self.tableView.horizontalHeader().resizeSection(count, int(i)) - self.tableView.horizontalHeader().resizeSection(5, 1) - self.tableView.horizontalHeader().setStretchLastSection(True) + self.tableView.horizontalHeader().resizeSection(5, 30) + self.tableView.horizontalHeader().setStretchLastSection(False) self.tableView.horizontalHeader().sectionClicked.connect( self.lib_ref.table_proxy_model.sort_table_columns) self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) @@ -822,7 +822,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): last_accessed_time = None if action == readAction: last_accessed_time = QtCore.QDateTime().currentDateTime() - position_perc = 100 + position_perc = 1 self.lib_ref.view_model.setData(i, metadata, QtCore.Qt.UserRole + 3) self.lib_ref.view_model.setData(i, position_perc, QtCore.Qt.UserRole + 7) @@ -916,8 +916,11 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): self.settingsDialog.hide() self.definitionDialog.hide() self.temp_dir.remove() - for i in self.active_bookmark_docks: - i.setVisible(False) + for this_dock in self.active_bookmark_docks: + try: + this_dock.setVisible(False) + except RuntimeError: + pass self.settings['last_open_books'] = [] if self.tabWidget.count() > 1: diff --git a/lector/delegates.py b/lector/delegates.py index 7738470..0ebbdf2 100644 --- a/lector/delegates.py +++ b/lector/delegates.py @@ -33,13 +33,8 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate): # painter.fillRect(option.rect, QtGui.QColor().fromRgb(255, 0, 0, 20)) option = option.__class__(option) - title = index.data(QtCore.Qt.UserRole) file_exists = index.data(QtCore.Qt.UserRole + 5) - metadata = index.data(QtCore.Qt.UserRole + 3) - - position = metadata['position'] - if position: - is_read = position['is_read'] + position_percent = index.data(QtCore.Qt.UserRole + 7) # The shadow pixmap currently is set to 420 x 600 # Only draw the cover shadow in case the setting is enabled @@ -64,26 +59,10 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate): return QtWidgets.QStyledItemDelegate.paint(self, painter, option, index) - if position: - if is_read: - progress = total = -1 - else: - try: - progress = position['current_block'] - total = position['total_blocks'] - if progress == total == 0: - raise KeyError - except KeyError: - # For comics and older database entries - # It looks ugly but leave it like this - try: - progress = position['current_chapter'] - total = position['total_chapters'] - except KeyError: - return + if position_percent: read_icon = pie_chart.pixmapper( - progress, total, self.temp_dir, 36) + position_percent, self.temp_dir, self.parent.settings['consider_read_at'], 36) x_draw = option.rect.bottomRight().x() - 30 y_draw = option.rect.bottomRight().y() - 35 diff --git a/lector/library.py b/lector/library.py index cc155b7..5d9ad02 100644 --- a/lector/library.py +++ b/lector/library.py @@ -101,18 +101,22 @@ class Library: if position: position = pickle.loads(position) if position['is_read']: - position_perc = 100 + position_perc = 1 else: try: position_perc = ( - position['current_chapter'] * 100 / position['total_chapters']) - except KeyError: - position_perc = None + position['current_block'] / position['total_blocks']) + except (KeyError, ZeroDivisionError): + try: + position_perc = ( + position['current_chapter'] / position['total_chapters']) + except KeyError: + position_perc = None try: file_exists = os.path.exists(path) except UnicodeEncodeError: - print('Error with unicode encoding in the library module') + print('Library: Unicode encoding error') all_metadata = { 'title': title, @@ -176,7 +180,9 @@ class Library: self.parent.listView.setModel(self.item_proxy_model) self.table_proxy_model = TableProxyModel( - self.parent.temp_dir.path(), self.parent.tableView.horizontalHeader()) + self.parent.temp_dir.path(), + self.parent.tableView.horizontalHeader(), + self.parent.settings['consider_read_at']) self.table_proxy_model.setSourceModel(self.view_model) self.table_proxy_model.setSortCaseSensitivity(False) self.parent.tableView.setModel(self.table_proxy_model) @@ -223,7 +229,8 @@ class Library: 1: 1, 2: 2, 3: 9, - 4: 12} + 4: 12, + 5: 7} # Sorting according to roles and the drop down in the library toolbar self.item_proxy_model.setSortRole( @@ -231,7 +238,7 @@ class Library: # This can be expanded to other fields by appending to the list sort_order = QtCore.Qt.AscendingOrder - if self.parent.libraryToolBar.sortingBox.currentIndex() in [3, 4]: + if self.parent.libraryToolBar.sortingBox.currentIndex() in [3, 4, 5]: sort_order = QtCore.Qt.DescendingOrder self.item_proxy_model.sort(0, sort_order) diff --git a/lector/models.py b/lector/models.py index aa2d2cd..c87d31a 100644 --- a/lector/models.py +++ b/lector/models.py @@ -65,9 +65,10 @@ class ItemProxyModel(QtCore.QSortFilterProxyModel): class TableProxyModel(QtCore.QSortFilterProxyModel): - def __init__(self, temp_dir, tableViewHeader, parent=None): + def __init__(self, temp_dir, tableViewHeader, consider_read_at, parent=None): super(TableProxyModel, self).__init__(parent) self.tableViewHeader = tableViewHeader + self.consider_read_at = consider_read_at self._translate = QtCore.QCoreApplication.translate title_string = self._translate('TableProxyModel', 'Title') @@ -101,6 +102,7 @@ class TableProxyModel(QtCore.QSortFilterProxyModel): return self.header_data[column] except IndexError: print('Table proxy model: Can\'t find header for column', column) + # The column will be called IndexError. Not a typo. return 'IndexError' def flags(self, index): @@ -121,36 +123,16 @@ class TableProxyModel(QtCore.QSortFilterProxyModel): return_pixmap = None file_exists = item.data(QtCore.Qt.UserRole + 5) - metadata = item.data(QtCore.Qt.UserRole + 3) - progress_perc = item.data(QtCore.Qt.UserRole + 7) - - position = metadata['position'] - if position: - is_read = position['is_read'] + position_percent = item.data(QtCore.Qt.UserRole + 7) if not file_exists: return pie_chart.pixmapper( -1, None, None, QtCore.Qt.SizeHintRole + 10) - if position: - if is_read: - progress = total = -2 - else: - try: - progress = position['current_block'] - total = position['total_blocks'] - - if progress == total == 0: - raise KeyError - except KeyError: - try: - progress = position['current_chapter'] - total = position['total_chapters'] - except KeyError: - return - + if position_percent: return_pixmap = pie_chart.pixmapper( - progress, total, self.temp_dir, + position_percent, self.temp_dir, + self.consider_read_at, QtCore.Qt.SizeHintRole + 10) return return_pixmap @@ -218,6 +200,7 @@ class ProxyModelsCommonFunctions: title = model.data(this_index, QtCore.Qt.UserRole) author = model.data(this_index, QtCore.Qt.UserRole + 1) tags = model.data(this_index, QtCore.Qt.UserRole + 4) + progress = model.data(this_index, QtCore.Qt.UserRole + 7) directory_name = model.data(this_index, QtCore.Qt.UserRole + 10) directory_tags = model.data(this_index, QtCore.Qt.UserRole + 11) last_accessed = model.data(this_index, QtCore.Qt.UserRole + 12) @@ -226,6 +209,10 @@ class ProxyModelsCommonFunctions: if self.parent_model.sorting_box_position == 4 and not last_accessed: return False + # Hide untouched files when sorting by progress + if self.parent_model.sorting_box_position == 5 and not progress: + return False + if self.parent_model.active_library_filters: if directory_name not in self.parent_model.active_library_filters: return False diff --git a/lector/resources/pie_chart.py b/lector/resources/pie_chart.py index ec93c10..5a2cb2d 100644 --- a/lector/resources/pie_chart.py +++ b/lector/resources/pie_chart.py @@ -94,16 +94,18 @@ def generate_pie(progress_percent, temp_dir=None): return lSvg -def pixmapper(progress, total, temp_dir, size): +def pixmapper(position_percent, temp_dir, consider_read_at, size): # A current_chapter of -1 implies the files does not exist - # A chapter number == Total chapters implies the file is unread - return_pixmap = None + # position_percent and consider_read_at are expected as a <1 decimal value - if progress == -1: + return_pixmap = None + consider_read_at = consider_read_at / 100 + + if position_percent == -1: return_pixmap = QtGui.QIcon(':/images/error.svg').pixmap(size) return return_pixmap - if progress >= .95 * total: # Consider book read @ 95% progress + if position_percent >= consider_read_at: # Consider book read @ 95% progress return_pixmap = QtGui.QIcon(':/images/checkmark.svg').pixmap(size) else: @@ -111,8 +113,7 @@ def pixmapper(progress, total, temp_dir, size): # See if saving the svg to disk can be avoided # Maybe make the alignment a little more uniform across emblems - progress_percent = int(progress * 100 / total) - generate_pie(progress_percent, temp_dir) + generate_pie(int(position_percent * 100), temp_dir) svg_path = os.path.join(temp_dir, 'lector_progress.svg') return_pixmap = QtGui.QIcon(svg_path).pixmap(size - 4) ## The -4 looks more proportional diff --git a/lector/resources/raw/settings.ui b/lector/resources/raw/settings.ui index a79a80b..7a09e7c 100644 --- a/lector/resources/raw/settings.ui +++ b/lector/resources/raw/settings.ui @@ -6,7 +6,7 @@ 0 0 - 1088 + 1119 612 @@ -47,6 +47,74 @@ + + + + + + + + + 0 + 0 + + + + Consider book read at percent + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 90 + + + 95 + + + + + + + + + + + Restart application to see changes + + + Icon theme: + + + + + + + Restart application to see changes + + + &Dark + + + + + + + Restart application to see changes + + + L&ight + + + + + + + @@ -88,40 +156,6 @@ - - - - - - Restart application to see changes - - - Icon theme: - - - - - - - Restart application to see changes - - - &Dark - - - - - - - Restart application to see changes - - - L&ight - - - - - diff --git a/lector/resources/settingswindow.py b/lector/resources/settingswindow.py index 947f670..f2d5ff6 100644 --- a/lector/resources/settingswindow.py +++ b/lector/resources/settingswindow.py @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(1088, 612) + Dialog.resize(1119, 612) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") self.listView = QtWidgets.QListView(Dialog) @@ -46,6 +46,38 @@ class Ui_Dialog(object): self.gridLayout_4.setObjectName("gridLayout_4") self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") + self.horizontalLayout_13 = QtWidgets.QHBoxLayout() + self.horizontalLayout_13.setObjectName("horizontalLayout_13") + self.horizontalLayout_14 = QtWidgets.QHBoxLayout() + self.horizontalLayout_14.setObjectName("horizontalLayout_14") + self.readAtLabel = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.readAtLabel.sizePolicy().hasHeightForWidth()) + self.readAtLabel.setSizePolicy(sizePolicy) + self.readAtLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.readAtLabel.setObjectName("readAtLabel") + self.horizontalLayout_14.addWidget(self.readAtLabel) + self.readAtPercent = QtWidgets.QSpinBox(self.groupBox) + self.readAtPercent.setMinimum(90) + self.readAtPercent.setProperty("value", 95) + self.readAtPercent.setObjectName("readAtPercent") + self.horizontalLayout_14.addWidget(self.readAtPercent) + self.horizontalLayout_13.addLayout(self.horizontalLayout_14) + self.horizontalLayout_7 = QtWidgets.QHBoxLayout() + self.horizontalLayout_7.setObjectName("horizontalLayout_7") + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.horizontalLayout_7.addWidget(self.label) + self.darkIconsRadio = QtWidgets.QRadioButton(self.groupBox) + self.darkIconsRadio.setObjectName("darkIconsRadio") + self.horizontalLayout_7.addWidget(self.darkIconsRadio) + self.lightIconsRadio = QtWidgets.QRadioButton(self.groupBox) + self.lightIconsRadio.setObjectName("lightIconsRadio") + self.horizontalLayout_7.addWidget(self.lightIconsRadio) + self.horizontalLayout_13.addLayout(self.horizontalLayout_7) + self.verticalLayout_2.addLayout(self.horizontalLayout_13) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.refreshLibrary = QtWidgets.QCheckBox(self.groupBox) @@ -66,18 +98,6 @@ class Ui_Dialog(object): self.verticalLayout_2.addLayout(self.horizontalLayout_3) self.horizontalLayout_9 = QtWidgets.QHBoxLayout() self.horizontalLayout_9.setObjectName("horizontalLayout_9") - self.horizontalLayout_7 = QtWidgets.QHBoxLayout() - self.horizontalLayout_7.setObjectName("horizontalLayout_7") - self.label = QtWidgets.QLabel(self.groupBox) - self.label.setObjectName("label") - self.horizontalLayout_7.addWidget(self.label) - self.darkIconsRadio = QtWidgets.QRadioButton(self.groupBox) - self.darkIconsRadio.setObjectName("darkIconsRadio") - self.horizontalLayout_7.addWidget(self.darkIconsRadio) - self.lightIconsRadio = QtWidgets.QRadioButton(self.groupBox) - self.lightIconsRadio.setObjectName("lightIconsRadio") - self.horizontalLayout_7.addWidget(self.lightIconsRadio) - self.horizontalLayout_9.addLayout(self.horizontalLayout_7) self.autoTags = QtWidgets.QCheckBox(self.groupBox) self.autoTags.setObjectName("autoTags") self.horizontalLayout_9.addWidget(self.autoTags) @@ -283,17 +303,18 @@ class Ui_Dialog(object): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Settings")) self.groupBox.setTitle(_translate("Dialog", "Library")) - self.refreshLibrary.setText(_translate("Dialog", "Startup: Refresh library")) - self.fileRemember.setText(_translate("Dialog", "Remember open files")) - self.coverShadows.setText(_translate("Dialog", "Cover shadows")) - self.performCulling.setToolTip(_translate("Dialog", "Enabling reduces startup time and memory usage")) - self.performCulling.setText(_translate("Dialog", "Load covers only when needed")) + self.readAtLabel.setText(_translate("Dialog", "Consider book read at percent")) self.label.setToolTip(_translate("Dialog", "Restart application to see changes")) self.label.setText(_translate("Dialog", "Icon theme: ")) self.darkIconsRadio.setToolTip(_translate("Dialog", "Restart application to see changes")) self.darkIconsRadio.setText(_translate("Dialog", "&Dark")) self.lightIconsRadio.setToolTip(_translate("Dialog", "Restart application to see changes")) self.lightIconsRadio.setText(_translate("Dialog", "L&ight")) + self.refreshLibrary.setText(_translate("Dialog", "Startup: Refresh library")) + self.fileRemember.setText(_translate("Dialog", "Remember open files")) + self.coverShadows.setText(_translate("Dialog", "Cover shadows")) + self.performCulling.setToolTip(_translate("Dialog", "Enabling reduces startup time and memory usage")) + self.performCulling.setText(_translate("Dialog", "Load covers only when needed")) self.autoTags.setText(_translate("Dialog", "Generate tags from files")) self.groupBox_2.setTitle(_translate("Dialog", "Reading")) self.hideScrollBars.setToolTip(_translate("Dialog", "Horizontal scrolling with Alt + Scroll\n" diff --git a/lector/settings.py b/lector/settings.py index 537f15e..fee4442 100644 --- a/lector/settings.py +++ b/lector/settings.py @@ -105,6 +105,7 @@ class Settings: self.parent.settings['hide_scrollbars'] = literal_eval(self.settings.value( 'hideScrollBars', 'False').capitalize()) self.parent.settings['scroll_speed'] = int(self.settings.value('scrollSpeed', 7)) + self.parent.settings['consider_read_at'] = int(self.settings.value('considerReadAt', 95)) self.settings.endGroup() self.settings.beginGroup('dialogSettings') @@ -176,6 +177,7 @@ class Settings: self.settings.setValue('cachingEnabled', current_settings['caching_enabled']) self.settings.setValue('hideScrollBars', current_settings['hide_scrollbars']) self.settings.setValue('scrollSpeed', current_settings['scroll_speed']) + self.settings.setValue('considerReadAt', current_settings['consider_read_at']) self.settings.endGroup() self.settings.beginGroup('dialogSettings') diff --git a/lector/settingsdialog.py b/lector/settingsdialog.py index 15b9293..dedfcf1 100644 --- a/lector/settingsdialog.py +++ b/lector/settingsdialog.py @@ -92,6 +92,7 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog): self.cachingEnabled.setChecked(self.parent.settings['caching_enabled']) self.hideScrollBars.setChecked(self.parent.settings['hide_scrollbars']) self.scrollSpeedSlider.setValue(self.parent.settings['scroll_speed']) + self.readAtPercent.setValue(self.parent.settings['consider_read_at']) self.autoTags.clicked.connect(self.manage_checkboxes) self.coverShadows.clicked.connect(self.manage_checkboxes) @@ -101,6 +102,7 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog): self.cachingEnabled.clicked.connect(self.manage_checkboxes) self.hideScrollBars.clicked.connect(self.manage_checkboxes) self.scrollSpeedSlider.valueChanged.connect(self.change_scroll_speed) + self.readAtPercent.valueChanged.connect(self.change_read_at) # Generate the QStandardItemModel for the listView self.listModel = QtGui.QStandardItemModel() @@ -318,9 +320,12 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog): 2: 'hi'} self.parent.settings['dictionary_language'] = language_dict[self.languageBox.currentIndex()] - def change_scroll_speed(self, event): + def change_scroll_speed(self, event=None): self.parent.settings['scroll_speed'] = self.scrollSpeedSlider.value() + def change_read_at(self, event=None): + self.parent.settings['consider_read_at'] = self.readAtPercent.value() + def manage_checkboxes(self, event=None): sender = self.sender().objectName() diff --git a/lector/threaded.py b/lector/threaded.py index 4ec566c..b88d8db 100644 --- a/lector/threaded.py +++ b/lector/threaded.py @@ -130,6 +130,10 @@ class BackGroundCacheRefill(QtCore.QThread): def __init__(self, image_cache, remove_value, filetype, book, all_pages, parent=None): super(BackGroundCacheRefill, self).__init__(parent) + # TODO + # Return with only the first image in case of a cache miss + # Rebuilding the entire n image cache takes considerably longer + self.image_cache = image_cache self.remove_value = remove_value self.filetype = filetype diff --git a/lector/toolbars.py b/lector/toolbars.py index 8b73186..b404d10 100644 --- a/lector/toolbars.py +++ b/lector/toolbars.py @@ -426,13 +426,15 @@ class LibraryToolBar(QtWidgets.QToolBar): self.searchBar.setObjectName('searchBar') # Sorter - title_string = self._translate('TableProxyModel', 'Title') - author_string = self._translate('TableProxyModel', 'Author') - year_string = self._translate('TableProxyModel', 'Year') - newest_string = self._translate('TableProxyModel', 'Newest') - lastread_string = self._translate('TableProxyModel', 'Last Read') + title_string = self._translate('LibraryToolBar', 'Title') + author_string = self._translate('LibraryToolBar', 'Author') + year_string = self._translate('LibraryToolBar', 'Year') + newest_string = self._translate('LibraryToolBar', 'Newest') + lastread_string = self._translate('LibraryToolBar', 'Last Read') + progress_string = self._translate('LibraryToolBar', 'Progress') sorting_choices = [ - title_string, author_string, year_string, newest_string, lastread_string] + title_string, author_string, year_string, + newest_string, lastread_string, progress_string] self.sortingBox = FixedComboBox(self) self.sortingBox.addItems(sorting_choices) diff --git a/lector/widgets.py b/lector/widgets.py index a7002ae..9b5ab16 100644 --- a/lector/widgets.py +++ b/lector/widgets.py @@ -1022,11 +1022,8 @@ class PliantWidgetsCommonFunctions: position_percentage = (self.pw.parent.metadata['position']['current_block'] / self.pw.parent.metadata['position']['total_blocks']) - # Update book metadata and position percentage + # Update position percentage if model_index: - self.main_window.lib_ref.view_model.setData( - model_index[0], self.pw.parent.metadata, QtCore.Qt.UserRole + 3) - self.main_window.lib_ref.view_model.setData( model_index[0], position_percentage, QtCore.Qt.UserRole + 7)