umaudemc 0.12.2__py3-none-any.whl → 0.13.1__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.
- umaudemc/__init__.py +1 -1
- umaudemc/__main__.py +5 -0
- umaudemc/api.py +1 -1
- umaudemc/backend/_spot.py +7 -8
- umaudemc/command/graph.py +30 -2
- umaudemc/common.py +0 -24
- umaudemc/counterprint.py +12 -4
- umaudemc/data/smcview.js +42 -29
- umaudemc/formatter.py +20 -25
- umaudemc/grapher.py +6 -5
- umaudemc/gtk.py +88 -499
- umaudemc/kleene.py +156 -0
- umaudemc/opsem.py +1 -1
- umaudemc/probabilistic.py +12 -9
- umaudemc/pyslang.py +320 -186
- umaudemc/simulators.py +3 -2
- umaudemc/statistical.py +1 -0
- umaudemc/webui.py +31 -33
- umaudemc/wrappers.py +44 -2
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/METADATA +1 -1
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/RECORD +25 -24
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/WHEEL +1 -1
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/LICENSE +0 -0
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/entry_points.txt +0 -0
- {umaudemc-0.12.2.dist-info → umaudemc-0.13.1.dist-info}/top_level.txt +0 -0
umaudemc/gtk.py
CHANGED
|
@@ -1,529 +1,109 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Desktop interface with Gtk
|
|
2
|
+
# Desktop interface with Gtk (wrapper around the web interface)
|
|
3
3
|
#
|
|
4
4
|
|
|
5
|
-
import threading
|
|
6
|
-
from xml.sax.saxutils import escape
|
|
7
|
-
|
|
8
5
|
import gi
|
|
9
|
-
gi.require_version('Gtk', '
|
|
6
|
+
gi.require_version('Gtk', '4.0')
|
|
10
7
|
gi.require_version('Gio', '2.0')
|
|
11
|
-
gi.require_version('
|
|
12
|
-
from gi.repository import Gtk, Gio, GLib,
|
|
13
|
-
|
|
14
|
-
from . import resources
|
|
15
|
-
from . import mproc
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
MODULE_TYPE_NAMES = {
|
|
19
|
-
'fmod' : 'Functional module',
|
|
20
|
-
'mod' : 'System module',
|
|
21
|
-
'smod' : 'Strategy module',
|
|
22
|
-
'fth' : 'Functional theory',
|
|
23
|
-
'th' : 'System theory',
|
|
24
|
-
'sth' : 'Strategy theory'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class Banner(Gtk.Bin):
|
|
29
|
-
"""Banner where module and counterexample information is presented"""
|
|
30
|
-
|
|
31
|
-
__gsignals__ = {
|
|
32
|
-
# A strategy or atomic proposition name has been clicked
|
|
33
|
-
'hint-clicked': (GObject.SignalFlags.RUN_FIRST, None, (bool, str, int))
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
def __init__(self):
|
|
37
|
-
# Three widgets may appear in the banner (only one at a time) to respectively
|
|
38
|
-
# show a fixed message, module information or a counterexample
|
|
39
|
-
|
|
40
|
-
self.label = Gtk.Label(
|
|
41
|
-
label='Please select a Maude file and a Maude module defining the system and properties specification',
|
|
42
|
-
use_markup=True,
|
|
43
|
-
justify=Gtk.Justification.CENTER,
|
|
44
|
-
track_visited_links=False
|
|
45
|
-
)
|
|
46
|
-
self.grid = None
|
|
47
|
-
self.canvas = None
|
|
48
|
-
|
|
49
|
-
Gtk.Bin.__init__(self, child=self.label)
|
|
50
|
-
|
|
51
|
-
def __makeGrid(self, info):
|
|
52
|
-
"""Compose the grid where module information is presented"""
|
|
53
|
-
|
|
54
|
-
# The grid has 2 columns and 4 rows starting with a header with the module
|
|
55
|
-
# name and type, and followed by some attributes of the module whose values
|
|
56
|
-
# appear in the second column
|
|
57
|
-
|
|
58
|
-
self.module_type = Gtk.Label(label=f'<big>{MODULE_TYPE_NAMES[info["type"]]}</big>', use_markup=True)
|
|
59
|
-
self.module_name = Gtk.Label(label=f'<big>{info["name"]}</big>', use_markup=True)
|
|
60
|
-
|
|
61
|
-
# Labels for each module attribute name
|
|
62
|
-
sort_label = Gtk.Label.new('State sort:')
|
|
63
|
-
props_label = Gtk.Label.new('Atomic propositions:')
|
|
64
|
-
self.strats_label = Gtk.Label.new('Strategies:')
|
|
65
|
-
|
|
66
|
-
# Labels for each module attribute value (to be filled on its selection)
|
|
67
|
-
self.sort_value = Gtk.Label.new('')
|
|
68
|
-
self.props_value = Gtk.Label(label='', use_markup=True, track_visited_links=False)
|
|
69
|
-
self.strats_value = Gtk.Label(label='', use_markup=True, track_visited_links=False)
|
|
70
|
-
self.props_value.set_line_wrap(True)
|
|
71
|
-
self.strats_value.set_line_wrap(True)
|
|
72
|
-
|
|
73
|
-
# Propositions and strategies can be clicked to be filled
|
|
74
|
-
# in their corresponding fields
|
|
75
|
-
self.props_value.connect('activate-link', self.hint_clicked)
|
|
76
|
-
self.strats_value.connect('activate-link', self.hint_clicked)
|
|
77
|
-
|
|
78
|
-
# Align labels to the right and values to the left
|
|
79
|
-
self.module_type.set_halign(Gtk.Align.END)
|
|
80
|
-
sort_label.set_halign(Gtk.Align.END)
|
|
81
|
-
props_label.set_halign(Gtk.Align.END)
|
|
82
|
-
self.strats_label.set_halign(Gtk.Align.END)
|
|
83
|
-
self.module_name.set_halign(Gtk.Align.START)
|
|
84
|
-
self.sort_value.set_halign(Gtk.Align.START)
|
|
85
|
-
self.props_value.set_halign(Gtk.Align.START)
|
|
86
|
-
self.strats_value.set_halign(Gtk.Align.START)
|
|
87
|
-
|
|
88
|
-
self.grid = Gtk.Grid.new()
|
|
89
|
-
self.grid.set_valign(Gtk.Align.CENTER)
|
|
90
|
-
self.grid.set_halign(Gtk.Align.CENTER)
|
|
91
|
-
self.grid.set_column_spacing(15)
|
|
92
|
-
self.grid.set_row_spacing(5)
|
|
93
|
-
self.grid.attach(self.module_type, 0, 0, 1, 1)
|
|
94
|
-
self.grid.attach(self.module_name, 1, 0, 1, 1)
|
|
95
|
-
self.grid.attach(sort_label, 0, 1, 1, 1)
|
|
96
|
-
self.grid.attach(self.sort_value, 1, 1, 1, 1)
|
|
97
|
-
self.grid.attach(props_label, 0, 2, 1, 1)
|
|
98
|
-
self.grid.attach(self.props_value, 1, 2, 1, 1)
|
|
99
|
-
self.grid.attach(self.strats_label, 0, 3, 1, 1)
|
|
100
|
-
self.grid.attach(self.strats_value, 1, 3, 1, 1)
|
|
101
|
-
|
|
102
|
-
self.grid.show_all()
|
|
103
|
-
|
|
104
|
-
def __set_current(self, widget):
|
|
105
|
-
"""Set the current widget in the banner"""
|
|
106
|
-
|
|
107
|
-
if self.get_child() != widget:
|
|
108
|
-
self.remove(self.get_child())
|
|
109
|
-
self.add(widget)
|
|
110
|
-
|
|
111
|
-
@staticmethod
|
|
112
|
-
def composeSignature(prefix, signature):
|
|
113
|
-
"""Create a link with the signature of an atomic proposition or strategy"""
|
|
114
|
-
|
|
115
|
-
# Displayed signature
|
|
116
|
-
composed = escape(signature[0]) if len(signature) == 1 else \
|
|
117
|
-
'{}({})'.format(escape(signature[0]), ', '.join(signature[1:]))
|
|
118
|
-
|
|
119
|
-
return f'<a href="#{prefix}:{escape(signature[0])}:{len(signature) - 1}">{composed}</a>'
|
|
120
|
-
|
|
121
|
-
def show_modinfo(self, info):
|
|
122
|
-
"""Process and presents the given module information"""
|
|
123
|
-
|
|
124
|
-
if not info['valid']:
|
|
125
|
-
self.label.set_label(f'<big>{MODULE_TYPE_NAMES[info["type"]]} {info["name"]}</big>'
|
|
126
|
-
'\n\nNot valid for model checking')
|
|
127
|
-
|
|
128
|
-
self.__set_current(self.label)
|
|
129
|
-
else:
|
|
130
|
-
if self.grid is None:
|
|
131
|
-
self.__makeGrid(info)
|
|
132
|
-
|
|
133
|
-
self.module_type.set_label(f'<big>{MODULE_TYPE_NAMES[info["type"]]}</big>')
|
|
134
|
-
self.module_name.set_label(f'<big>{info["name"]}</big>')
|
|
135
|
-
|
|
136
|
-
# State sort subtypes
|
|
137
|
-
self.sort_value.set_label(' '.join(info['state']))
|
|
138
|
-
|
|
139
|
-
# Atomic propositions
|
|
140
|
-
if info['prop']:
|
|
141
|
-
self.props_value.set_label(' '.join(map(lambda x: self.composeSignature('p', x), info['prop'])))
|
|
142
|
-
else:
|
|
143
|
-
self.props_value.set_label('none')
|
|
144
|
-
|
|
145
|
-
# Strategies
|
|
146
|
-
if info['strat']:
|
|
147
|
-
self.strats_value.set_label(' '.join(map(lambda x: self.composeSignature('s', x), info['strat'])))
|
|
148
|
-
self.strats_value.set_visible(True)
|
|
149
|
-
self.strats_label.set_visible(True)
|
|
150
|
-
else:
|
|
151
|
-
self.strats_value.set_visible(False)
|
|
152
|
-
self.strats_label.set_visible(False)
|
|
153
|
-
|
|
154
|
-
self.__set_current(self.grid)
|
|
155
|
-
|
|
156
|
-
def hint_clicked(self, banner, uri):
|
|
157
|
-
"""A link was clicked"""
|
|
158
|
-
|
|
159
|
-
if len(uri) == 0 or uri[0] != '#':
|
|
160
|
-
return True
|
|
161
|
-
|
|
162
|
-
# The URI contains the type of objects, its name and arity
|
|
163
|
-
kind, name, arity = uri[1:].split(':')
|
|
164
|
-
self.emit('hint-clicked', kind == 's', name, int(arity))
|
|
165
|
-
|
|
166
|
-
return True
|
|
167
|
-
|
|
168
|
-
@classmethod
|
|
169
|
-
def dict2jsobj(cls, value):
|
|
170
|
-
"""Convert a Python dictionary to a Javascript object literal"""
|
|
171
|
-
|
|
172
|
-
if isinstance(value, bool):
|
|
173
|
-
return 'true' if value else 'false'
|
|
174
|
-
|
|
175
|
-
elif value is None:
|
|
176
|
-
return 'null'
|
|
177
|
-
|
|
178
|
-
elif isinstance(value, dict):
|
|
179
|
-
entries = [f'{key}: {cls.dict2jsobj(value)}' for key, value in value.items()]
|
|
180
|
-
return '{' + ', '.join(entries) + '}'
|
|
181
|
-
|
|
182
|
-
elif isinstance(value, list):
|
|
183
|
-
return '[' + ', '.join(cls.dict2jsobj(elem) for elem in value) + ']'
|
|
184
|
-
|
|
185
|
-
else:
|
|
186
|
-
return repr(value)
|
|
187
|
-
|
|
188
|
-
def show_counterexample(self, result, background=None):
|
|
189
|
-
"""Show a counterexample in the banner"""
|
|
190
|
-
|
|
191
|
-
js_result = self.dict2jsobj(result)
|
|
192
|
-
|
|
193
|
-
if self.canvas is None:
|
|
194
|
-
self.canvas = WebKit2.WebView.new()
|
|
195
|
-
# Enable inspector (for debugging purposes)
|
|
196
|
-
self.canvas.get_settings().props.enable_developer_extras = True
|
|
197
|
-
# Resolve files with the umaudemc scheme to static resources
|
|
198
|
-
self.canvas.get_context().register_uri_scheme('umaudemc', self.get_webview_resource, None)
|
|
199
|
-
self.html_load_id = self.canvas.connect('load-changed', self.html_load, f'initCanvas({js_result})')
|
|
200
|
-
# Load the input data page
|
|
201
|
-
self.canvas.load_html(resources.get_resource_content('result.htm'))
|
|
202
|
-
self.canvas.show_all()
|
|
203
|
-
else:
|
|
204
|
-
self.canvas.run_javascript(f'initCanvas({js_result})', None, self.js_callback, None)
|
|
205
|
-
|
|
206
|
-
# Set the background color to that of the windows (if this is background)
|
|
207
|
-
if background is not None:
|
|
208
|
-
self.canvas.set_background_color(background)
|
|
8
|
+
gi.require_version('WebKit', '6.0')
|
|
9
|
+
from gi.repository import Gtk, Gio, GLib, WebKit
|
|
209
10
|
|
|
210
|
-
|
|
211
|
-
self.__set_current(self.canvas)
|
|
11
|
+
import multiprocessing
|
|
212
12
|
|
|
213
|
-
def set_editable(self, value):
|
|
214
|
-
"""Disable active widgets in the banner"""
|
|
215
13
|
|
|
216
|
-
|
|
217
|
-
|
|
14
|
+
def start_server(queue):
|
|
15
|
+
"""Start a server in a separate process"""
|
|
16
|
+
from . import webui
|
|
17
|
+
import http.server
|
|
218
18
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if path == 'smcview.css':
|
|
225
|
-
content = resources.get_resource_binary(path)
|
|
226
|
-
request.finish(Gio.MemoryInputStream.new_from_data(content), len(content), 'text/css')
|
|
227
|
-
|
|
228
|
-
elif path == 'smcgraph.js':
|
|
229
|
-
content = resources.get_resource_binary(path)
|
|
230
|
-
request.finish(Gio.MemoryInputStream.new_from_data(content), len(content), 'text/javascript')
|
|
231
|
-
|
|
232
|
-
else:
|
|
233
|
-
request.finish_error(GLib.Error.new_literal(GLib.FileError(GLib.FileError.NOENT)))
|
|
234
|
-
|
|
235
|
-
def js_callback(self, sender, result, data):
|
|
236
|
-
"""Callback for run_javascript"""
|
|
237
|
-
|
|
238
|
-
self.canvas.run_javascript_finish(result)
|
|
239
|
-
|
|
240
|
-
def html_load(self, canvas, event, call):
|
|
241
|
-
"""Callback when the page has been loaded"""
|
|
242
|
-
|
|
243
|
-
if event == WebKit2.LoadEvent.FINISHED:
|
|
244
|
-
canvas.run_javascript(call, None, self.js_callback, None)
|
|
245
|
-
canvas.disconnect(self.html_load_id)
|
|
19
|
+
httpd = http.server.ThreadingHTTPServer(('127.0.0.1', 0), webui.RequestHandler)
|
|
20
|
+
httpd.info = webui.ConnectionInfo()
|
|
21
|
+
httpd.info.path_handler.set_default(None)
|
|
22
|
+
queue.put(httpd.server_port)
|
|
23
|
+
httpd.serve_forever()
|
|
246
24
|
|
|
247
25
|
|
|
248
26
|
class ModelCheckerWindow(Gtk.ApplicationWindow):
|
|
249
27
|
"""The main window of the model checker GUI"""
|
|
250
28
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
'
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
self.
|
|
259
|
-
|
|
260
|
-
self.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
self.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
self.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
self.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
self.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
self.
|
|
292
|
-
|
|
293
|
-
self.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
self.opaques = Gtk.Entry()
|
|
297
|
-
self.add_labeled_widget(bottom2, '_Opaque strategies:', self.opaques)
|
|
298
|
-
|
|
299
|
-
# A button starts model checking
|
|
300
|
-
self.button = Gtk.Button(label='Model check', sensitive=False)
|
|
301
|
-
bottom2.pack_start(self.button, False, False, 0)
|
|
302
|
-
self.button.connect('clicked', self.model_check_clicked)
|
|
303
|
-
self.connect('mc-finished', self.model_check_finished)
|
|
304
|
-
|
|
305
|
-
# When an entry changes, the usability of the model-checking
|
|
306
|
-
# button is updated
|
|
307
|
-
self.initial.connect('changed', self.entry_changed)
|
|
308
|
-
self.formula.connect('changed', self.entry_changed)
|
|
309
|
-
|
|
310
|
-
# Widgets lists (for doing actions on all of them)
|
|
311
|
-
self.entry_widgets = [self.openbutton, self.module, self.initial,
|
|
312
|
-
self.formula, self.sexpr, self.opaques]
|
|
313
|
-
self.text_widgets = [self.initial, self.formula, self.sexpr, self.opaques]
|
|
314
|
-
|
|
315
|
-
# Whether widgets are editable (not blocked by other operation)
|
|
316
|
-
self.editable = True
|
|
317
|
-
# A connection to an interruptable Maude session
|
|
318
|
-
self.maude_session = mproc.MaudeRemote()
|
|
319
|
-
|
|
320
|
-
def signal_entry_error(self, entry, value):
|
|
321
|
-
"""Mark an entry widget with a warning in case of syntax error"""
|
|
322
|
-
|
|
323
|
-
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
|
|
324
|
-
'dialog-warning' if value else None)
|
|
325
|
-
|
|
326
|
-
def add_labeled_widget(self, container, label, widget, expand=True):
|
|
327
|
-
"""Add a widget together with a label to a container"""
|
|
328
|
-
|
|
329
|
-
label_widget = Gtk.Label.new_with_mnemonic(label)
|
|
330
|
-
label_widget.set_mnemonic_widget(widget)
|
|
331
|
-
container.pack_start(label_widget, False, False, 0)
|
|
332
|
-
container.pack_start(widget, expand, expand, 0)
|
|
29
|
+
def __init__(self, port=8000):
|
|
30
|
+
super().__init__(title='Unified Maude model-checking tool')
|
|
31
|
+
self.connect('close-request', self.destroyed)
|
|
32
|
+
self.set_default_size(800, 600)
|
|
33
|
+
|
|
34
|
+
# Use a WebView as the only content of the window
|
|
35
|
+
self.canvas = WebKit.WebView.new()
|
|
36
|
+
self.set_child(self.canvas)
|
|
37
|
+
# Enable inspector (for debugging purposes)
|
|
38
|
+
self.canvas.get_settings().props.enable_developer_extras = True
|
|
39
|
+
# Remove the header bar (there is already a window title)
|
|
40
|
+
user_manager = self.canvas.get_user_content_manager()
|
|
41
|
+
style = WebKit.UserStyleSheet('header { display: none; }',
|
|
42
|
+
WebKit.UserContentInjectedFrames.TOP_FRAME,
|
|
43
|
+
WebKit.UserStyleLevel.AUTHOR, None, None)
|
|
44
|
+
user_manager.add_style_sheet(style)
|
|
45
|
+
# Allow using the system open dialog
|
|
46
|
+
self.enable_open_dialog(user_manager)
|
|
47
|
+
|
|
48
|
+
# Load the input page
|
|
49
|
+
self.canvas.load_uri(f'http://127.0.0.1:{port}/')
|
|
50
|
+
|
|
51
|
+
def destroyed(self, window):
|
|
52
|
+
"""When the window is closed"""
|
|
53
|
+
self.get_application().server_process.terminate()
|
|
54
|
+
|
|
55
|
+
def enable_open_dialog(self, user_manager):
|
|
56
|
+
"""Enable access to the open dialog in the browser"""
|
|
57
|
+
|
|
58
|
+
# Register a script so that the native open dialog is used
|
|
59
|
+
# instead of the web-based file explorer
|
|
60
|
+
script = WebKit.UserScript('loadSource = loadSourceNative',
|
|
61
|
+
WebKit.UserContentInjectedFrames.TOP_FRAME,
|
|
62
|
+
WebKit.UserScriptInjectionTime.END, None, None)
|
|
63
|
+
user_manager.add_script(script)
|
|
64
|
+
|
|
65
|
+
# Create a file open dialog
|
|
66
|
+
self.dialog = self.make_open_dialog()
|
|
67
|
+
|
|
68
|
+
# Register the custom umaudemc scheme to open the file dialog
|
|
69
|
+
self.canvas.get_context().register_uri_scheme('umaudemc', self._umaudemc_request, None)
|
|
70
|
+
# Allow cross-origin requests to umaudemc (from localhost)
|
|
71
|
+
self.canvas.set_cors_allowlist(['umaudemc://*/*'])
|
|
72
|
+
# Disable catching (for the umaudemc request to be repeatable)
|
|
73
|
+
self.canvas.get_context().set_cache_model(WebKit.CacheModel.DOCUMENT_VIEWER)
|
|
333
74
|
|
|
334
75
|
def make_open_dialog(self):
|
|
335
76
|
"""Build the Maude file open dialog"""
|
|
336
77
|
|
|
337
|
-
opendialog = Gtk.
|
|
338
|
-
|
|
339
|
-
action=Gtk.FileChooserAction.OPEN)
|
|
340
|
-
|
|
341
|
-
opendialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
|
|
342
|
-
opendialog.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
|
78
|
+
opendialog = Gtk.FileDialog()
|
|
79
|
+
opendialog.props.title = 'Choose a Maude file'
|
|
343
80
|
|
|
344
81
|
filter_maude = Gtk.FileFilter()
|
|
345
82
|
filter_maude.set_name('Maude files (*.maude)')
|
|
346
83
|
filter_maude.add_pattern('*.maude')
|
|
347
|
-
opendialog.add_filter(filter_maude)
|
|
348
|
-
|
|
349
|
-
return opendialog
|
|
350
|
-
|
|
351
|
-
def destroyed(self, event, other):
|
|
352
|
-
"""Callback for the window destruction"""
|
|
353
|
-
|
|
354
|
-
self.maude_session.shutdown()
|
|
355
|
-
return False
|
|
356
|
-
|
|
357
|
-
def entry_changed(self, editable):
|
|
358
|
-
"""Callback when some entry has changed (to disable/enable the MC button)"""
|
|
359
|
-
|
|
360
|
-
complete = self.initial.get_text() and self.formula.get_text() \
|
|
361
|
-
and self.openbutton.get_filename() and self.module.get_active_id()
|
|
362
|
-
self.button.set_sensitive(complete)
|
|
363
|
-
|
|
364
|
-
def file_selected(self, data):
|
|
365
|
-
"""A file has been selected to be loaded"""
|
|
366
|
-
|
|
367
|
-
filename = self.openbutton.get_filename()
|
|
368
|
-
print('Loading', filename)
|
|
369
|
-
|
|
370
|
-
if not self.maude_session.load(filename):
|
|
371
|
-
self.notify_error(f'Error loading the file {filename}.')
|
|
372
|
-
else:
|
|
373
|
-
last_id = None
|
|
374
|
-
for name, mtype in self.maude_session.getModules():
|
|
375
|
-
self.module.append(name, f'{name} ({mtype})')
|
|
376
|
-
last_id = name
|
|
377
84
|
|
|
378
|
-
|
|
379
|
-
|
|
85
|
+
filters = Gio.ListStore()
|
|
86
|
+
filters.append(filter_maude)
|
|
87
|
+
opendialog.props.filters = filters
|
|
380
88
|
|
|
381
|
-
|
|
382
|
-
"""Reload the currently selected file (after an interrupted session)"""
|
|
383
|
-
|
|
384
|
-
filename = self.openbutton.get_filename()
|
|
385
|
-
print('Reloading', filename)
|
|
386
|
-
|
|
387
|
-
if not self.maude_session.load(filename):
|
|
388
|
-
self.notify_error(f'Error reloading the file {filename}.')
|
|
389
|
-
else:
|
|
390
|
-
current_id = self.module.get_active_id()
|
|
391
|
-
|
|
392
|
-
for name, mtype in self.maude_session.getModules():
|
|
393
|
-
self.module.append(name, f'{name} ({mtype})')
|
|
394
|
-
|
|
395
|
-
self.module.set_active_id(current_id)
|
|
396
|
-
|
|
397
|
-
def module_selected(self, data):
|
|
398
|
-
"""Callback for the selection of a module"""
|
|
399
|
-
|
|
400
|
-
self.module_info = self.maude_session.getModuleInfo(self.module.get_active_id())
|
|
401
|
-
self.show_modinfo()
|
|
402
|
-
|
|
403
|
-
def link_click(self, sender, is_strat, text, arity):
|
|
404
|
-
"""Callback for the activation of a banner link"""
|
|
405
|
-
|
|
406
|
-
# An entry file is completed with the clicked object
|
|
407
|
-
entry = self.sexpr if is_strat else self.formula
|
|
408
|
-
|
|
409
|
-
entry.delete_selection()
|
|
410
|
-
inserted_text = text + ('' if arity == 0 else '(')
|
|
411
|
-
entry.insert_text(inserted_text, entry.get_position())
|
|
412
|
-
entry.set_position(entry.get_position() + len(inserted_text))
|
|
413
|
-
|
|
414
|
-
def model_check_clicked(self, sender):
|
|
415
|
-
"""Callback for the model-checking/cancel button"""
|
|
416
|
-
|
|
417
|
-
# The button acts as a cancel when model checking
|
|
418
|
-
if not self.editable:
|
|
419
|
-
self.set_editable(True)
|
|
420
|
-
self.maude_session.shutdown()
|
|
421
|
-
self.maude_session = mproc.MaudeRemote()
|
|
422
|
-
self.file_reload()
|
|
423
|
-
return
|
|
424
|
-
|
|
425
|
-
# Pass the model checking data to Maude
|
|
426
|
-
data = {
|
|
427
|
-
'file' : self.openbutton.get_filename(),
|
|
428
|
-
'module' : self.module.get_active_id(),
|
|
429
|
-
'initial' : self.initial.get_text(),
|
|
430
|
-
'strat' : self.sexpr.get_text(),
|
|
431
|
-
'formula' : self.formula.get_text(),
|
|
432
|
-
'opaques' : self.opaques.get_text().split()
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
info = self.maude_session.modelCheck(data)
|
|
436
|
-
|
|
437
|
-
# Model checking has not been done yet, but the input data of the problem
|
|
438
|
-
# has been checked. info includes a reference to obtain the actual result.
|
|
89
|
+
return opendialog
|
|
439
90
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
self.signal_entry_error(self.initial, cause == 'term')
|
|
443
|
-
self.signal_entry_error(self.formula, cause == 'formula')
|
|
444
|
-
self.signal_entry_error(self.sexpr, cause == 'strat')
|
|
445
|
-
self.signal_entry_error(self.opaques, cause == 'opaque')
|
|
91
|
+
def _umaudemc_request(self, request, data):
|
|
92
|
+
path = request.get_uri()
|
|
446
93
|
|
|
447
|
-
|
|
448
|
-
|
|
94
|
+
if path == 'umaudemc://open/':
|
|
95
|
+
self.dialog.open(self, None, self._file_selected, request)
|
|
449
96
|
|
|
450
97
|
else:
|
|
451
|
-
|
|
452
|
-
for widget in self.text_widgets:
|
|
453
|
-
self.signal_entry_error(widget, False)
|
|
454
|
-
|
|
455
|
-
self.set_editable(False)
|
|
456
|
-
|
|
457
|
-
# Model checking is started in a separate thread not to block the
|
|
458
|
-
# interface (although most components are disabled)
|
|
459
|
-
self.thread = threading.Thread(target=self.model_check_action, args=(info['ref'], data, ))
|
|
460
|
-
self.thread.daemon = True
|
|
461
|
-
self.thread.start()
|
|
462
|
-
|
|
463
|
-
def notify_error(self, text):
|
|
464
|
-
"""Notify an error to the user (with a dialog)"""
|
|
465
|
-
|
|
466
|
-
dialog = Gtk.MessageDialog(text=text,
|
|
467
|
-
buttons=Gtk.ButtonsType.OK,
|
|
468
|
-
message_type=Gtk.MessageType.ERROR)
|
|
469
|
-
dialog.run()
|
|
470
|
-
dialog.destroy()
|
|
98
|
+
request.finish_error(GLib.Error.new_literal(1, "Not found", 404))
|
|
471
99
|
|
|
472
|
-
def
|
|
473
|
-
"""
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
self.banner.set_editable(value)
|
|
480
|
-
|
|
481
|
-
self.button.set_label('Model check' if value else 'Cancel')
|
|
482
|
-
|
|
483
|
-
def model_check_action(self, mcref, data):
|
|
484
|
-
"""Callback for the result of the model-checking task"""
|
|
485
|
-
|
|
486
|
-
# This is not run in the GTK thread
|
|
487
|
-
|
|
488
|
-
result = self.maude_session.get_result(mcref)
|
|
489
|
-
|
|
490
|
-
# Complement result with the initial data
|
|
491
|
-
result['formula'] = data['formula']
|
|
492
|
-
result['initial'] = data['initial']
|
|
493
|
-
result['strat'] = data.get('strat')
|
|
494
|
-
|
|
495
|
-
def emit_mcfinished():
|
|
496
|
-
# Instead of emitting the signal, its handler code can be done here
|
|
497
|
-
self.emit('mc-finished', result)
|
|
498
|
-
return False
|
|
499
|
-
|
|
500
|
-
GLib.idle_add(emit_mcfinished)
|
|
501
|
-
|
|
502
|
-
def model_check_finished(self, sender, result):
|
|
503
|
-
"""Callback in the GTK thread for the model checking finalization"""
|
|
504
|
-
|
|
505
|
-
# Join the model checker thread (just finished)
|
|
506
|
-
self.thread.join(2)
|
|
507
|
-
|
|
508
|
-
holds = result['result']
|
|
509
|
-
has_counterexample = result['hasCounterexample']
|
|
510
|
-
|
|
511
|
-
if not has_counterexample or holds:
|
|
512
|
-
# InfoBar will look more like the web version
|
|
513
|
-
dialog = Gtk.MessageDialog(text=('✔ The property holds.' if holds else '✗ The property does not hold.'),
|
|
514
|
-
buttons=Gtk.ButtonsType.OK)
|
|
515
|
-
dialog.run()
|
|
516
|
-
dialog.destroy()
|
|
517
|
-
|
|
518
|
-
bg = self.get_style_context().get_background_color(self.get_state())
|
|
519
|
-
if has_counterexample:
|
|
520
|
-
self.banner.show_counterexample(result, background=bg)
|
|
521
|
-
self.set_editable(True)
|
|
522
|
-
|
|
523
|
-
def show_modinfo(self):
|
|
524
|
-
"""Set the module information in the banner"""
|
|
525
|
-
|
|
526
|
-
self.banner.show_modinfo(self.module_info)
|
|
100
|
+
def _file_selected(self, dialog, result, request):
|
|
101
|
+
"""Callback on the selection of a file"""
|
|
102
|
+
try:
|
|
103
|
+
content = dialog.open_finish(result).get_path().encode('utf-8')
|
|
104
|
+
request.finish(Gio.MemoryInputStream.new_from_data(content), len(content), 'text/plain')
|
|
105
|
+
except GLib.Error:
|
|
106
|
+
request.finish_error(GLib.Error.new_literal(1, "Not found", 404))
|
|
527
107
|
|
|
528
108
|
|
|
529
109
|
def run_gtk():
|
|
@@ -531,12 +111,21 @@ def run_gtk():
|
|
|
531
111
|
app = Gtk.Application.new('es.ucm.maude.umaudemc', Gio.ApplicationFlags.FLAGS_NONE)
|
|
532
112
|
|
|
533
113
|
def window_init(app):
|
|
534
|
-
|
|
114
|
+
# Start the web server
|
|
115
|
+
queue = multiprocessing.Queue()
|
|
116
|
+
app.server_process = multiprocessing.Process(target=start_server, args=(queue,))
|
|
117
|
+
app.server_process.start()
|
|
118
|
+
|
|
119
|
+
# The random server listening port is obtained through the queue
|
|
120
|
+
port = queue.get()
|
|
121
|
+
window = ModelCheckerWindow(port)
|
|
122
|
+
|
|
535
123
|
app.add_window(window)
|
|
536
|
-
window.
|
|
124
|
+
window.show()
|
|
537
125
|
|
|
538
126
|
app.connect('activate', window_init)
|
|
539
127
|
return app.run()
|
|
540
128
|
|
|
129
|
+
|
|
541
130
|
if __name__ == "__main__":
|
|
542
131
|
run_gtk()
|