1 """Chart inspector, for viewing the chart during parsing.
2
3 A graphical interface to observe the state of a chart during parsing.
4 A chart inspector runs alongside the parser in a separate thread
5 and updates its representation of the chart from the actual one being
6 manipulated by the parser whenever the user asks for an update.
7
8 The inspector shows the number of signs in each cell of the chart
9 and allows inspection of what those signs are and even the derivation
10 trace (if available) for each one.
11
12 Note that this module uses PyGtk, which is not required for most of
13 the project. You should not import anything from this module at the
14 top level, but only when you need it. That way, if PyGtk is not
15 installed, an error will only occur when you try to use it.
16
17 You can also read a pickled chart in from a file and use the chart
18 inspector to examine it.
19
20 """
21 """
22 ============================== License ========================================
23 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
24
25 This file is part of The Jazz Parser.
26
27 The Jazz Parser is free software: you can redistribute it and/or modify
28 it under the terms of the GNU General Public License as published by
29 the Free Software Foundation, either version 3 of the License, or
30 (at your option) any later version.
31
32 The Jazz Parser is distributed in the hope that it will be useful,
33 but WITHOUT ANY WARRANTY; without even the implied warranty of
34 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 GNU General Public License for more details.
36
37 You should have received a copy of the GNU General Public License
38 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
39
40 ============================ End license ======================================
41
42 """
43 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
44
45
46
47 import pygtk
48 pygtk.require('2.0')
49
50 import gtk, pango, sys, gobject
51 import copy
52 from threading import Thread
53
54 LAST_UPDATE_COLOR = "#f0d0d0"
55
57 """
58 A small window that gives more information about a particular cell
59 in the chart. Displays a list of the signs in that cell.
60 This gets popped up when you click on a cell.
61
62 """
63 - def __init__(self, signs, from_node, to_node, parent=None, *args, **kwargs):
64 """
65 Creates a new mini inspector window to show information about a
66 cell in the chart.
67
68 @type signs: list of signs
69 @param signs: the signs contained in the cell
70 @type from_node: int
71 @param from_node: the start node of the chart edge represented
72 by this cell.
73 @type to_node: int
74 @param to_node: the end node of the edge.
75 @type parent: gtk.Window
76 @param parent: the parent window of this small dialog. If
77 omitted, the window will be considered top-level.
78
79 """
80 super(CellInspectorWindow, self).__init__(*args, **kwargs)
81 self.signs = signs
82 self.from_node = from_node
83 self.to_node = to_node
84
85
86
87 self.set_modal(True)
88 self.set_default_size(400, 800)
89 self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
90 self.set_title("Cell (%d,%d) of chart - The Jazz Parser" % (from_node, to_node))
91
92 vbox = gtk.VBox()
93 self.add(vbox)
94
95 if parent is not None:
96
97 self.set_transient_for(parent)
98 self.set_destroy_with_parent(True)
99
100
101
102 liststore = self._create_column_data()
103 self.liststore = liststore
104
105 treeview = gtk.TreeView(liststore)
106 self.treeview = treeview
107
108 self._create_columns()
109
110 treeview_scroll = gtk.ScrolledWindow()
111 treeview_scroll.add_with_viewport(treeview)
112
113 treeview.connect("button_press_event", self.popup_item_menu)
114
115
116
117 menu = gtk.Menu()
118
119 deriv_item = gtk.MenuItem("Show derivation trace")
120 deriv_item.show()
121 deriv_item.connect("activate", self.show_derivation_window)
122 menu.append(deriv_item)
123
124 vbox.add(treeview_scroll)
125
126
127 self.context_menu = menu
128
129 self.show_all()
130
132 """
133 Creates the data for the TreeView.
134 This allows the display to be overridden by subclasses.
135
136 """
137 liststore = gtk.ListStore(gobject.TYPE_STRING)
138
139 for sign in self.signs:
140 liststore.append(["%s" % sign])
141 return liststore
142
144 """
145 Instantiates the TreeView's columns and adds them to the
146 TreeView self.treeview.
147 This allows the display to be overridden by subclasses.
148
149 """
150
151 sign_column = gtk.TreeViewColumn("Signs on edge (%d,%d)" % (self.from_node, self.to_node))
152 self.treeview.append_column(sign_column)
153
154 sign_renderer = gtk.CellRendererText()
155 sign_renderer.set_property('family', 'monospace')
156 sign_column.pack_start(sign_renderer, True)
157 sign_column.add_attribute(sign_renderer, "text", 0)
158
159 self.treeview.set_search_column(0)
160
162 """
163 Handler for right-click event on items in the list.
164
165 """
166 if event.button == 3:
167
168 x, y = int(event.x), int(event.y)
169 time = event.time
170
171 pthinfo = treeview.get_path_at_pos(x, y)
172
173 if pthinfo is not None:
174 path, col, cellx, celly = pthinfo
175 treeview.grab_focus()
176 treeview.set_cursor(path, col, 0)
177
178 self.context_menu.popup(None, None, None, event.button, time)
179 return True
180 return False
181
183 """
184 Handler for selecting the derivation window menu item.
185
186 """
187
188 sign = self.get_selected_sign()
189 if hasattr(sign, 'derivation_trace') and sign.derivation_trace is not None:
190 dt_window = DerivationTraceWindow(sign)
191 else:
192
193 md = gtk.MessageDialog(self, buttons=gtk.BUTTONS_OK, \
194 message_format="The sign has no derivation trace.\n\n"\
195 "This may be because no derivation "\
196 "traces are being stored during parsing. Try the "\
197 "-d option.")
198 md.run()
199 md.destroy()
200
202 """
203 @return: the sign represented by the value currently selected
204 in the list, or None is none is selected.
205
206 """
207 index,column = self.treeview.get_cursor()
208 if index is None:
209
210 return None
211 else:
212
213
214 return self.signs[index[0]]
215
217 """
218 A window that displays a summary of the chart and allows inspection
219 of it in greater detail.
220
221 """
222 """Class to create the cell inspector. May be overridden by subclasses."""
223 CELL_INSPECTOR_IMPL = CellInspectorWindow
224
225 - def __init__(self, chart=None, input_strs=None, filename=None, *args, **kwargs):
226 """
227 Creates a new ChartInspectorWindow.
228 Either chart or filename must be given. If filename is given,
229 the chart will be loaded from a pickled representation in the
230 named file and this will be reloaded when the chart is updated.
231
232 @type chart: Chart
233 @param chart: the chart to inspect
234 @type input_strs: list of strings
235 @param input_strs: a string representation of the input, for
236 display.
237 @type filename: string
238 @param filename: the location of a file to load the chart from.
239
240 """
241 self.filename = filename
242 if filename is not None:
243 self._load_chart_file()
244 else:
245 self.chart = chart
246 self._chart_matrix = None
247
248 self.highlighted = [[False for i in range(len(row))] for row in self.chart._table]
249 self.update_chart()
250
251
252 super(ChartInspectorWindow, self).__init__(gtk.WINDOW_TOPLEVEL, *args, **kwargs)
253
254
255 self.set_border_width(5)
256 self.set_title("Chart inspector - The Jazz Parser")
257
258 vbox = gtk.VBox(spacing=5)
259 self.add(vbox)
260
261 if input_strs is not None:
262 input_label = gtk.Label()
263 input_label.set_markup("<span font-family=\"monospace\">Input: %s</span>" % " ".join(input_strs))
264
265 input_scroll = gtk.ScrolledWindow()
266 input_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
267 input_scroll.add_with_viewport(input_label)
268 vbox.pack_start(input_scroll, expand=False)
269
270
271 self.scroll_container = gtk.ScrolledWindow()
272 self.scroll_container.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
273 vbox.pack_start(self.scroll_container, expand=True)
274
275 chart_size = self.chart.size
276 self.table = gtk.Table(rows=chart_size+1, columns=chart_size+1, homogeneous=True)
277 self.table.set_col_spacings(5)
278 self.table.set_row_spacings(5)
279 self.scroll_container.add_with_viewport(self.table)
280
281
282
283 for i in range(chart_size):
284 label = gtk.Label()
285 label.set_markup("<b>%d</b>" % i)
286 self.table.attach(label, 0, 1, i+1, i+2, xoptions=0, yoptions=0)
287 label.show()
288
289 for i in range(chart_size):
290 label = gtk.Label()
291 label.set_markup("<b>%d</b>" % (i+1))
292 self.table.attach(label, i+1, i+2, 0, 1, xoptions=0, yoptions=0)
293 label.show()
294
295 def _get_inspect_cell(x, y):
296
297 def _inspect_cell(widget, event):
298 cell_window = self.CELL_INSPECTOR_IMPL(self._chart_matrix[x][y-x], x, y+1)
299 cell_window.show()
300 return _inspect_cell
301
302
303 self._label_matrix = []
304 for i in range(chart_size):
305 lab_row = []
306 for j in range(chart_size):
307
308 label = gtk.Label()
309
310 box = gtk.EventBox()
311 box.add(label)
312
313 self.table.attach(box, j+1, j+2, i+1, i+2, xoptions=0, yoptions=0)
314 label.show()
315 box.show()
316 box.set_events(gtk.gdk.BUTTON_PRESS_MASK)
317 if i <= j:
318
319
320 label.set_tooltip_text("(%d,%d)" % (i,j+1))
321
322 box.connect('button_press_event', _get_inspect_cell(i, j))
323
324 lab_row.append(label)
325 self._label_matrix.append(lab_row)
326
327 self.length = chart_size
328
329
330 self.set_default_size(600,600)
331
332
333 def _key_pressed(widget, event):
334 keyname = gtk.gdk.keyval_name(event.keyval)
335 if keyname == "F5":
336
337 self.draw_chart()
338 return True
339 else:
340
341 return False
342 self.connect('key_press_event', _key_pressed)
343
344
345 self.draw_chart(update=False)
346
347
348 self.connect("destroy", self.kill)
349
350
351 self.show_all()
352
357
358 - def __set_cell_contents(self, row, col, value):
359 """Put a string in the cell with the given coordinate."""
360 if row >= self.length or col >= self.length:
361
362 raise IndexError, "tried to put something in (%d,%d) of a "\
363 "chart of size %d" % (row,col,self.length)
364 value = str(value)
365
366 if value == "0":
367
368 value = '<span foreground="#999">%s</span>' % value
369
370 self._label_matrix[row][col].set_markup(value)
371
373 """Sets the text of a cell to a particular colour."""
374 self._label_matrix[row][col].modify_fg(gtk.STATE_NORMAL, color)
375
377 """
378 Recopies the information needed from the chart object into the
379 local cache. If the chart was loaded from a file, reloads the
380 file first.
381
382 """
383 if self.filename is not None:
384
385 self._load_chart_file()
386
387
388
389
390 self._old_chart_matrix = self._chart_matrix
391 while True:
392 try:
393
394
395
396 self._chart_matrix = [[copy.copy(hash_set.values()) for hash_set in row] for row in self.chart._table]
397 except RuntimeError:
398
399 print "WARNING: thread error while copying chart"
400 continue
401 else:
402 break
403
404
405 if self._old_chart_matrix is not None:
406 for i in range(len(self._chart_matrix)):
407 for j in range(len(self._chart_matrix[i])):
408 self.highlighted[i][j] = \
409 (self._old_chart_matrix[i][j] != self._chart_matrix[i][j])
410
412 """
413 Refresh the values in the displayed table representing the chart.
414 By default this will update the chart representation. If you
415 just want to draw and not update first, set update=False.
416
417 """
418 if update:
419 self.update_chart()
420
421 highlighted = gtk.gdk.color_parse("red")
422 unhighlighted = gtk.gdk.color_parse("black")
423
424
425 for from_node in range(self.length):
426 for to_node in range(from_node+1, self.length+1):
427 new_value = self._chart_matrix[from_node][to_node-from_node-1]
428
429 if self._old_chart_matrix is None or \
430 new_value != self._old_chart_matrix[from_node][to_node-from_node-1]:
431 self.__set_cell_contents(from_node, to_node-1, \
432 "%d" % len(new_value) )
433
434 if self.highlighted[from_node][to_node-from_node-1]:
435 self.__set_cell_color(from_node, to_node-1, highlighted)
436 else:
437 self.__set_cell_color(from_node, to_node-1, unhighlighted)
438
439 - def kill(self, obj=None):
440 """
441 Stops the thread in which the inspector is running.
442
443 """
444 gtk.main_quit()
445
447 """
448 Simple window to spew out a derivation trace for a sign in the
449 chart. Just gives a space to display the plain text representation -
450 no need to do anything more fancy.
451
452 """
453 - def __init__(self, sign, *args, **kwargs):
454 super(DerivationTraceWindow, self).__init__(*args, **kwargs)
455
456
457 self.set_modal(True)
458 self.set_default_size(600, 600)
459 self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
460 self.set_title("Derivation trace - The Jazz Parser")
461
462
463 scroller = gtk.ScrolledWindow()
464 scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
465 self.add(scroller)
466 text_view = gtk.TextView()
467 text_view.set_editable(False)
468 text_view.set_cursor_visible(False)
469 scroller.add_with_viewport(text_view)
470
471 font = pango.FontDescription("monospace")
472 text_view.modify_font(font)
473
474
475 text_view.get_buffer().set_text(str(sign.derivation_trace))
476
477 self.show_all()
478
480 """
481 A thread to display a window at the same time as parsing is going
482 on. The window contains information about the chart. This is taken
483 from a copy of the chart, so it will only update when the user
484 requests an update.
485
486 The thread will end when the window is closed.
487
488 """
489 """The class to use to create the inspector window. May be overridden by subclasses."""
490 INSPECTOR_IMPL = ChartInspectorWindow
491
492 - def __init__(self, chart, input_strs=None, *args, **kwargs):
500
502
503 gtk.gdk.threads_init()
504 gobject.threads_init()
505
506 gtk.main()
507
509 """
510 Load a pickled chart from a file and display the chart inspector
511 to examine it.
512
513 @type filename: string
514 @param filename: the filename of the file to load
515 @type inspector_cls: class
516 @param inspector_cls: the class of the inspector window to load the
517 chart in. By default, uses the CKY inspector, but you may want
518 to use subclasses.
519
520 """
521
522 if inspector_cls is None:
523 inspector = ChartInspectorWindow(filename=filename)
524 else:
525 inspector = inspector_cls(filename=filename)
526 gtk.main()
527