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/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', '3.0')
6
+ gi.require_version('Gtk', '4.0')
10
7
  gi.require_version('Gio', '2.0')
11
- gi.require_version('WebKit2', '4.0')
12
- from gi.repository import Gtk, Gio, GLib, GObject, WebKit2
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
- # Replace the current widget with this
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
- if self.grid is not None:
217
- self.grid.set_sensitive(value)
14
+ def start_server(queue):
15
+ """Start a server in a separate process"""
16
+ from . import webui
17
+ import http.server
218
18
 
219
- def get_webview_resource(self, request, data):
220
- """Get a resource from the package data"""
221
-
222
- path = request.get_path()
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
- __gsignals__ = {
252
- # Model checking has finished
253
- 'mc-finished': (GObject.SignalFlags.RUN_FIRST, None, (object, ))
254
- }
255
-
256
- def __init__(self):
257
- super().__init__(title='Maude strategy-aware model checker')
258
- self.connect('delete-event', self.destroyed)
259
- self.set_border_width(6)
260
- self.resize(800, 600)
261
-
262
- # The window consists of a header (top), a banner (self.vbox). and two
263
- # bottom containers (bottom1 and bottom2) vertically arranged
264
-
265
- self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
266
- top = Gtk.Box(spacing=6)
267
- self.banner = Banner()
268
- self.banner.connect('hint-clicked', self.link_click)
269
- bottom1 = Gtk.Box(spacing=6)
270
- bottom2 = Gtk.Box(spacing=6)
271
- self.vbox.pack_start(top, False, False, 0)
272
- self.vbox.pack_start(self.banner, True, True, 0)
273
- self.vbox.pack_start(bottom1, False, False, 0)
274
- self.vbox.pack_start(bottom2, False, False, 0)
275
- self.add(self.vbox)
276
-
277
- # A header allow choosing and show the current file and module
278
- self.openbutton = Gtk.FileChooserButton.new_with_dialog(self.make_open_dialog())
279
- self.add_labeled_widget(top, 'Maude file:', self.openbutton)
280
- self.openbutton.connect('file-set', self.file_selected)
281
-
282
- self.module = Gtk.ComboBoxText()
283
- self.add_labeled_widget(top, '_Module:', self.module)
284
- self.module.connect('changed', self.module_selected)
285
-
286
- # A footer includes entries to input the problem data
287
- self.initial = Gtk.Entry()
288
- self.add_labeled_widget(bottom1, '_Initial term:', self.initial)
289
-
290
- self.formula = Gtk.Entry()
291
- self.add_labeled_widget(bottom1, '_Formula:', self.formula)
292
-
293
- self.sexpr = Gtk.Entry()
294
- self.add_labeled_widget(bottom2, '_Strategy:', self.sexpr)
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.FileChooserDialog(title='Choose a Maude file',
338
- parent=self,
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
- self.module.set_active_id(last_id)
379
- self.entry_changed(self.openbutton)
85
+ filters = Gio.ListStore()
86
+ filters.append(filter_maude)
87
+ opendialog.props.filters = filters
380
88
 
381
- def file_reload(self):
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
- if not info['ok']:
441
- cause = info['cause']
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
- if cause == 'nobackend':
448
- self.notify_error(f'No installed backend for the {info["logic"]} logic')
94
+ if path == 'umaudemc://open/':
95
+ self.dialog.open(self, None, self._file_selected, request)
449
96
 
450
97
  else:
451
- # Hide previous error marks
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 set_editable(self, value):
473
- """Enable or disable the active components of the window"""
474
-
475
- self.editable = value
476
-
477
- for widget in self.entry_widgets:
478
- widget.set_sensitive(value)
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
- window = ModelCheckerWindow()
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.show_all()
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()