1""" GUI utils for widgets """
2from typing import Sequence
3from numbers import Real, Integral
4from collections import namedtuple
5
6from AnyQt.QtGui import QTextDocument, QAbstractTextDocumentLayout
7from AnyQt.QtCore import Qt, QSize, QSortFilterProxyModel
8from AnyQt.QtWidgets import QFrame, QStyle, QApplication, QStyledItemDelegate, QStyleOptionViewItem
9
10from .gene_sets import GeneSetsSelection
11from .gene_scoring import GeneScoringWidget, gene_scoring_method
12from .list_completer import TokenListCompleter
13from .label_selection import (
14    RowGroup,
15    ColumnGroup,
16    LabelSelectionWidget,
17    itemselection,
18    group_candidates,
19    standarditem_from,
20    group_selection_mask,
21)
22
23__all__ = (
24    'GeneSetsSelection',
25    'GeneScoringWidget',
26    'gene_scoring_method',
27    'TokenListCompleter',
28    'RowGroup',
29    'ColumnGroup',
30    'LabelSelectionWidget',
31    'itemselection',
32    'group_candidates',
33    'standarditem_from',
34    'group_selection_mask',
35)
36
37
38# Creates line separator
39def horizontal_line():
40    line = QFrame()
41    line.setFrameShape(QFrame.HLine)
42    line.setFrameShadow(QFrame.Sunken)
43    return line
44
45
46class FilterProxyModel(QSortFilterProxyModel):
47    """
48    A simple filter proxy model with settable filter predicates
49    Example
50    -------
51    >>> proxy = FilterProxyModel()
52    >>> proxy.set_filters([
53    ...     FilterProxyModel.Filter(0, Qt.DisplayRole, lambda value: value < 1)
54    ... ])
55    """
56
57    Filter = namedtuple("Filter", ["column", "role", "predicate"])
58
59    def __init__(self, *args, **kwargs):
60        super().__init__(*args, **kwargs)
61        self.__filters = []
62
63    def reset_filters(self):
64        self.__filters = []
65        self.invalidateFilter()
66
67    def set_filters(self, filters):
68        # type: (Sequence[FilterProxyModel.Filter]) -> None
69
70        filters = [FilterProxyModel.Filter(f.column, f.role, f.predicate) for f in filters]
71        self.__filters = filters
72        self.invalidateFilter()
73
74    def filterAcceptsRow(self, row, parent):
75        source = self.sourceModel()
76
77        def apply(f):
78            index = source.index(row, f.column, parent)
79            data = source.data(index, f.role)
80            try:
81                return f.predicate(data)
82            except (TypeError, ValueError):
83                return False
84
85        return all(apply(f) for f in self.__filters)
86
87
88class NumericalColumnDelegate(QStyledItemDelegate):
89    """
90    An Item delegate for displaying numerical columns
91    """
92
93    def __init__(self, parent=None, precision=4, notation='f'):
94        super().__init__(parent)
95        self.precision = precision
96        self.notation = notation
97
98    def displayText(self, value, locale):
99        if isinstance(value, Integral):
100            return locale.toString(int(value))
101        elif isinstance(value, Real):
102            return locale.toString(float(value), self.notation, self.precision)
103        else:
104            return super().displayText(value, locale)
105
106    def initStyleOption(self, option, index):
107        super().initStyleOption(option, index)
108        align = index.data(Qt.TextAlignmentRole)
109        data = index.data(Qt.DisplayRole)
110        if align is None and isinstance(data, Real):
111            # Right align if the model does not specify otherwise
112            option.displayAlignment = Qt.AlignRight | Qt.AlignVCenter
113
114
115class HTMLDelegate(QStyledItemDelegate):
116    """
117    https://stackoverflow.com/questions/1956542/how-to-make-item-view-render-rich-html-text-in-qt
118    https://stackoverflow.com/questions/2375763/how-to-open-an-url-in-a-qtableview
119    """
120
121    def sizeHint(self, option, index):
122        options = QStyleOptionViewItem(option)
123        gene_obj = index.data(Qt.DisplayRole)
124        self.initStyleOption(options, index)
125
126        doc = QTextDocument()
127        doc.setHtml(gene_obj.to_html())
128        doc.setTextWidth(options.rect.width() - 10)
129
130        return QSize(doc.idealWidth(), doc.size().height())
131
132    def paint(self, painter, option, index):
133        options = QStyleOptionViewItem(option)
134        row_obj = index.data(Qt.DisplayRole)
135        self.initStyleOption(options, index)
136        # print(option.rect.width())
137        style = QApplication.style() if options.widget is None else options.widget.style()
138
139        doc = QTextDocument()
140        doc.setHtml(row_obj.to_html())
141        doc.setTextWidth(option.rect.width() - 10)
142
143        # doc.setPageSize(300)
144        # print(doc.loadResource(3))
145
146        options.text = ""
147        style.drawControl(QStyle.CE_ItemViewItem, options, painter)
148
149        ctx = QAbstractTextDocumentLayout.PaintContext()
150
151        text_rect = style.subElementRect(QStyle.SE_ItemViewItemText, options)
152        painter.save()
153        painter.translate(text_rect.topLeft())
154        painter.setClipRect(text_rect.translated(-text_rect.topLeft()))
155        doc.documentLayout().draw(painter, ctx)
156
157        painter.restore()
158