triplot 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dscpanel/ui/palette.py ADDED
@@ -0,0 +1,193 @@
1
+ """F3: the operator palette.
2
+
3
+ Blender's command search: type a few letters, see what exists,
4
+ press Enter. Its value here is the same as there - the window stays quiet
5
+ because the twenty things you do rarely are one keystroke away instead of in
6
+ a menu - plus one thing that is specific to a plot: the list is filtered by
7
+ what is SELECTED, so "Distribute offsets evenly" appears when three scans are
8
+ selected and not when one is.
9
+
10
+ Disabled operators are listed, greyed, at the bottom rather than hidden. That
11
+ is deliberate: the palette is also how somebody finds out what the program
12
+ can do, and an empty list teaches nothing. Seeing "Set molar mass (needs one
13
+ sample selected)" greyed out says what to do next.
14
+ """
15
+
16
+ from PySide6.QtCore import QPoint, Qt
17
+ from PySide6.QtGui import QGuiApplication
18
+ from PySide6.QtWidgets import (QAbstractItemView, QDialog, QHeaderView,
19
+ QLineEdit, QTreeWidget, QTreeWidgetItem,
20
+ QVBoxLayout)
21
+
22
+
23
+ class OperatorPalette(QDialog):
24
+ """Type-to-filter operator list; Enter or double-click runs one."""
25
+
26
+ def __init__(self, registry, ctx, parent=None, previous=""):
27
+ QDialog.__init__(self, parent)
28
+ self.setWindowTitle("Operator search")
29
+ self.setModal(True)
30
+ self.registry = registry
31
+ self.ctx = ctx
32
+ self.chosen = None
33
+ layout = QVBoxLayout(self)
34
+ layout.setContentsMargins(8, 8, 8, 8)
35
+ layout.setSpacing(6)
36
+ self.edit = QLineEdit(self)
37
+ self.edit.setPlaceholderText("Search operators...")
38
+ self.edit.setClearButtonEnabled(True)
39
+ layout.addWidget(self.edit)
40
+ # A two-column tree rather than a list with a tab in the text: a
41
+ # tab in a QListWidget's label is not a tab stop, so the keys came
42
+ # out ragged, which is exactly the sort of thing that makes a
43
+ # palette feel unfinished.
44
+ self.list = QTreeWidget(self)
45
+ self.list.setColumnCount(2)
46
+ self.list.setHeaderHidden(True)
47
+ self.list.setRootIsDecorated(False)
48
+ self.list.setSelectionMode(QAbstractItemView.SingleSelection)
49
+ self.list.setUniformRowHeights(True)
50
+ self.list.header().setSectionResizeMode(0, QHeaderView.Stretch)
51
+ self.list.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
52
+ layout.addWidget(self.list, 1)
53
+ self.resize(520, 380)
54
+ self.edit.textChanged.connect(self._refill)
55
+ self.edit.returnPressed.connect(self._run_current)
56
+ self.list.itemActivated.connect(lambda _i, _c=0: self._run_current())
57
+ self.list.itemDoubleClicked.connect(
58
+ lambda _i, _c=0: self._run_current())
59
+ # The arrow keys belong to the LIST while the cursor is in the box,
60
+ # which is what makes "type two letters, arrow down, Enter" work.
61
+ self.edit.installEventFilter(self)
62
+ self._refill(previous)
63
+ if previous:
64
+ self.edit.setText(previous)
65
+ self.edit.selectAll()
66
+
67
+ def eventFilter(self, obj, event):
68
+ if obj is self.edit and event.type() == event.Type.KeyPress:
69
+ if event.key() in (Qt.Key_Down, Qt.Key_Up):
70
+ self.list.setFocus()
71
+ self.list.keyPressEvent(event)
72
+ self.edit.setFocus()
73
+ return True
74
+ return QDialog.eventFilter(self, obj, event)
75
+
76
+ def _refill(self, text=""):
77
+ self.list.clear()
78
+ for op, enabled in self.registry.search(text, self.ctx):
79
+ item = QTreeWidgetItem(self.list)
80
+ item.setText(0, op.label)
81
+ item.setText(1, op.shortcut or op.key or "")
82
+ item.setTextAlignment(1, int(Qt.AlignRight | Qt.AlignVCenter))
83
+ item.setData(0, Qt.UserRole, op.id)
84
+ if not enabled:
85
+ item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
86
+ for row in range(self.list.topLevelItemCount()):
87
+ item = self.list.topLevelItem(row)
88
+ if item.flags() & Qt.ItemIsEnabled:
89
+ self.list.setCurrentItem(item)
90
+ break
91
+
92
+ def _run_current(self):
93
+ item = self.list.currentItem()
94
+ if item is None or not (item.flags() & Qt.ItemIsEnabled):
95
+ return
96
+ self.chosen = item.data(0, Qt.UserRole)
97
+ self.accept()
98
+
99
+
100
+ class MeasurePalette(QDialog):
101
+ """Which analysis to run on the two cursors that are down.
102
+
103
+ The same shape as the operator palette, on purpose: type to filter,
104
+ arrows to walk, Enter to run - a quick-select menu as in Blender, and
105
+ the one the hands already know.
106
+ """
107
+
108
+ def __init__(self, models, parent=None, previous=""):
109
+ QDialog.__init__(self, parent)
110
+ self.setWindowTitle("Analyse the interval")
111
+ self.setModal(True)
112
+ self.models = list(models)
113
+ self.chosen = None
114
+ layout = QVBoxLayout(self)
115
+ layout.setContentsMargins(8, 8, 8, 8)
116
+ layout.setSpacing(6)
117
+ self.edit = QLineEdit(self)
118
+ self.edit.setPlaceholderText("Onset, integration, glass transition...")
119
+ self.edit.setClearButtonEnabled(True)
120
+ layout.addWidget(self.edit)
121
+ self.list = QTreeWidget(self)
122
+ self.list.setColumnCount(2)
123
+ self.list.setHeaderHidden(True)
124
+ self.list.setRootIsDecorated(False)
125
+ self.list.setSelectionMode(QAbstractItemView.SingleSelection)
126
+ self.list.header().setSectionResizeMode(0, QHeaderView.Stretch)
127
+ self.list.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
128
+ layout.addWidget(self.list, 1)
129
+ # Small: it opens under the pointer, over the curve being measured,
130
+ # and should cover as little of it as a menu would.
131
+ self.resize(560, 205)
132
+ self.edit.textChanged.connect(self._refill)
133
+ self.edit.returnPressed.connect(self._run_current)
134
+ self.list.itemActivated.connect(lambda _i, _c=0: self._run_current())
135
+ # ONE click chooses, as in a menu. The hand has just let go of a
136
+ # drag; asking it for a double-click on top is one gesture too many.
137
+ self.list.itemClicked.connect(lambda _i, _c=0: self._run_current())
138
+ self.edit.installEventFilter(self)
139
+ self._refill(previous)
140
+
141
+ def place_at(self, point):
142
+ """Open with the top left a little above-left of `point`, on screen.
143
+
144
+ So the first entry is under the pointer, and a list opened near an
145
+ edge is pulled back rather than half off the screen.
146
+ """
147
+ target = QPoint(point.x() - 24, point.y() - 24)
148
+ screen = QGuiApplication.screenAt(point) or QGuiApplication.primaryScreen()
149
+ if screen is not None:
150
+ area = screen.availableGeometry()
151
+ target.setX(max(area.left(), min(target.x(),
152
+ area.right() - self.width())))
153
+ target.setY(max(area.top(), min(target.y(),
154
+ area.bottom() - self.height() - 30)))
155
+ self.move(target)
156
+ return target
157
+
158
+ def eventFilter(self, obj, event):
159
+ if obj is self.edit and event.type() == event.Type.KeyPress:
160
+ if event.key() in (Qt.Key_Down, Qt.Key_Up):
161
+ self.list.setFocus()
162
+ self.list.keyPressEvent(event)
163
+ self.edit.setFocus()
164
+ return True
165
+ return QDialog.eventFilter(self, obj, event)
166
+
167
+ def _refill(self, text=""):
168
+ self.list.clear()
169
+ words = [w for w in (text or "").lower().split() if w]
170
+ for entry in self.models:
171
+ hay = (entry.title + " " + entry.name + " " + entry.note).lower()
172
+ if all(word in hay for word in words):
173
+ item = QTreeWidgetItem(self.list)
174
+ # The TITLE only: this list answers "which analysis?", and a
175
+ # row reading "Peak Integration (enthalpy)" is the model
176
+ # string, not the word anybody is looking for.
177
+ item.setText(0, entry.title)
178
+ item.setText(1, entry.note)
179
+ item.setData(0, Qt.UserRole, entry.name)
180
+ if self.list.topLevelItemCount():
181
+ self.list.setCurrentItem(self.list.topLevelItem(0))
182
+
183
+ def _run_current(self):
184
+ item = self.list.currentItem()
185
+ if item is None:
186
+ return
187
+ self.chosen = item.data(0, Qt.UserRole)
188
+ self.accept()
189
+
190
+ # Qt calls the handlers here by itself; an error in one is logged and
191
+ # survived rather than the end of the program (`core/log.py`).
192
+ from ..core import log as _log
193
+ _log.guard_classes(globals(), __name__)