openprocess 0.7.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.
- cpnpy/__init__.py +46 -0
- openprocess/__init__.py +57 -0
- openprocess/analysis/__init__.py +0 -0
- openprocess/analysis/state_space.py +521 -0
- openprocess/analysis/state_space_process.py +251 -0
- openprocess/cli.py +742 -0
- openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +31 -0
- openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +38 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +27 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +29 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +65 -0
- openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +1 -0
- openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +67 -0
- openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +75 -0
- openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +5 -0
- openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +27 -0
- openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +65 -0
- openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +1 -0
- openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +62 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +1 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +36 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +28 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +30 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +36 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +69 -0
- openprocess/exercises/pack.md +14 -0
- openprocess/flow/__init__.py +50 -0
- openprocess/flow/box.py +466 -0
- openprocess/flow/boxes/__init__.py +7 -0
- openprocess/flow/boxes/check.py +119 -0
- openprocess/flow/boxes/compare.py +16 -0
- openprocess/flow/boxes/cpn.py +53 -0
- openprocess/flow/boxes/discover.py +124 -0
- openprocess/flow/boxes/filter.py +80 -0
- openprocess/flow/boxes/input.py +124 -0
- openprocess/flow/boxes/output.py +52 -0
- openprocess/flow/boxes/predict.py +186 -0
- openprocess/flow/boxes/science.py +159 -0
- openprocess/flow/boxes/sweeps.py +18 -0
- openprocess/flow/convert.py +187 -0
- openprocess/flow/datasets.py +198 -0
- openprocess/flow/explain.py +115 -0
- openprocess/flow/library.py +222 -0
- openprocess/flow/record.py +385 -0
- openprocess/flow/runner.py +357 -0
- openprocess/flow/sweep.py +92 -0
- openprocess/flow/types.py +290 -0
- openprocess/flow/workflow.py +628 -0
- openprocess/gui/__init__.py +0 -0
- openprocess/gui/app.py +90 -0
- openprocess/gui/arc_editing.py +295 -0
- openprocess/gui/canvas.py +1414 -0
- openprocess/gui/flow/__init__.py +8 -0
- openprocess/gui/flow/canvas.py +854 -0
- openprocess/gui/flow/page.py +972 -0
- openprocess/gui/flow/templates.py +131 -0
- openprocess/gui/flow/viewers.py +665 -0
- openprocess/gui/items.py +1275 -0
- openprocess/gui/learn/answer_boxes.py +978 -0
- openprocess/gui/learn/concealment.py +91 -0
- openprocess/gui/learn/mode.py +1181 -0
- openprocess/gui/panning.py +241 -0
- openprocess/gui/resources/openprocess-icon.png +0 -0
- openprocess/gui/studio/__init__.py +1 -0
- openprocess/gui/studio/__main__.py +3 -0
- openprocess/gui/studio/app.py +4031 -0
- openprocess/gui/studio/charts.py +115 -0
- openprocess/gui/studio/compare_page.py +487 -0
- openprocess/gui/studio/cpn_page.py +1858 -0
- openprocess/gui/studio/definition_view.py +284 -0
- openprocess/gui/studio/derivation_view.py +421 -0
- openprocess/gui/studio/documents.py +152 -0
- openprocess/gui/studio/dotted_chart.py +1401 -0
- openprocess/gui/studio/file_dialogs.py +143 -0
- openprocess/gui/studio/filter_dialog.py +247 -0
- openprocess/gui/studio/graph_builders.py +176 -0
- openprocess/gui/studio/graph_view.py +682 -0
- openprocess/gui/studio/instances.py +413 -0
- openprocess/gui/studio/log_editor.py +675 -0
- openprocess/gui/studio/log_page.py +800 -0
- openprocess/gui/studio/markdown_view.py +127 -0
- openprocess/gui/studio/mathtext.py +260 -0
- openprocess/gui/studio/ml_highlighter.py +75 -0
- openprocess/gui/studio/model_page.py +760 -0
- openprocess/gui/studio/net_comparison.py +124 -0
- openprocess/gui/studio/notes_overlay.py +275 -0
- openprocess/gui/studio/petri_page.py +844 -0
- openprocess/gui/studio/regions_view.py +502 -0
- openprocess/gui/studio/sidebar.py +149 -0
- openprocess/gui/studio/style.py +503 -0
- openprocess/gui/studio/tool_icons.py +134 -0
- openprocess/gui/studio/updates.py +439 -0
- openprocess/gui/studio/widgets.py +899 -0
- openprocess/gui/studio/workers.py +60 -0
- openprocess/gui/studio/workspace.py +447 -0
- openprocess/gui/theme.py +394 -0
- openprocess/gui/tidy.py +86 -0
- openprocess/io/__init__.py +0 -0
- openprocess/io/cpn_reader.py +389 -0
- openprocess/io/cpn_writer.py +357 -0
- openprocess/learn/__init__.py +23 -0
- openprocess/learn/answers.py +188 -0
- openprocess/learn/checks.py +953 -0
- openprocess/learn/computed.py +1180 -0
- openprocess/learn/context.py +145 -0
- openprocess/learn/exam.py +169 -0
- openprocess/learn/exercise-packs.md +325 -0
- openprocess/learn/importer.py +216 -0
- openprocess/learn/notation.py +474 -0
- openprocess/learn/pack.py +511 -0
- openprocess/learn/sheet.py +296 -0
- openprocess/mining/__init__.py +73 -0
- openprocess/mining/analysis.py +689 -0
- openprocess/mining/columns.py +282 -0
- openprocess/mining/compare_nets.py +246 -0
- openprocess/mining/conformance/__init__.py +0 -0
- openprocess/mining/conformance/alignments.py +263 -0
- openprocess/mining/conformance/quality.py +145 -0
- openprocess/mining/conformance/token_replay.py +252 -0
- openprocess/mining/csv_import.py +222 -0
- openprocess/mining/definitions.py +584 -0
- openprocess/mining/dfg.py +187 -0
- openprocess/mining/discovery/__init__.py +0 -0
- openprocess/mining/discovery/alpha.py +168 -0
- openprocess/mining/discovery/heuristics.py +332 -0
- openprocess/mining/discovery/inductive.py +477 -0
- openprocess/mining/discovery/state_regions.py +62 -0
- openprocess/mining/filtering.py +237 -0
- openprocess/mining/footprint.py +183 -0
- openprocess/mining/invariants.py +191 -0
- openprocess/mining/layout.py +279 -0
- openprocess/mining/log.py +364 -0
- openprocess/mining/petrinet.py +354 -0
- openprocess/mining/playout.py +75 -0
- openprocess/mining/pm4py_bridge.py +82 -0
- openprocess/mining/pnml.py +223 -0
- openprocess/mining/processtree.py +216 -0
- openprocess/mining/regions.py +476 -0
- openprocess/mining/stats.py +160 -0
- openprocess/mining/structure.py +374 -0
- openprocess/mining/transition_system.py +409 -0
- openprocess/mining/xes.py +399 -0
- openprocess/ml/__init__.py +0 -0
- openprocess/ml/ast_nodes.py +332 -0
- openprocess/ml/builtins.py +364 -0
- openprocess/ml/colorsets.py +522 -0
- openprocess/ml/errors.py +60 -0
- openprocess/ml/evaluator.py +754 -0
- openprocess/ml/lexer.py +277 -0
- openprocess/ml/multiset.py +417 -0
- openprocess/ml/parser.py +737 -0
- openprocess/ml/values.py +319 -0
- openprocess/model/__init__.py +0 -0
- openprocess/model/declarations.py +617 -0
- openprocess/model/examples.py +98 -0
- openprocess/model/net.py +701 -0
- openprocess/model/plain.py +192 -0
- openprocess/references.py +280 -0
- openprocess/sim/__init__.py +0 -0
- openprocess/sim/binding.py +620 -0
- openprocess/sim/export.py +66 -0
- openprocess/sim/simulator.py +315 -0
- openprocess/teaching/__init__.py +4 -0
- openprocess/teaching/answers.py +4 -0
- openprocess/teaching/checks.py +5 -0
- openprocess/teaching/pack.py +4 -0
- openprocess/teaching/sheet.py +4 -0
- openprocess-0.7.0.dist-info/METADATA +927 -0
- openprocess-0.7.0.dist-info/RECORD +173 -0
- openprocess-0.7.0.dist-info/WHEEL +5 -0
- openprocess-0.7.0.dist-info/entry_points.txt +6 -0
- openprocess-0.7.0.dist-info/licenses/LICENSE +21 -0
- openprocess-0.7.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Definitions on screen: hover tooltips, click pop-ups and the reference dialog.
|
|
2
|
+
|
|
3
|
+
Every property an Analysis tab reports (sound, live, free-choice, ...) is
|
|
4
|
+
defined in :mod:`openprocess.mining.definitions`. Here that becomes:
|
|
5
|
+
|
|
6
|
+
* a **tooltip** when the mouse rests on the property: the definition's
|
|
7
|
+
formulas and, underneath, the same formulas **filled in for this net**
|
|
8
|
+
(its place names, and the marking and firing sequence that break the
|
|
9
|
+
property when it fails -- see :mod:`.instances`);
|
|
10
|
+
* a **pop-up** when the property is clicked, which also has the notes, the
|
|
11
|
+
definitions it builds on (click to follow) and the source;
|
|
12
|
+
* the **Definitions** dialog (Help ▸ Definitions, or *All definitions…*):
|
|
13
|
+
the whole reference, the same text as ``docs/definitions.md``.
|
|
14
|
+
|
|
15
|
+
Formulas are typeset by :mod:`.mathtext`.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from html import escape
|
|
21
|
+
|
|
22
|
+
from PySide6.QtCore import QEvent, QObject, QPoint, Qt, QUrl
|
|
23
|
+
from PySide6.QtGui import QCursor, QGuiApplication
|
|
24
|
+
from PySide6.QtWidgets import (
|
|
25
|
+
QDialog, QFrame, QHBoxLayout, QPushButton, QTextBrowser, QVBoxLayout, QWidget,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from ...mining.definitions import BY_KEY, DEFINITIONS, SECTIONS, Definition, lookup
|
|
29
|
+
from . import style
|
|
30
|
+
from .mathtext import display, render_text
|
|
31
|
+
|
|
32
|
+
WIDTH = 480
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# HTML
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
def definition_html(definition: Definition, instance: list[str] | None = None,
|
|
39
|
+
compact: bool = False) -> str:
|
|
40
|
+
"""The definition (and its instance for this net) as rich text.
|
|
41
|
+
|
|
42
|
+
``compact`` is the tooltip: formulas and instance only, with a hint that
|
|
43
|
+
a click shows more.
|
|
44
|
+
"""
|
|
45
|
+
t = style.tokens()
|
|
46
|
+
parts = [f"<div style='font-size: 15px; font-weight: 600'>{escape(definition.name)}"
|
|
47
|
+
"</div>",
|
|
48
|
+
f"<div style='color: {t.text_secondary}; margin-top: 2px'>"
|
|
49
|
+
f"{render_text(definition.summary)}</div>"]
|
|
50
|
+
parts += [display(formula) for formula in definition.formulas]
|
|
51
|
+
if instance:
|
|
52
|
+
parts.append(f"<div style='margin-top: 8px; font-weight: 600; color: {t.accent}'>"
|
|
53
|
+
"For this net</div>")
|
|
54
|
+
parts += [display(line) for line in instance]
|
|
55
|
+
if compact:
|
|
56
|
+
parts.append(f"<div style='color: {t.text_muted}; font-size: 11px; margin-top: 6px'>"
|
|
57
|
+
"Click for notes and related definitions.</div>")
|
|
58
|
+
else:
|
|
59
|
+
for note in definition.notes:
|
|
60
|
+
parts.append(f"<p style='color: {t.text_secondary}'>{render_text(note)}</p>")
|
|
61
|
+
if definition.uses:
|
|
62
|
+
links = ", ".join(f"<a href='def:{key}'>{escape(BY_KEY[key].name)}</a>"
|
|
63
|
+
for key in definition.uses)
|
|
64
|
+
parts.append(f"<p>Builds on: {links}.</p>")
|
|
65
|
+
if definition.source:
|
|
66
|
+
parts.append(f"<p style='color: {t.text_muted}; font-size: 11px'>Source: "
|
|
67
|
+
f"{render_text(definition.source)}.</p>")
|
|
68
|
+
body = "".join(parts)
|
|
69
|
+
return (f"<table width='{WIDTH - 24}' cellpadding='0' cellspacing='0'><tr><td>"
|
|
70
|
+
f"{body}</td></tr></table>")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def reference_html() -> str:
|
|
74
|
+
"""Every definition, grouped by section, with anchors for links."""
|
|
75
|
+
t = style.tokens()
|
|
76
|
+
parts = ["<h2>Definitions</h2>",
|
|
77
|
+
f"<p style='color: {t.text_secondary}'>Every property the analyses report. "
|
|
78
|
+
"Throughout, " + render_text("$N = (P, T, F)$") + " is a Petri net and, for "
|
|
79
|
+
"WF-nets, " + render_text("$i$ and $o$") + " are its start and end places. "
|
|
80
|
+
"The same text is in <code>docs/definitions.md</code>.</p>"]
|
|
81
|
+
for section, title, intro in SECTIONS:
|
|
82
|
+
parts.append(f"<h3 style='margin-top: 18px'>{escape(title)}</h3>"
|
|
83
|
+
f"<p style='color: {t.text_secondary}'>{render_text(intro)}</p>")
|
|
84
|
+
for definition in DEFINITIONS:
|
|
85
|
+
if definition.section == section:
|
|
86
|
+
parts.append(f"<a name='{definition.key}'></a>"
|
|
87
|
+
f"<div style='margin: 10px 0 14px 0'>"
|
|
88
|
+
f"{definition_html(definition)}</div>")
|
|
89
|
+
return "".join(parts)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Widgets
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
class _Browser(QTextBrowser):
|
|
96
|
+
"""Rich text with ``def:key`` links handled by the owner."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, on_link) -> None:
|
|
99
|
+
super().__init__()
|
|
100
|
+
self.setOpenLinks(False)
|
|
101
|
+
self.setFrameShape(QFrame.NoFrame)
|
|
102
|
+
self.document().setDefaultStyleSheet(
|
|
103
|
+
f"a {{ color: {style.tokens().accent}; text-decoration: none; }}")
|
|
104
|
+
self.anchorClicked.connect(lambda url: on_link(url.toString()))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class DefinitionPopup(QFrame):
|
|
108
|
+
"""The pop-up a click on a property opens (closes on any click outside)."""
|
|
109
|
+
|
|
110
|
+
def __init__(self, definition: Definition, instance: list[str] | None = None,
|
|
111
|
+
parent: QWidget | None = None) -> None:
|
|
112
|
+
super().__init__(parent, Qt.Popup)
|
|
113
|
+
self.setObjectName("definitionPopup")
|
|
114
|
+
t = style.tokens()
|
|
115
|
+
self.setStyleSheet(f"#definitionPopup {{ background: {t.surface}; "
|
|
116
|
+
f"border: 1px solid {t.border}; border-radius: 10px; }}"
|
|
117
|
+
f"QTextBrowser {{ background: transparent; }}")
|
|
118
|
+
layout = QVBoxLayout(self)
|
|
119
|
+
layout.setContentsMargins(12, 10, 12, 10)
|
|
120
|
+
self.browser = _Browser(self._link)
|
|
121
|
+
self.browser.setFixedWidth(WIDTH)
|
|
122
|
+
layout.addWidget(self.browser)
|
|
123
|
+
row = QHBoxLayout()
|
|
124
|
+
self.back_button = QPushButton("← Back")
|
|
125
|
+
self.back_button.clicked.connect(self._back)
|
|
126
|
+
everything = QPushButton("All definitions…")
|
|
127
|
+
everything.clicked.connect(lambda: self._open_reference(self.current.key))
|
|
128
|
+
row.addWidget(self.back_button)
|
|
129
|
+
row.addStretch(1)
|
|
130
|
+
row.addWidget(everything)
|
|
131
|
+
layout.addLayout(row)
|
|
132
|
+
self.history: list[tuple[Definition, list[str] | None]] = []
|
|
133
|
+
self.current = definition
|
|
134
|
+
self._show(definition, instance)
|
|
135
|
+
|
|
136
|
+
def _show(self, definition: Definition, instance) -> None:
|
|
137
|
+
self.current, self.instance = definition, instance
|
|
138
|
+
self.browser.setHtml(definition_html(definition, instance))
|
|
139
|
+
self.back_button.setVisible(bool(self.history))
|
|
140
|
+
document = self.browser.document()
|
|
141
|
+
document.setTextWidth(WIDTH - 8)
|
|
142
|
+
screen = QGuiApplication.screenAt(QCursor.pos()) or QGuiApplication.primaryScreen()
|
|
143
|
+
limit = int(screen.availableGeometry().height() * 0.7) if screen else 560
|
|
144
|
+
self.browser.setFixedHeight(min(int(document.size().height()) + 12, limit))
|
|
145
|
+
self.adjustSize()
|
|
146
|
+
|
|
147
|
+
def _link(self, target: str) -> None:
|
|
148
|
+
if target.startswith("def:") and target[4:] in BY_KEY:
|
|
149
|
+
self.history.append((self.current, self.instance))
|
|
150
|
+
self._show(BY_KEY[target[4:]], None)
|
|
151
|
+
|
|
152
|
+
def _back(self) -> None:
|
|
153
|
+
if self.history:
|
|
154
|
+
self._show(*self.history.pop())
|
|
155
|
+
|
|
156
|
+
def _open_reference(self, key: str) -> None:
|
|
157
|
+
self.close()
|
|
158
|
+
show_reference(key, self.parentWidget())
|
|
159
|
+
|
|
160
|
+
def show_near(self, point: QPoint) -> None:
|
|
161
|
+
"""Open below-right of ``point``, kept on the screen."""
|
|
162
|
+
self.adjustSize()
|
|
163
|
+
screen = QGuiApplication.screenAt(point) or QGuiApplication.primaryScreen()
|
|
164
|
+
area = screen.availableGeometry()
|
|
165
|
+
x = min(point.x() + 8, area.right() - self.width() - 8)
|
|
166
|
+
y = point.y() + 12
|
|
167
|
+
if y + self.height() > area.bottom():
|
|
168
|
+
y = max(area.top() + 8, point.y() - self.height() - 12)
|
|
169
|
+
self.move(max(area.left() + 8, x), y)
|
|
170
|
+
self.show()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class DefinitionsDialog(QDialog):
|
|
174
|
+
"""The complete reference (Help ▸ Definitions)."""
|
|
175
|
+
|
|
176
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
|
177
|
+
super().__init__(parent)
|
|
178
|
+
self.setWindowTitle("Definitions")
|
|
179
|
+
self.resize(640, 760)
|
|
180
|
+
layout = QVBoxLayout(self)
|
|
181
|
+
self.browser = _Browser(self._link)
|
|
182
|
+
self.browser.setHtml(reference_html())
|
|
183
|
+
layout.addWidget(self.browser)
|
|
184
|
+
|
|
185
|
+
def _link(self, target: str) -> None:
|
|
186
|
+
if target.startswith("def:"):
|
|
187
|
+
self.browser.scrollToAnchor(target[4:])
|
|
188
|
+
|
|
189
|
+
def show_key(self, key: str | None) -> None:
|
|
190
|
+
if key:
|
|
191
|
+
self.browser.scrollToAnchor(key)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
_dialog: DefinitionsDialog | None = None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def show_reference(key: str | None = None, parent: QWidget | None = None) -> DefinitionsDialog:
|
|
198
|
+
"""Open (or raise) the Definitions dialog, scrolled to ``key``."""
|
|
199
|
+
global _dialog
|
|
200
|
+
if _dialog is None:
|
|
201
|
+
_dialog = DefinitionsDialog(parent.window() if parent is not None else None)
|
|
202
|
+
_dialog.show()
|
|
203
|
+
_dialog.raise_()
|
|
204
|
+
_dialog.activateWindow()
|
|
205
|
+
_dialog.show_key(key)
|
|
206
|
+
return _dialog
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class _ClickFilter(QObject):
|
|
210
|
+
"""Opens the pop-up when the widget it watches is clicked."""
|
|
211
|
+
|
|
212
|
+
def __init__(self, widget: QWidget, definition: Definition, instance) -> None:
|
|
213
|
+
super().__init__(widget)
|
|
214
|
+
self.widget, self.definition, self.instance = widget, definition, instance
|
|
215
|
+
|
|
216
|
+
def eventFilter(self, watched, event) -> bool: # noqa: N802
|
|
217
|
+
if event.type() == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton:
|
|
218
|
+
popup = DefinitionPopup(self.definition, self.instance, self.widget)
|
|
219
|
+
popup.show_near(QCursor.pos())
|
|
220
|
+
self.widget.last_popup = popup # for tests
|
|
221
|
+
return True
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def attach_definition(widget: QWidget, key: str | None, instance: list[str] | None = None,
|
|
226
|
+
targets: list[QWidget] | None = None) -> Definition | None:
|
|
227
|
+
"""Give ``widget`` the definition ``key``: tooltip on hover, pop-up on click.
|
|
228
|
+
|
|
229
|
+
``targets`` are the child widgets that react (default: the widget
|
|
230
|
+
itself); selectable text labels are best left out, so their text can
|
|
231
|
+
still be selected.
|
|
232
|
+
"""
|
|
233
|
+
definition = lookup(key)
|
|
234
|
+
if definition is None:
|
|
235
|
+
return None
|
|
236
|
+
tooltip = definition_html(definition, instance, compact=True)
|
|
237
|
+
for target in targets or [widget]:
|
|
238
|
+
target.setToolTip(tooltip)
|
|
239
|
+
target.setCursor(Qt.PointingHandCursor)
|
|
240
|
+
target.installEventFilter(_ClickFilter(widget, definition, instance))
|
|
241
|
+
widget.definition = definition
|
|
242
|
+
return definition
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# Guides: References, Writing Exercise Packs
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
GUIDES = {"references": "References", "exercise-packs": "Writing Exercise Packs"}
|
|
249
|
+
_guides: dict[str, QDialog] = {}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def guide_markdown(name: str) -> str:
|
|
253
|
+
"""The text of a guide (Markdown)."""
|
|
254
|
+
if name == "references":
|
|
255
|
+
from ... import references
|
|
256
|
+
text = references.markdown()
|
|
257
|
+
return text.split("-->", 1)[-1].lstrip()
|
|
258
|
+
from pathlib import Path
|
|
259
|
+
from ...learn import __file__ as learn
|
|
260
|
+
return (Path(learn).parent / f"{name}.md").read_text(encoding="utf-8")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def show_guide(name: str, parent: QWidget | None = None) -> QDialog:
|
|
264
|
+
"""Open (or raise) a guide in a window of its own."""
|
|
265
|
+
from .markdown_view import markdown_html
|
|
266
|
+
dialog = _guides.get(name)
|
|
267
|
+
if dialog is None:
|
|
268
|
+
dialog = QDialog(parent.window() if parent is not None else None)
|
|
269
|
+
dialog.setWindowTitle(GUIDES.get(name, name))
|
|
270
|
+
dialog.resize(760, 760)
|
|
271
|
+
layout = QVBoxLayout(dialog)
|
|
272
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
273
|
+
browser = QTextBrowser()
|
|
274
|
+
browser.setObjectName("guideBrowser")
|
|
275
|
+
browser.setOpenExternalLinks(True)
|
|
276
|
+
browser.setFrameShape(QFrame.NoFrame)
|
|
277
|
+
browser.setHtml(markdown_html(guide_markdown(name)))
|
|
278
|
+
layout.addWidget(browser)
|
|
279
|
+
dialog.browser = browser
|
|
280
|
+
_guides[name] = dialog
|
|
281
|
+
dialog.show()
|
|
282
|
+
dialog.raise_()
|
|
283
|
+
dialog.activateWindow()
|
|
284
|
+
return dialog
|