1#!/usr/bin/env python3 2 3#****************************************************************************** 4# breadcrumbview.py, provides a class for the breadcrumb view 5# 6# TreeLine, an information storage program 7# Copyright (C) 2017, Douglas W. Bell 8# 9# This is free software; you can redistribute it and/or modify it under the 10# terms of the GNU General Public License, either Version 2 or any later 11# version. This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY. See the included LICENSE file for details. 13#****************************************************************************** 14 15import operator 16from PyQt5.QtCore import QSize, Qt 17from PyQt5.QtGui import QPainter, QPalette 18from PyQt5.QtWidgets import (QAbstractItemView, QApplication, 19 QStyledItemDelegate, QTableWidget, 20 QTableWidgetItem) 21import globalref 22 23 24class CrumbItem(QTableWidgetItem): 25 """Class to store breadcrumb item spot refs and positions. 26 """ 27 def __init__(self, spotRef): 28 """Initialize the breadcrumb item. 29 30 Arguments: 31 spotRef -- ref to the associated spot item 32 """ 33 super().__init__(spotRef.nodeRef.title(spotRef)) 34 self.spot = spotRef 35 self.selectedSpot = False 36 self.setTextAlignment(Qt.AlignCenter) 37 self.setForeground(QApplication.palette().brush(QPalette.Link)) 38 39 40class BorderDelegate(QStyledItemDelegate): 41 """Class override to show borders between rows. 42 """ 43 def __init__(self, parent=None): 44 """Initialize the delegate class. 45 46 Arguments: 47 parent -- the parent view 48 """ 49 super().__init__(parent) 50 51 def paint(self, painter, styleOption, modelIndex): 52 """Paint the cells with borders between rows. 53 """ 54 super().paint(painter, styleOption, modelIndex) 55 cell = self.parent().item(modelIndex.row(), modelIndex.column()) 56 if modelIndex.row() > 0 and cell: 57 upperCell = None 58 row = modelIndex.row() 59 while not upperCell and row > 0: 60 row -= 1 61 upperCell = self.parent().item(row, modelIndex.column()) 62 if cell.text() and upperCell and upperCell.text(): 63 painter.drawLine(styleOption.rect.topLeft(), 64 styleOption.rect.topRight()) 65 66 67class BreadcrumbView(QTableWidget): 68 """Class override for the breadcrumb view. 69 70 Sets view defaults and updates the content. 71 """ 72 def __init__(self, treeView, parent=None): 73 """Initialize the breadcrumb view. 74 75 Arguments: 76 treeView - the tree view, needed for the current selection model 77 parent -- the parent main window 78 """ 79 super().__init__(parent) 80 self.treeView = treeView 81 self.borderItems = [] 82 self.setFocusPolicy(Qt.NoFocus) 83 self.horizontalHeader().hide() 84 self.verticalHeader().hide() 85 self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) 86 self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) 87 self.setSelectionMode(QAbstractItemView.NoSelection) 88 self.setEditTriggers(QAbstractItemView.NoEditTriggers) 89 self.setItemDelegate(BorderDelegate(self)) 90 self.setShowGrid(False) 91 self.setMouseTracking(True) 92 self.itemClicked.connect(self.changeSelection) 93 94 def updateContents(self): 95 """Reload the view's content if the view is shown. 96 97 Avoids update if view is not visible or has zero height or width. 98 """ 99 if not self.isVisible() or self.height() == 0 or self.width() == 0: 100 return 101 self.clear() 102 self.clearSpans() 103 selModel = self.treeView.selectionModel() 104 selSpots = selModel.selectedSpots() 105 if len(selSpots) != 1: 106 return 107 selSpot = selSpots[0] 108 spotList = sorted(list(selSpot.nodeRef.spotRefs), 109 key=operator.methodcaller('sortKey')) 110 chainList = [[CrumbItem(chainSpot) for chainSpot in spot.spotChain()] 111 for spot in spotList] 112 self.setRowCount(len(chainList)) 113 for row in range(len(chainList)): 114 columns = len(chainList[row]) * 2 - 1 115 if columns > self.columnCount(): 116 self.setColumnCount(columns) 117 for col in range(len(chainList[row])): 118 item = chainList[row][col] 119 if (row == 0 or col >= len(chainList[row - 1]) or 120 item.spot is not chainList[row - 1][col].spot): 121 rowSpan = 1 122 while (row + rowSpan < len(chainList) and 123 col < len(chainList[row + rowSpan]) and 124 item.spot is chainList[row + rowSpan][col].spot): 125 rowSpan += 1 126 if col < len(chainList[row]) - 1: 127 arrowItem = QTableWidgetItem('\u25ba') 128 arrowItem.setTextAlignment(Qt.AlignCenter) 129 self.setItem(row, col * 2 + 1, arrowItem) 130 if rowSpan > 1: 131 self.setSpan(row, col * 2 + 1, rowSpan, 1) 132 self.setItem(row, col * 2, item) 133 if rowSpan > 1: 134 self.setSpan(row, col * 2, rowSpan, 1) 135 if item.spot is selSpot: 136 item.selectedSpot = True 137 item.setForeground(QApplication.palette(). 138 brush(QPalette.WindowText)) 139 self.resizeColumnsToContents() 140 141 def changeSelection(self, item): 142 """Change the current selection to given item bassed on a mouse click. 143 144 Arguments: 145 item -- the breadcrumb item that was clicked 146 """ 147 selModel = self.treeView.selectionModel() 148 if hasattr(item, 'spot') and not item.selectedSpot: 149 selModel.selectSpots([item.spot]) 150 self.setCursor(Qt.ArrowCursor) 151 152 def minimumSizeHint(self): 153 """Set a short minimum size fint to allow the display of one row. 154 """ 155 return QSize(super().minimumSizeHint().width(), 156 self.fontInfo().pixelSize() * 3) 157 158 def mouseMoveEvent(self, event): 159 """Change the mouse pointer if over a clickable item. 160 161 Arguments: 162 event -- the mouse move event 163 """ 164 item = self.itemAt(event.localPos().toPoint()) 165 if item and hasattr(item, 'spot') and not item.selectedSpot: 166 self.setCursor(Qt.PointingHandCursor) 167 else: 168 self.setCursor(Qt.ArrowCursor) 169 super().mouseMoveEvent(event) 170 171 def resizeEvent(self, event): 172 """Update view if was collaped by splitter. 173 """ 174 if ((event.oldSize().height() == 0 and event.size().height()) or 175 (event.oldSize().width() == 0 and event.size().width())): 176 self.updateContents() 177 return super().resizeEvent(event) 178