def __init__(self, parent=None): super().__init__(parent) self._delegate = None # ScrollPerPixel means user can draw scroll bar and move list items pixel by pixel, # but mouse wheel still scroll item by item (the number of items scrolled depends on # qApp.wheelScrollLines) self.setVerticalScrollMode(self.ScrollPerPixel) self.scrollbar = DiaryList.ScrollBar(self) self.scrollbar.wantSetRow.connect(self.setRow) self.setVerticalScrollBar(self.scrollbar) self.setupTheme() # disable default editor. Editor is implemented in the View self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.originModel = DiaryModel(self) self.modelProxy = MultiSortFilterProxyModel(self) self.modelProxy.setSourceModel(self.originModel) self.modelProxy.setDynamicSortFilter(True) self.modelProxy.addFilter([db.TAGS], cs=Qt.CaseSensitive) self.modelProxy.addFilter([db.TITLE, db.TEXT], cs=Qt.CaseInsensitive) self.modelProxy.addFilter([db.DATETIME]) self.setModel(self.modelProxy) self.sort() self.editAct = QAction(self.tr('Edit'), self) self.delAct = QAction(makeQIcon(':/menu/list-delete.png', scaled2x=True), self.tr('Delete'), self, shortcut=QKeySequence.Delete) self.randAct = QAction(makeQIcon(':/menu/random-big.png', scaled2x=True), self.tr('Random'), self, shortcut=QKeySequence(Qt.Key_F7), triggered=self.selectRandomly) self.gotoAct = QAction(self.tr('Go to location'), self) for i in (self.editAct, self.delAct, self.randAct, self.gotoAct): self.addAction(i)
def __init__(self, parent=None): super().__init__(parent) self._delegate = None # ScrollPerPixel means user can draw scroll bar and move list items pixel by pixel, # but mouse wheel still scroll item by item (the number of items scrolled depends on # qApp.wheelScrollLines) self.setVerticalScrollMode(self.ScrollPerPixel) self.scrollbar = DiaryListScrollBar(self) self.scrollbar.wantSetRow.connect(self.setRow) self.setVerticalScrollBar(self.scrollbar) # disable default editor. Editor is implemented in the View self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.originModel = DiaryModel(self) self.modelProxy = MultiSortFilterProxyModel(self) self.modelProxy.setSourceModel(self.originModel) self.modelProxy.setDynamicSortFilter(True) self.modelProxy.addFilter([db.TAGS], cs=Qt.CaseSensitive) self.modelProxy.addFilter([db.TITLE, db.TEXT], cs=Qt.CaseInsensitive) self.modelProxy.addFilter([db.DATETIME]) self.setModel(self.modelProxy) self.sort() self.setupTheme() self.editAct = QAction(self.tr('Edit'), self) self.delAct = QAction(makeQIcon(':/menu/list-delete.png', scaled2x=True), self.tr('Delete'), self, shortcut=QKeySequence.Delete) self.randAct = QAction(makeQIcon(':/menu/random-big.png', scaled2x=True), self.tr('Random'), self, shortcut=QKeySequence(Qt.Key_F7), triggered=self.selectRandomly) self.gotoAct = QAction(self.tr('Go to location'), self) for i in (self.editAct, self.delAct, self.randAct, self.gotoAct): self.addAction(i)
class DiaryList(QListView): """Main List that display preview of diaries""" startLoading = Signal() countChanged = Signal() class ScrollBar(QScrollBar): """Annotated scrollbar.""" wantSetRow = Signal(int) def __init__(self, parent): super().__init__(parent, objectName='diaryListSB') self._poses = () self._pairs = () # year: row self._color = QColor('gold') def paintEvent(self, event): super().paintEvent(event) if not self._poses: return p = QPainter(self) # avoid painting on slider handle opt = QStyleOptionSlider() self.initStyleOption(opt) groove = self.style().subControlRect(QStyle.CC_ScrollBar, opt, QStyle.SC_ScrollBarGroove, self) slider = self.style().subControlRect(QStyle.CC_ScrollBar, opt, QStyle.SC_ScrollBarSlider, self) p.setClipRegion(QRegion(groove) - QRegion(slider), Qt.IntersectClip) x, y, w, h = groove.getRect() x += 1 w -= 2 c = self._color c.setAlpha(70) p.setBrush(c) c.setAlpha(145) p.setPen(QPen(c, scaleRatio)) p.drawRects([QRect(x, y+h*i, w, 3*scaleRatio) for i in self._poses]) def contextMenuEvent(self, event): """Used to jump to the first day of years. Original menu is almost useless.""" menu = QMenu() menu.addAction(QAction(self.tr('Go to the first diary of each year'), menu, enabled=False)) for year, row in self._pairs: menu.addAction(QAction(str(year), menu, triggered=lambda r=row: self.wantSetRow.emit(r))) menu.exec_(event.globalPos()) menu.deleteLater() def setPositions(self, rowCount, pairs): self._poses = tuple(p / rowCount for _, p in pairs) self._pairs = pairs annotateColor = NProperty(QColor, '_color') def __init__(self, parent=None): super().__init__(parent) self._delegate = None # ScrollPerPixel means user can draw scroll bar and move list items pixel by pixel, # but mouse wheel still scroll item by item (the number of items scrolled depends on # qApp.wheelScrollLines) self.setVerticalScrollMode(self.ScrollPerPixel) self.scrollbar = DiaryList.ScrollBar(self) self.scrollbar.wantSetRow.connect(self.setRow) self.setVerticalScrollBar(self.scrollbar) self.setupTheme() # disable default editor. Editor is implemented in the View self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.originModel = DiaryModel(self) self.modelProxy = MultiSortFilterProxyModel(self) self.modelProxy.setSourceModel(self.originModel) self.modelProxy.setDynamicSortFilter(True) self.modelProxy.addFilter([db.TAGS], cs=Qt.CaseSensitive) self.modelProxy.addFilter([db.TITLE, db.TEXT], cs=Qt.CaseInsensitive) self.modelProxy.addFilter([db.DATETIME]) self.setModel(self.modelProxy) self.sort() self.editAct = QAction(self.tr('Edit'), self) self.delAct = QAction(makeQIcon(':/menu/list-delete.png', scaled2x=True), self.tr('Delete'), self, shortcut=QKeySequence.Delete) self.randAct = QAction(makeQIcon(':/menu/random-big.png', scaled2x=True), self.tr('Random'), self, shortcut=QKeySequence(Qt.Key_F7), triggered=self.selectRandomly) self.gotoAct = QAction(self.tr('Go to location'), self) for i in (self.editAct, self.delAct, self.randAct, self.gotoAct): self.addAction(i) def contextMenuEvent(self, event): menu = QMenu() menu.addAction(self.editAct) menu.addAction(self.delAct) menu.addSeparator() menu.addAction(self.randAct) selCount = len(self.selectedIndexes()) if selCount == 1 and self.modelProxy.isFiltered(): menu.addAction(self.gotoAct) self.editAct.setDisabled(selCount != 1) self.delAct.setDisabled(selCount == 0) self.randAct.setDisabled(self.modelProxy.rowCount() == 0) menu.exec_(event.globalPos()) def selectAll(self): """Prevent original implement to select invisible columns from the Table Model.""" sel = QItemSelection(self.model().index(0, 0), self.model().index(self.model().rowCount()-1, 0)) self.selectionModel().select(sel, QItemSelectionModel.ClearAndSelect) def setRow(self, row): self.setCurrentIndex(self.modelProxy.index(row, 0)) def selectRandomly(self): self.setRow(random.randrange(0, self.modelProxy.rowCount())) def load(self): self.startLoading.emit() self.originModel.loadFromDb() self.setAnnotatedScrollbar() self.countChanged.emit() def setupTheme(self): theme = settings['Main']['theme'] self._delegate = {'colorful': DiaryListDelegateColorful}.get(theme, DiaryListDelegate)() self.setItemDelegate(self._delegate) if self.isVisible(): # force items to be laid again self.setSpacing(self.spacing()) self.setAnnotatedScrollbar() def reload(self): self.originModel.clear() self.load() def handleExport(self, path, export_all): if export_all: selected = None else: selected = list(map(self.getDiaryDict, self.selectedIndexes())) db.export_txt(path, selected) def getDiaryDict(self, idx): """Get a diary dict by its index in proxy model. Diary dict is only used by Editor.""" return self.originModel.getDiaryDictByRow(self.modelProxy.mapToSource(idx).row()) def sort(self): sortBy = settings['Main']['listSortBy'] sortByCol = getattr(DiaryModel, sortBy.upper(), DiaryModel.DATETIME) reverse = settings['Main'].getboolean('listReverse') self.modelProxy.sort(sortByCol, Qt.DescendingOrder if reverse else Qt.AscendingOrder) if self.isVisible(): self.setAnnotatedScrollbar() def setAnnotatedScrollbar(self, show=None): if show is not False and settings['Main'].getboolean('listAnnotated'): self.scrollbar.setPositions(self.originModel.rowCount(), self.originModel.getYearFirsts()) self.scrollbar.update() else: self.scrollbar.poses = () def _setFilter(self, filterKey, s): self.modelProxy.setFilterPattern(filterKey, s) self.setAnnotatedScrollbar(False if s else not self.modelProxy.isFiltered()) self.countChanged.emit() def setFilterBySearchString(self, s): self._setFilter(1, s) def setFilterByTag(self, s): self._setFilter(0, s) def setFilterByDatetime(self, s): self._setFilter(2, s) @Slot(str) def refreshFilteredTags(self, newTagName): """Update items with old tag after a tag's name changed, and update filter ( Right click tag item and rename it will always set filter).""" model, modelP = self.originModel, self.modelProxy needRefresh = [modelP.mapToSource(modelP.index(i, 0)) for i in range(modelP.rowCount())] for i in needRefresh: diary = db[i.data()] # lazy model.setData(i.sibling(i.row(), DiaryModel.TAGS), diary[DiaryModel.TAGS]) self.setFilterByTag(newTagName)
class DiaryList(QListView): """Main List that display preview of diaries""" startLoading = Signal() countChanged = Signal() def __init__(self, parent=None): super().__init__(parent) self._delegate = None # ScrollPerPixel means user can draw scroll bar and move list items pixel by pixel, # but mouse wheel still scroll item by item (the number of items scrolled depends on # qApp.wheelScrollLines) self.setVerticalScrollMode(self.ScrollPerPixel) self.scrollbar = DiaryListScrollBar(self) self.scrollbar.wantSetRow.connect(self.setRow) self.setVerticalScrollBar(self.scrollbar) # disable default editor. Editor is implemented in the View self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.originModel = DiaryModel(self) self.modelProxy = MultiSortFilterProxyModel(self) self.modelProxy.setSourceModel(self.originModel) self.modelProxy.setDynamicSortFilter(True) self.modelProxy.addFilter([db.TAGS], cs=Qt.CaseSensitive) self.modelProxy.addFilter([db.TITLE, db.TEXT], cs=Qt.CaseInsensitive) self.modelProxy.addFilter([db.DATETIME]) self.setModel(self.modelProxy) self.sort() self.setupTheme() self.editAct = QAction(self.tr('Edit'), self) self.delAct = QAction(makeQIcon(':/menu/list-delete.png', scaled2x=True), self.tr('Delete'), self, shortcut=QKeySequence.Delete) self.randAct = QAction(makeQIcon(':/menu/random-big.png', scaled2x=True), self.tr('Random'), self, shortcut=QKeySequence(Qt.Key_F7), triggered=self.selectRandomly) self.gotoAct = QAction(self.tr('Go to location'), self) for i in (self.editAct, self.delAct, self.randAct, self.gotoAct): self.addAction(i) def contextMenuEvent(self, event): menu = QMenu() menu.addAction(self.editAct) menu.addAction(self.delAct) menu.addSeparator() menu.addAction(self.randAct) selCount = len(self.selectedIndexes()) if selCount == 1 and self.modelProxy.isFiltered(): menu.addAction(self.gotoAct) self.editAct.setDisabled(selCount != 1) self.delAct.setDisabled(selCount == 0) self.randAct.setDisabled(self.modelProxy.rowCount() == 0) menu.exec_(event.globalPos()) def selectAll(self): """Prevent original implement to select invisible columns from the Table Model.""" sel = QItemSelection( self.model().index(0, 0), self.model().index(self.model().rowCount() - 1, 0)) self.selectionModel().select(sel, QItemSelectionModel.ClearAndSelect) def resizeEvent(self, event): super().resizeEvent(event) if self._delegate.__class__ == DiaryListDelegateColorful: self._delegate.adjustWidgetCache(self.height()) def setRow(self, row): self.setCurrentIndex(self.modelProxy.index(row, 0)) def selectRandomly(self): self.setRow(random.randrange(0, self.modelProxy.rowCount())) def load(self): self.startLoading.emit() self.originModel.loadFromDb() self.setAnnotatedScrollbar() self.countChanged.emit() def setupTheme(self): theme = settings['Main']['theme'] if theme == 'colorful': self._delegate = DiaryListDelegateColorful(self.modelProxy) else: self._delegate = DiaryListDelegate() self.setItemDelegate(self._delegate) if self.isVisible(): # force items to be laid again self.setSpacing(self.spacing()) self.setAnnotatedScrollbar() if theme == 'colorful': self._delegate.adjustWidgetCache(self.height()) def reload(self): self.originModel.clear() self.load() def handleExport(self, path, export_all): if export_all: selected = None else: selected = list(map(self.getDiaryDict, self.selectedIndexes())) db.export_txt(path, selected) def getDiaryDict(self, idx): """Get a diary dict by its index in proxy model. Diary dict is only used by Editor.""" return self.originModel.getDiaryDictByRow( self.modelProxy.mapToSource(idx).row()) def sort(self): sortBy = settings['Main']['listSortBy'] sortByCol = getattr(DiaryModel, sortBy.upper(), DiaryModel.DATETIME) reverse = settings['Main'].getboolean('listReverse') self.modelProxy.sort( sortByCol, Qt.DescendingOrder if reverse else Qt.AscendingOrder) if self.isVisible(): self.setAnnotatedScrollbar() def setAnnotatedScrollbar(self, show=None): if show is not False and settings['Main'].getboolean('listAnnotated'): self.scrollbar.setPositions(self.originModel.rowCount(), self.originModel.getYearFirsts()) else: self.scrollbar.setPositions(None, None) def _setFilter(self, filterKey, s): self.modelProxy.setFilterPattern(filterKey, s) self.setAnnotatedScrollbar( False if s else not self.modelProxy.isFiltered()) self.countChanged.emit() def setFilterBySearchString(self, s): self._setFilter(1, s) def setFilterByTag(self, s): self._setFilter(0, s) def setFilterByDatetime(self, s): self._setFilter(2, s) @Slot(str) def refreshFilteredTags(self, newTagName): """Update items with old tag after a tag's name changed, and update filter ( Right click tag item and rename it will always set filter).""" model, modelP = self.originModel, self.modelProxy needRefresh = [ modelP.mapToSource(modelP.index(i, 0)) for i in range(modelP.rowCount()) ] for i in needRefresh: diary = db[i.data()] # lazy model.setData(i.sibling(i.row(), DiaryModel.TAGS), diary[DiaryModel.TAGS]) self.setFilterByTag(newTagName)
class DiaryList(QListView): """Main List that display preview of diaries""" startLoading = Signal() countChanged = Signal() def __init__(self, parent=None): super().__init__(parent) self._delegate = None # ScrollPerPixel means user can draw scroll bar and move list items pixel by pixel, # but mouse wheel still scroll item by item (the number of items scrolled depends on # qApp.wheelScrollLines) self.setVerticalScrollMode(self.ScrollPerPixel) self.scrollbar = DiaryListScrollBar(self) self.scrollbar.wantSetRow.connect(self.setRow) self.setVerticalScrollBar(self.scrollbar) # disable default editor. Editor is implemented in the View self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.originModel = DiaryModel(self) self.modelProxy = MultiSortFilterProxyModel(self) self.modelProxy.setSourceModel(self.originModel) self.modelProxy.setDynamicSortFilter(True) self.modelProxy.addFilter([db.TAGS], cs=Qt.CaseSensitive) self.modelProxy.addFilter([db.TITLE, db.TEXT], cs=Qt.CaseInsensitive) self.modelProxy.addFilter([db.DATETIME]) self.setModel(self.modelProxy) self.sort() self.setupTheme() self.editAct = QAction(self.tr('Edit'), self) self.delAct = QAction(makeQIcon(':/menu/list-delete.png', scaled2x=True), self.tr('Delete'), self, shortcut=QKeySequence.Delete) self.randAct = QAction(makeQIcon(':/menu/random-big.png', scaled2x=True), self.tr('Random'), self, shortcut=QKeySequence(Qt.Key_F7), triggered=self.selectRandomly) self.gotoAct = QAction(self.tr('Go to location'), self) for i in (self.editAct, self.delAct, self.randAct, self.gotoAct): self.addAction(i) def contextMenuEvent(self, event): menu = QMenu() menu.addAction(self.editAct) menu.addAction(self.delAct) menu.addSeparator() menu.addAction(self.randAct) selCount = len(self.selectedIndexes()) if selCount == 1 and self.modelProxy.isFiltered(): menu.addAction(self.gotoAct) self.editAct.setDisabled(selCount != 1) self.delAct.setDisabled(selCount == 0) self.randAct.setDisabled(self.modelProxy.rowCount() == 0) menu.exec_(event.globalPos()) def selectAll(self): """Prevent original implement to select invisible columns from the Table Model.""" sel = QItemSelection(self.model().index(0, 0), self.model().index(self.model().rowCount()-1, 0)) self.selectionModel().select(sel, QItemSelectionModel.ClearAndSelect) def resizeEvent(self, event): super().resizeEvent(event) if self._delegate.__class__ == DiaryListDelegateColorful: self._delegate.adjustWidgetCache(self.height()) def setRow(self, row): self.setCurrentIndex(self.modelProxy.index(row, 0)) def selectRandomly(self): self.setRow(random.randrange(0, self.modelProxy.rowCount())) def load(self): self.startLoading.emit() self.originModel.loadFromDb() self.setAnnotatedScrollbar() self.countChanged.emit() def setupTheme(self): theme = settings['Main']['theme'] if theme == 'colorful': self._delegate = DiaryListDelegateColorful(self.modelProxy) else: self._delegate = DiaryListDelegate() self.setItemDelegate(self._delegate) if self.isVisible(): # force items to be laid again self.setSpacing(self.spacing()) self.setAnnotatedScrollbar() if theme == 'colorful': self._delegate.adjustWidgetCache(self.height()) def reload(self): self.originModel.clear() self.load() def handleExport(self, path, export_all): if export_all: selected = None else: selected = list(map(self.getDiaryDict, self.selectedIndexes())) db.export_txt(path, selected) def getDiaryDict(self, idx): """Get a diary dict by its index in proxy model. Diary dict is only used by Editor.""" return self.originModel.getDiaryDictByRow(self.modelProxy.mapToSource(idx).row()) def sort(self): sortBy = settings['Main']['listSortBy'] sortByCol = getattr(DiaryModel, sortBy.upper(), DiaryModel.DATETIME) reverse = settings['Main'].getboolean('listReverse') self.modelProxy.sort(sortByCol, Qt.DescendingOrder if reverse else Qt.AscendingOrder) if self.isVisible(): self.setAnnotatedScrollbar() def setAnnotatedScrollbar(self, show=None): if show is not False and settings['Main'].getboolean('listAnnotated'): self.scrollbar.setPositions(self.originModel.rowCount(), self.originModel.getYearFirsts()) else: self.scrollbar.setPositions(None, None) def _setFilter(self, filterKey, s): self.modelProxy.setFilterPattern(filterKey, s) self.setAnnotatedScrollbar(False if s else not self.modelProxy.isFiltered()) self.countChanged.emit() def setFilterBySearchString(self, s): self._setFilter(1, s) def setFilterByTag(self, s): self._setFilter(0, s) def setFilterByDatetime(self, s): self._setFilter(2, s) @Slot(str) def refreshFilteredTags(self, newTagName): """Update items with old tag after a tag's name changed, and update filter ( Right click tag item and rename it will always set filter).""" model, modelP = self.originModel, self.modelProxy needRefresh = [modelP.mapToSource(modelP.index(i, 0)) for i in range(modelP.rowCount())] for i in needRefresh: diary = db[i.data()] # lazy model.setData(i.sibling(i.row(), DiaryModel.TAGS), diary[DiaryModel.TAGS]) self.setFilterByTag(newTagName)