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