Package jazzparser :: Package parsers :: Package cky :: Module inspector
[hide private]
[frames] | no frames]

Source Code for Module jazzparser.parsers.cky.inspector

  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  # Check we've got the right version of PyGtk 
 46  # This check is made when this module first gets loaded 
 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   
56 -class CellInspectorWindow(gtk.Window):
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 ####### Window furniture 86 # Set up the appearance of the windw 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 # A box to put all the widgets in 92 vbox = gtk.VBox() 93 self.add(vbox) 94 95 if parent is not None: 96 # Make this like a dialog window subject to a parent window 97 self.set_transient_for(parent) 98 self.set_destroy_with_parent(True) 99 100 ######## The list 101 # Set up the list to contain the signs 102 liststore = self._create_column_data() 103 self.liststore = liststore 104 105 treeview = gtk.TreeView(liststore) 106 self.treeview = treeview 107 # Create the column definitions 108 self._create_columns() 109 # Put the treeview in scrollbars 110 treeview_scroll = gtk.ScrolledWindow() 111 treeview_scroll.add_with_viewport(treeview) 112 # Connect a click event to the list 113 treeview.connect("button_press_event", self.popup_item_menu) 114 115 ######## Context menu 116 # Create a context menu to appear when right-clicking on an item 117 menu = gtk.Menu() 118 # Add any menu items 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 # Keep references to things we might need 127 self.context_menu = menu 128 129 self.show_all()
130
131 - def _create_column_data(self):
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 # Add all the signs to the store 139 for sign in self.signs: 140 liststore.append(["%s" % sign]) 141 return liststore
142
143 - def _create_columns(self):
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 # Add a column to the treeview to display the signs 151 sign_column = gtk.TreeViewColumn("Signs on edge (%d,%d)" % (self.from_node, self.to_node)) 152 self.treeview.append_column(sign_column) 153 # Render the signs as text 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 # Make it possible to search the signs 159 self.treeview.set_search_column(0)
160
161 - def popup_item_menu(self, treeview, event):
162 """ 163 Handler for right-click event on items in the list. 164 165 """ 166 if event.button == 3: 167 # Get the coordinates of the click 168 x, y = int(event.x), int(event.y) 169 time = event.time 170 # Work out what was clicked 171 pthinfo = treeview.get_path_at_pos(x, y) 172 # Only do something if an item was clicked on 173 if pthinfo is not None: 174 path, col, cellx, celly = pthinfo 175 treeview.grab_focus() 176 treeview.set_cursor(path, col, 0) 177 # Show the context menu 178 self.context_menu.popup(None, None, None, event.button, time) 179 return True 180 return False
181
182 - def show_derivation_window(self, widget):
183 """ 184 Handler for selecting the derivation window menu item. 185 186 """ 187 # Get the selected sign from the list 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 # No DT available 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
201 - def get_selected_sign(self):
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 # Nothing selected 210 return None 211 else: 212 # index is a tuple, because TreeView can display trees 213 # We only ever have one item, because we only display lists 214 return self.signs[index[0]]
215
216 -class ChartInspectorWindow(gtk.Window):
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 # Matrix to note whether a cell's highlighted 248 self.highlighted = [[False for i in range(len(row))] for row in self.chart._table] 249 self.update_chart() 250 251 # Init the window 252 super(ChartInspectorWindow, self).__init__(gtk.WINDOW_TOPLEVEL, *args, **kwargs) 253 254 # Setup the displayed window 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 # Display the input string if one was given 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 # Give it a horizontal scrollbar 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 #### The table itself 270 # Add scrollbars 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 # Put a table in the window to display the chart summary in 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 # Add labels to the axes of the table 282 # "From" down the side 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 # "To" along the top 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 # This creates a closure for x and y to use in the click callback 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 # Put a label in every cell whose text we can set 303 self._label_matrix = [] 304 for i in range(chart_size): 305 lab_row = [] 306 for j in range(chart_size): 307 # Create an empty label in the table cell 308 label = gtk.Label() 309 # We need to put it in an EventBox so it can receive click events 310 box = gtk.EventBox() 311 box.add(label) 312 # Put the label in the tabular layout 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 # Only do these things for cells that correspond to actual edges 319 # Create a tooltip for this label so we can easily work out what it is 320 label.set_tooltip_text("(%d,%d)" % (i,j+1)) 321 # Bind the click event to a callback to show the cell inspector 322 box.connect('button_press_event', _get_inspect_cell(i, j)) 323 # Keep a ref to it in the matrix 324 lab_row.append(label) 325 self._label_matrix.append(lab_row) 326 327 self.length = chart_size 328 329 # Make the window nice and big 330 self.set_default_size(600,600) 331 332 # Bind key presses 333 def _key_pressed(widget, event): 334 keyname = gtk.gdk.keyval_name(event.keyval) 335 if keyname == "F5": 336 # Update and redraw the chart 337 self.draw_chart() 338 return True 339 else: 340 # Fall through to any other handlers 341 return False 342 self.connect('key_press_event', _key_pressed) 343 344 # Draw the chart as it currently stands 345 self.draw_chart(update=False) 346 347 # Stop PyGtk when the window is closed 348 self.connect("destroy", self.kill) 349 350 # Show everything 351 self.show_all() 352
353 - def _load_chart_file(self):
354 from .chart import load_chart 355 # Load the pickled chart object 356 self.chart = load_chart(self.filename)
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 # Outside the bounds of the table 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 # Apply any formatting we want 366 if value == "0": 367 # Make 0s grey 368 value = '<span foreground="#999">%s</span>' % value 369 # Set the text of the label in that cell 370 self._label_matrix[row][col].set_markup(value)
371
372 - def __set_cell_color(self, row, col, color):
373 """Sets the text of a cell to a particular colour.""" 374 self._label_matrix[row][col].modify_fg(gtk.STATE_NORMAL, color)
375
376 - def update_chart(self):
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 # Reload the chart from the file 385 self._load_chart_file() 386 # This is hacky 387 # If the chart's being updated and this falls flat, just try again. 388 # A better solution would be to lock the chart. 389 # This rarely happens, so maybe not worth locking 390 self._old_chart_matrix = self._chart_matrix 391 while True: 392 try: 393 # Refresh the copy of the chart that we're using to draw 394 # Take a copy of the state of the chart 395 # No need to copy the signs themselves as they won't change 396 self._chart_matrix = [[copy.copy(hash_set.values()) for hash_set in row] for row in self.chart._table] 397 except RuntimeError: 398 # We probably caught the chart at a bad time: try again 399 print "WARNING: thread error while copying chart" 400 continue 401 else: 402 break 403 404 # Check which values have changed and flag them as highlighted 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
411 - def draw_chart(self, update=True):
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 # Colours to use below 421 highlighted = gtk.gdk.color_parse("red") 422 unhighlighted = gtk.gdk.color_parse("black") 423 424 # Put the number of signs in each cell 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 # Only update the label if the value's changed (it's time-consuming) 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 # Update the colour depending on whether it's highlighted 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
446 -class DerivationTraceWindow(gtk.Window):
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 # Set up the appearance of the windw 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 # Put a text area in the window in scrollbars 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 # Set the font in which the text is displayed 471 font = pango.FontDescription("monospace") 472 text_view.modify_font(font) 473 474 # Put the derivation trace in the buffer 475 text_view.get_buffer().set_text(str(sign.derivation_trace)) 476 477 self.show_all()
478
479 -class ChartInspectorThread(Thread):
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):
493 super(ChartInspectorThread, self).__init__(*args, **kwargs) 494 self.chart = chart 495 # Create a window 496 # This will be displayed immediately 497 # Note that it gets displayed from the main thread, but future 498 # interactions take place in the new thread 499 self.window = self.INSPECTOR_IMPL(self.chart, input_strs=input_strs)
500
501 - def run(self):
502 # Prepare Gtk to run neatly in a thread and not hold the global lock 503 gtk.gdk.threads_init() 504 gobject.threads_init() 505 # Go to the main loop, which will only end if the window's closed 506 gtk.main()
507
508 -def inspect_chart_file(filename, inspector_cls=None):
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 # Start up the chart inspector 522 if inspector_cls is None: 523 inspector = ChartInspectorWindow(filename=filename) 524 else: 525 inspector = inspector_cls(filename=filename) 526 gtk.main()
527