#!/usr/bin/env python3 # This file is a part of Lector, a Qt based ebook reader # Copyright (C) 2017 BasioMeusPuga # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . import pathlib from PyQt5 import QtCore, QtWidgets from resources import pie_chart class BookmarkProxyModel(QtCore.QSortFilterProxyModel): def __init__(self, parent=None): super(BookmarkProxyModel, self).__init__(parent) self.parent = parent self.filter_string = None def setFilterParams(self, filter_text): self.filter_text = filter_text def filterAcceptsRow(self, row, parent): # TODO # Connect this to the search bar return True def setData(self, index, value, role): if role == QtCore.Qt.EditRole: source_index = self.mapToSource(index) identifier = self.sourceModel().data(source_index, QtCore.Qt.UserRole + 2) self.sourceModel().setData(source_index, value, QtCore.Qt.DisplayRole) self.parent.metadata['bookmarks'][identifier]['description'] = value return True class ItemProxyModel(QtCore.QSortFilterProxyModel): def __init__(self, parent=None): super(ItemProxyModel, self).__init__(parent) self.filter_text = None self.active_library_filters = None self.sorting_box_position = None self.common_functions = ProxyModelsCommonFunctions(self) def setFilterParams(self, filter_text, active_library_filters, sorting_box_position): self.common_functions.setFilterParams( filter_text, active_library_filters, sorting_box_position) def filterAcceptsRow(self, row, parent): output = self.common_functions.filterAcceptsRow(row, parent) return output class TableProxyModel(QtCore.QSortFilterProxyModel): def __init__(self, temp_dir, parent=None): super(TableProxyModel, self).__init__(parent) self.header_data = [ None, 'Title', 'Author', 'Year', '%', 'Tags'] self.temp_dir = temp_dir self.filter_text = None self.active_library_filters = None self.sorting_box_position = None self.role_dictionary = { 1: QtCore.Qt.UserRole, # Title 2: QtCore.Qt.UserRole + 1, # Author 3: QtCore.Qt.UserRole + 2, # Year 4: QtCore.Qt.UserRole + 7, # Position percentage 5: QtCore.Qt.UserRole + 4} # Tags self.common_functions = ProxyModelsCommonFunctions(self) def columnCount(self, parent): return 6 def headerData(self, column, orientation, role): if role == QtCore.Qt.DisplayRole: return self.header_data[column] def flags(self, index): # Tag editing will take place by way of a right click menu # These tags denote clickable and that's about it return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable def data(self, index, role): source_index = self.mapToSource(index) item = self.sourceModel().item(source_index.row(), 0) if role == QtCore.Qt.TextAlignmentRole and index.column() == 3: return QtCore.Qt.AlignHCenter if role == QtCore.Qt.DecorationRole: if index.column() == 4: return_pixmap = None file_exists = item.data(QtCore.Qt.UserRole + 5) metadata = item.data(QtCore.Qt.UserRole + 3) position = metadata['position'] if position: is_read = position['is_read'] if not file_exists: return pie_chart.pixmapper( -1, None, None, QtCore.Qt.SizeHintRole + 10) if position: if is_read: current_chapter = total_chapters = 100 else: try: current_chapter = position['current_chapter'] total_chapters = position['total_chapters'] # TODO # See if there's any rationale for this if current_chapter == 1: raise KeyError except KeyError: return return_pixmap = pie_chart.pixmapper( current_chapter, total_chapters, self.temp_dir, QtCore.Qt.SizeHintRole + 10) return return_pixmap elif role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole: if index.column() in (0, 4): # Cover and Status return QtCore.QVariant() return item.data(self.role_dictionary[index.column()]) else: return QtCore.QVariant() def setFilterParams(self, filter_text, active_library_filters, sorting_box_position): self.common_functions.setFilterParams( filter_text, active_library_filters, sorting_box_position) def filterAcceptsRow(self, row, parent): output = self.common_functions.filterAcceptsRow(row, parent) return output def sort_table_columns(self, column): sorting_order = self.sender().sortIndicatorOrder() self.sort(0, sorting_order) self.setSortRole(self.role_dictionary[column]) class ProxyModelsCommonFunctions: def __init__(self, parent_model): self.parent_model = parent_model def setFilterParams(self, filter_text, active_library_filters, sorting_box_position): self.parent_model.filter_text = filter_text self.parent_model.active_library_filters = [i.lower() for i in active_library_filters] self.parent_model.sorting_box_position = sorting_box_position def filterAcceptsRow(self, row, parent): model = self.parent_model.sourceModel() this_index = model.index(row, 0) 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) 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) # Hide untouched files when sorting by last accessed if self.parent_model.sorting_box_position == 4 and not last_accessed: return False if self.parent_model.active_library_filters: if directory_name not in self.parent_model.active_library_filters: return False else: return False if not self.parent_model.filter_text: return True else: valid_data = [ i.lower() for i in ( title, author, tags, directory_name, directory_tags) if i is not None] for i in valid_data: if self.parent_model.filter_text.lower() in i: return True return False class MostExcellentFileSystemModel(QtWidgets.QFileSystemModel): # Directories are tracked on the basis of their paths # Poll the tag_data dictionary to get User selection def __init__(self, tag_data, parent=None): super(MostExcellentFileSystemModel, self).__init__(parent) self.tag_data = tag_data self.field_dict = { 0: 'check_state', 4: 'name', 5: 'tags'} def columnCount(self, parent): # The QFileSystemModel returns 4 columns by default # Columns 1, 2, 3 will be present but hidden return 6 def headerData(self, col, orientation, role): # Columns not mentioned here will be hidden if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: column_dict = { 0: 'Path', 4: 'Name', 5: 'Tags'} try: return column_dict[col] except KeyError: pass def data(self, index, role): if (index.column() in (4, 5) and (role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole)): read_field = self.field_dict[index.column()] try: return self.tag_data[self.filePath(index)][read_field] except KeyError: return QtCore.QVariant() if role == QtCore.Qt.CheckStateRole and index.column() == 0: return self.checkState(index) return QtWidgets.QFileSystemModel.data(self, index, role) def flags(self, index): if index.column() in (4, 5): return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable else: return QtWidgets.QFileSystemModel.flags(self, index) | QtCore.Qt.ItemIsUserCheckable def checkState(self, index): while index.isValid(): index_path = self.filePath(index) if index_path in self.tag_data: return self.tag_data[index_path]['check_state'] index = index.parent() return QtCore.Qt.Unchecked def setData(self, index, value, role): if (role == QtCore.Qt.EditRole or role == QtCore.Qt.CheckStateRole) and index.isValid(): write_field = self.field_dict[index.column()] self.layoutAboutToBeChanged.emit() this_path = self.filePath(index) if this_path not in self.tag_data: self.populate_dictionary(this_path) self.tag_data[this_path][write_field] = value self.depopulate_dictionary() self.layoutChanged.emit() return True def populate_dictionary(self, path): self.tag_data[path] = {} self.tag_data[path]['name'] = None self.tag_data[path]['tags'] = None self.tag_data[path]['check_state'] = QtCore.Qt.Checked def depopulate_dictionary(self): # This keeps the tag_data dictionary manageable as well as preventing # weird ass behaviour when something is deselected and its tags are cleared deletable = set() for i in self.tag_data.items(): all_data = [j[1] for j in i[1].items()] filtered_down = list(filter(lambda x: x is not None and x != 0, all_data)) if not filtered_down: deletable.add(i[0]) # Get untagged subdirectories too all_dirs = [i for i in self.tag_data] all_dirs.sort() def is_child(this_dir): this_path = pathlib.Path(this_dir) for i in all_dirs: if pathlib.Path(i) in this_path.parents: # If a parent folder has tags, we only want the deletion # to kick in in case the parent is also checked if self.tag_data[i]['check_state'] == QtCore.Qt.Checked: return True return False for i in all_dirs: if is_child(i): dir_tags = (self.tag_data[i]['name'], self.tag_data[i]['tags']) filtered_down = list(filter(lambda x: x is not None and x != '', dir_tags)) if not filtered_down: deletable.add(i) for i in deletable: del self.tag_data[i] # TODO # Unbork this class FileSystemProxyModel(QtCore.QSortFilterProxyModel): def __init__(self, parent=None): super(FileSystemProxyModel, self).__init__(parent) def filterAcceptsRow(self, row_num, parent): model = self.sourceModel() filter_out = [ 'boot', 'dev', 'etc', 'lost+found', 'opt', 'pdb', 'proc', 'root', 'run', 'srv', 'sys', 'tmp', 'twonky', 'usr', 'var', 'bin', 'kdeinit5__0', 'lib', 'lib64', 'sbin'] name_index = model.index(row_num, 0) valid_data = model.data(name_index) print(valid_data) return True try: if valid_data in filter_out: return False except AttributeError: pass return True