File: combobox.py

package info (click to toggle)
orange3 3.40.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,912 kB
  • sloc: python: 162,745; ansic: 622; makefile: 322; sh: 93; cpp: 77
file content (185 lines) | stat: -rw-r--r-- 6,411 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
from AnyQt.QtCore import Qt, Signal
from AnyQt.QtGui import (
    QBrush, QColor, QPalette, QPen, QFont, QFontMetrics, QFocusEvent
)
from AnyQt.QtWidgets import (
    QStylePainter, QStyleOptionComboBox, QStyle, QApplication, QLineEdit
)

from orangewidget.utils.combobox import (
    ComboBoxSearch, ComboBox, qcombobox_emit_activated
)

__all__ = [
    "ComboBoxSearch", "ComboBox", "ItemStyledComboBox", "TextEditCombo"
]


class ItemStyledComboBox(ComboBox):
    """
    A QComboBox that draws its text using current item's foreground and font
    role.

    Note
    ----
    Stylesheets etc. can completely ignore this.
    """
    def __init__(self, *args, placeholderText="", **kwargs):
        self.__placeholderText = placeholderText
        super().__init__(*args, **kwargs)

    def paintEvent(self, _event) -> None:
        painter = QStylePainter(self)
        option = QStyleOptionComboBox()
        self.initStyleOption(option)
        painter.drawComplexControl(QStyle.CC_ComboBox, option)
        foreground = self.currentData(Qt.ForegroundRole)
        if isinstance(foreground, (QBrush, QColor)):
            foreground = QBrush(foreground)
            if foreground.style() != Qt.NoBrush:
                # some styles take WindowText some just use current pen?
                option.palette.setBrush(QPalette.WindowText, foreground)
                option.palette.setBrush(QPalette.ButtonText, foreground)
                option.palette.setBrush(QPalette.Text, foreground)
                painter.setPen(QPen(foreground, painter.pen().widthF()))
        font = self.currentData(Qt.FontRole)
        if isinstance(font, QFont):
            option.fontMetrics = QFontMetrics(font)
            painter.setFont(font)
        painter.drawControl(QStyle.CE_ComboBoxLabel, option)

    def placeholderText(self) -> str:
        """
        Return the placeholder text.

        Returns
        -------
        text : str
        """
        return self.__placeholderText

    def setPlaceholderText(self, text: str):
        """
        Set the placeholder text.

        This text is displayed on the checkbox when the currentIndex() == -1

        Parameters
        ----------
        text : str
        """
        if self.__placeholderText != text:
            self.__placeholderText = text
            self.update()

    def initStyleOption(self, option: 'QStyleOptionComboBox') -> None:
        super().initStyleOption(option)
        if self.currentIndex() == -1:
            option.currentText = self.__placeholderText
            option.palette.setCurrentColorGroup(QPalette.Disabled)


class TextEditCombo(ComboBox):
    #: This signal is emitted whenever the contents of the combo box are
    #: changed and the widget loses focus *OR* via item activation (activated
    #: signal)
    editingFinished = Signal()

    def __init__(self, *args, **kwargs):
        kwargs.setdefault("editable", True)
        # `activated=...` kwarg needs to be connected after `__on_activated`
        activated = kwargs.pop("activated", None)
        self.__edited = False
        super().__init__(*args, **kwargs)
        self.activated.connect(self.__on_activated)
        if activated is not None:
            self.activated.connect(activated)
        ledit = self.lineEdit()
        if ledit is not None:
            ledit.textEdited.connect(self.__markEdited)

    def setLineEdit(self, edit: QLineEdit) -> None:
        super().setLineEdit(edit)
        edit.textEdited.connect(self.__markEdited)

    def __markEdited(self):
        self.__edited = True

    def __on_activated(self):
        self.__edited = False  # mark clean on any activation
        self.editingFinished.emit()

    def focusOutEvent(self, event: QFocusEvent) -> None:
        super().focusOutEvent(event)
        popup = QApplication.activePopupWidget()
        if self.isEditable() and self.__edited and \
                (event.reason() != Qt.PopupFocusReason or
                    not (popup is not None
                         and popup.parent() in (self, self.lineEdit()))):
            def monitor():
                # monitor if editingFinished was emitted from
                # __on_editingFinished to avoid double emit.
                nonlocal emitted
                emitted = True
            emitted = False
            self.editingFinished.connect(monitor)
            self.__edited = False
            self.__on_editingFinished()
            self.editingFinished.disconnect(monitor)

            if not emitted:
                self.editingFinished.emit()

    def __on_editingFinished(self):
        le = self.lineEdit()
        policy = self.insertPolicy()
        text = le.text()
        if not text:
            return
        index = self.findText(text, Qt.MatchFixedString)
        if index != -1:
            self.setCurrentIndex(index)
            qcombobox_emit_activated(self, index)
            return
        if policy == ComboBox.NoInsert:
            return
        elif policy == ComboBox.InsertAtTop:
            index = 0
        elif policy == ComboBox.InsertAtBottom:
            index = self.count()
        elif policy == ComboBox.InsertAfterCurrent:
            index = self.currentIndex() + 1
        elif policy == ComboBox.InsertBeforeCurrent:
            index = max(self.currentIndex(), 0)
        elif policy == ComboBox.InsertAlphabetically:
            for index in range(self.count()):
                if self.itemText(index).lower() >= text.lower():
                    break
        elif policy == ComboBox.InsertAtCurrent:
            self.setItemText(self.currentIndex(), text)
            qcombobox_emit_activated(self, self.currentIndex())
            return

        if index > -1:
            self.insertItem(index, text)
            self.setCurrentIndex(index)
            qcombobox_emit_activated(self, self.currentIndex())

    def text(self):
        # type: () -> str
        """
        Return the current text.
        """
        return self.itemText(self.currentIndex())

    def setText(self, text):
        # type: (str) -> None
        """
        Set `text` as the current text (adding it to the model if necessary).
        """
        idx = self.findData(text, Qt.EditRole, Qt.MatchExactly)
        if idx != -1:
            self.setCurrentIndex(idx)
        else:
            self.addItem(text)
            self.setCurrentIndex(self.count() - 1)