Package nltk :: Package app :: Module chartparser_app
[hide private]
[frames] | no frames]

Source Code for Module nltk.app.chartparser_app

   1  # Natural Language Toolkit: Chart Parser Application 
   2  # 
   3  # Copyright (C) 2001-2011 NLTK Project 
   4  # Author: Edward Loper <edloper@gradient.cis.upenn.edu> 
   5  #         Jean Mark Gawron <gawron@mail.sdsu.edu> 
   6  #         Steven Bird <sb@csse.unimelb.edu.au> 
   7  # URL: <http://www.nltk.org/> 
   8  # For license information, see LICENSE.TXT 
   9  # 
  10  # $Id: chartparser_app.py 8730 2011-03-08 04:49:46Z StevenBird1 $ 
  11   
  12  """ 
  13  A graphical tool for exploring chart parsing. 
  14   
  15  Chart parsing is a flexible parsing algorithm that uses a data 
  16  structure called a "chart" to record hypotheses about syntactic 
  17  constituents.  Each hypothesis is represented by a single "edge" on 
  18  the chart.  A set of "chart rules" determine when new edges can be 
  19  added to the chart.  This set of rules controls the overall behavior 
  20  of the parser (e.g., whether it parses top-down or bottom-up). 
  21   
  22  The chart parsing tool demonstrates the process of parsing a single 
  23  sentence, with a given grammar and lexicon.  Its display is divided 
  24  into three sections: the bottom section displays the chart; the middle 
  25  section displays the sentence; and the top section displays the 
  26  partial syntax tree corresponding to the selected edge.  Buttons along 
  27  the bottom of the window are used to control the execution of the 
  28  algorithm. 
  29   
  30  The chart parsing tool allows for flexible control of the parsing 
  31  algorithm.  At each step of the algorithm, you can select which rule 
  32  or strategy you wish to apply.  This allows you to experiment with 
  33  mixing different strategies (e.g., top-down and bottom-up).  You can 
  34  exercise fine-grained control over the algorithm by selecting which 
  35  edge you wish to apply a rule to. 
  36  """ 
  37   
  38  # At some point, we should rewrite this tool to use the new canvas 
  39  # widget system. 
  40   
  41  import pickle 
  42  from tkFileDialog import asksaveasfilename, askopenfilename 
  43  import Tkinter 
  44  import math 
  45  import string 
  46  import os.path 
  47   
  48  from nltk.parse.chart import * 
  49  from nltk.tree import Tree 
  50  from nltk.grammar import Nonterminal, parse_cfg 
  51  from nltk.util import in_idle 
  52  from nltk.draw.util import * 
  53  from nltk.draw.cfg import CFGEditor 
  54  from nltk.draw.tree import tree_to_treesegment, TreeSegmentWidget 
  55   
  56  # Known bug: ChartView doesn't handle edges generated by epsilon 
  57  # productions (e.g., [Production: PP -> ]) very well. 
  58   
  59  ####################################################################### 
  60  # Edge List 
  61  ####################################################################### 
  62   
63 -class EdgeList(ColorizedList):
64 ARROW = SymbolWidget.SYMBOLS['rightarrow'] 65
66 - def _init_colortags(self, textwidget, options):
67 textwidget.tag_config('terminal', foreground='#006000') 68 textwidget.tag_config('arrow', font='symbol', underline='0') 69 textwidget.tag_config('dot', foreground = '#000000') 70 textwidget.tag_config('nonterminal', foreground='blue', 71 font=('helvetica', -12, 'bold'))
72
73 - def _item_repr(self, item):
74 contents = [] 75 contents.append(('%s\t' % item.lhs(), 'nonterminal')) 76 contents.append((self.ARROW, 'arrow')) 77 for i, elt in enumerate(item.rhs()): 78 if i == item.dot(): 79 contents.append((' *', 'dot')) 80 if isinstance(elt, Nonterminal): 81 contents.append((' %s' % elt.symbol(), 'nonterminal')) 82 else: 83 contents.append((' %r' % elt, 'terminal')) 84 if item.is_complete(): 85 contents.append((' *', 'dot')) 86 return contents
87 88 ####################################################################### 89 # Chart Matrix View 90 ####################################################################### 91
92 -class ChartMatrixView(object):
93 """ 94 A view of a chart that displays the contents of the corresponding matrix. 95 """
96 - def __init__(self, parent, chart, toplevel=True, title='Chart Matrix', 97 show_numedges=False):
98 self._chart = chart 99 self._cells = [] 100 self._marks = [] 101 102 self._selected_cell = None 103 104 if toplevel: 105 self._root = Tkinter.Toplevel(parent) 106 self._root.title(title) 107 self._root.bind('<Control-q>', self.destroy) 108 self._init_quit(self._root) 109 else: 110 self._root = Tkinter.Frame(parent) 111 112 self._init_matrix(self._root) 113 self._init_list(self._root) 114 if show_numedges: 115 self._init_numedges(self._root) 116 else: 117 self._numedges_label = None 118 119 self._callbacks = {} 120 121 self._num_edges = 0 122 123 self.draw()
124
125 - def _init_quit(self, root):
126 quit = Tkinter.Button(root, text='Quit', command=self.destroy) 127 quit.pack(side='bottom', expand=0, fill='none')
128
129 - def _init_matrix(self, root):
130 cframe = Tkinter.Frame(root, border=2, relief='sunken') 131 cframe.pack(expand=0, fill='none', padx=1, pady=3, side='top') 132 self._canvas = Tkinter.Canvas(cframe, width=200, height=200, 133 background='white') 134 self._canvas.pack(expand=0, fill='none')
135
136 - def _init_numedges(self, root):
137 self._numedges_label = Tkinter.Label(root, text='0 edges') 138 self._numedges_label.pack(expand=0, fill='none', side='top')
139
140 - def _init_list(self, root):
141 self._list = EdgeList(root, [], width=20, height=5) 142 self._list.pack(side='top', expand=1, fill='both', pady=3) 143 def cb(edge, self=self): self._fire_callbacks('select', edge) 144 self._list.add_callback('select', cb) 145 self._list.focus()
146
147 - def destroy(self, *e):
148 if self._root is None: return 149 try: self._root.destroy() 150 except: pass 151 self._root = None
152
153 - def set_chart(self, chart):
154 if chart is not self._chart: 155 self._chart = chart 156 self._num_edges = 0 157 self.draw()
158
159 - def update(self):
160 if self._root is None: return 161 162 # Count the edges in each cell 163 N = len(self._cells) 164 cell_edges = [[0 for i in range(N)] for j in range(N)] 165 for edge in self._chart: 166 cell_edges[edge.start()][edge.end()] += 1 167 168 # Color the cells correspondingly. 169 for i in range(N): 170 for j in range(i, N): 171 if cell_edges[i][j] == 0: 172 color = 'gray20' 173 else: 174 color = ('#00%02x%02x' % 175 (min(255, 50+128*cell_edges[i][j]/10), 176 max(0, 128-128*cell_edges[i][j]/10))) 177 cell_tag = self._cells[i][j] 178 self._canvas.itemconfig(cell_tag, fill=color) 179 if (i,j) == self._selected_cell: 180 self._canvas.itemconfig(cell_tag, outline='#00ffff', 181 width=3) 182 self._canvas.tag_raise(cell_tag) 183 else: 184 self._canvas.itemconfig(cell_tag, outline='black', 185 width=1) 186 187 # Update the edge list. 188 edges = list(self._chart.select(span=self._selected_cell)) 189 self._list.set(edges) 190 191 # Update our edge count. 192 self._num_edges = self._chart.num_edges() 193 if self._numedges_label is not None: 194 self._numedges_label['text'] = '%d edges' % self._num_edges
195
196 - def activate(self):
197 self._canvas.itemconfig('inactivebox', state='hidden') 198 self.update()
199
200 - def inactivate(self):
201 self._canvas.itemconfig('inactivebox', state='normal') 202 self.update()
203
204 - def add_callback(self, event, func):
205 self._callbacks.setdefault(event,{})[func] = 1
206
207 - def remove_callback(self, event, func=None):
208 if func is None: del self._callbacks[event] 209 else: 210 try: del self._callbacks[event][func] 211 except: pass
212
213 - def _fire_callbacks(self, event, *args):
214 if not self._callbacks.has_key(event): return 215 for cb_func in self._callbacks[event].keys(): cb_func(*args)
216
217 - def select_cell(self, i, j):
218 if self._root is None: return 219 220 # If the cell is already selected (and the chart contents 221 # haven't changed), then do nothing. 222 if ((i,j) == self._selected_cell and 223 self._chart.num_edges() == self._num_edges): return 224 225 self._selected_cell = (i,j) 226 self.update() 227 228 # Fire the callback. 229 self._fire_callbacks('select_cell', i, j)
230
231 - def deselect_cell(self):
232 if self._root is None: return 233 self._selected_cell = None 234 self._list.set([]) 235 self.update()
236
237 - def _click_cell(self, i, j):
238 if self._selected_cell == (i,j): 239 self.deselect_cell() 240 else: 241 self.select_cell(i, j)
242
243 - def view_edge(self, edge):
244 self.select_cell(*edge.span()) 245 self._list.view(edge)
246
247 - def mark_edge(self, edge):
248 if self._root is None: return 249 self.select_cell(*edge.span()) 250 self._list.mark(edge)
251
252 - def unmark_edge(self, edge=None):
253 if self._root is None: return 254 self._list.unmark(edge)
255
256 - def markonly_edge(self, edge):
257 if self._root is None: return 258 self.select_cell(*edge.span()) 259 self._list.markonly(edge)
260
261 - def draw(self):
262 if self._root is None: return 263 LEFT_MARGIN = BOT_MARGIN = 15 264 TOP_MARGIN = 5 265 c = self._canvas 266 c.delete('all') 267 N = self._chart.num_leaves()+1 268 dx = (int(c['width'])-LEFT_MARGIN)/N 269 dy = (int(c['height'])-TOP_MARGIN-BOT_MARGIN)/N 270 271 c.delete('all') 272 273 # Labels and dotted lines 274 for i in range(N): 275 c.create_text(LEFT_MARGIN-2, i*dy+dy/2+TOP_MARGIN, 276 text=`i`, anchor='e') 277 c.create_text(i*dx+dx/2+LEFT_MARGIN, N*dy+TOP_MARGIN+1, 278 text=`i`, anchor='n') 279 c.create_line(LEFT_MARGIN, dy*(i+1)+TOP_MARGIN, 280 dx*N+LEFT_MARGIN, dy*(i+1)+TOP_MARGIN, dash='.') 281 c.create_line(dx*i+LEFT_MARGIN, TOP_MARGIN, 282 dx*i+LEFT_MARGIN, dy*N+TOP_MARGIN, dash='.') 283 284 # A box around the whole thing 285 c.create_rectangle(LEFT_MARGIN, TOP_MARGIN, 286 LEFT_MARGIN+dx*N, dy*N+TOP_MARGIN, 287 width=2) 288 289 # Cells 290 self._cells = [[None for i in range(N)] for j in range(N)] 291 for i in range(N): 292 for j in range(i, N): 293 t = c.create_rectangle(j*dx+LEFT_MARGIN, i*dy+TOP_MARGIN, 294 (j+1)*dx+LEFT_MARGIN, 295 (i+1)*dy+TOP_MARGIN, 296 fill='gray20') 297 self._cells[i][j] = t 298 def cb(event, self=self, i=i, j=j): self._click_cell(i,j) 299 c.tag_bind(t, '<Button-1>', cb) 300 301 # Inactive box 302 xmax, ymax = int(c['width']), int(c['height']) 303 t = c.create_rectangle(-100, -100, xmax+100, ymax+100, 304 fill='gray50', state='hidden', 305 tag='inactivebox') 306 c.tag_lower(t) 307 308 # Update the cells. 309 self.update()
310
311 - def pack(self, *args, **kwargs):
312 self._root.pack(*args, **kwargs)
313 314 ####################################################################### 315 # Chart Results View 316 ####################################################################### 317
318 -class ChartResultsView(object):
319 - def __init__(self, parent, chart, grammar, toplevel=True):
320 self._chart = chart 321 self._grammar = grammar 322 self._trees = [] 323 self._y = 10 324 self._treewidgets = [] 325 self._selection = None 326 self._selectbox = None 327 328 if toplevel: 329 self._root = Tkinter.Toplevel(parent) 330 self._root.title('Chart Parser Application: Results') 331 self._root.bind('<Control-q>', self.destroy) 332 else: 333 self._root = Tkinter.Frame(parent) 334 335 # Buttons 336 if toplevel: 337 buttons = Tkinter.Frame(self._root) 338 buttons.pack(side='bottom', expand=0, fill='x') 339 Tkinter.Button(buttons, text='Quit', 340 command=self.destroy).pack(side='right') 341 Tkinter.Button(buttons, text='Print All', 342 command=self.print_all).pack(side='left') 343 Tkinter.Button(buttons, text='Print Selection', 344 command=self.print_selection).pack(side='left') 345 346 # Canvas frame. 347 self._cframe = CanvasFrame(self._root, closeenough=20) 348 self._cframe.pack(side='top', expand=1, fill='both') 349 350 # Initial update 351 self.update()
352
353 - def update(self, edge=None):
354 if self._root is None: return 355 # If the edge isn't a parse edge, do nothing. 356 if edge is not None: 357 if edge.lhs() != self._grammar.start(): return 358 if edge.span() != (0, self._chart.num_leaves()): return 359 360 for parse in self._chart.parses(self._grammar.start()): 361 if parse not in self._trees: 362 self._add(parse)
363
364 - def _add(self, parse):
365 # Add it to self._trees. 366 self._trees.append(parse) 367 368 # Create a widget for it. 369 c = self._cframe.canvas() 370 treewidget = tree_to_treesegment(c, parse) 371 372 # Add it to the canvas frame. 373 self._treewidgets.append(treewidget) 374 self._cframe.add_widget(treewidget, 10, self._y) 375 376 # Register callbacks. 377 treewidget.bind_click(self._click) 378 379 # Update y. 380 self._y = treewidget.bbox()[3] + 10
381
382 - def _click(self, widget):
383 c = self._cframe.canvas() 384 if self._selection is not None: 385 c.delete(self._selectbox) 386 self._selection = widget 387 (x1, y1, x2, y2) = widget.bbox() 388 self._selectbox = c.create_rectangle(x1, y1, x2, y2, 389 width=2, outline='#088')
390
391 - def _color(self, treewidget, color):
392 treewidget.node()['color'] = color 393 for child in treewidget.subtrees(): 394 if isinstance(child, TreeSegmentWidget): 395 self._color(child, color) 396 else: 397 child['color'] = color
398
399 - def print_all(self, *e):
400 if self._root is None: return 401 self._cframe.print_to_file()
402
403 - def print_selection(self, *e):
404 if self._root is None: return 405 if self._selection is None: 406 tkMessageBox.showerror('Print Error', 'No tree selected') 407 else: 408 c = self._cframe.canvas() 409 for widget in self._treewidgets: 410 if widget is not self._selection: 411 self._cframe.destroy_widget(widget) 412 c.delete(self._selectbox) 413 (x1,y1,x2,y2) = self._selection.bbox() 414 self._selection.move(10-x1,10-y1) 415 c['scrollregion'] = '0 0 %s %s' % (x2-x1+20, y2-y1+20) 416 self._cframe.print_to_file() 417 418 # Restore our state. 419 self._treewidgets = [self._selection] 420 self.clear() 421 self.update()
422
423 - def clear(self):
424 if self._root is None: return 425 for treewidget in self._treewidgets: 426 self._cframe.destroy_widget(treewidget) 427 self._trees = [] 428 self._treewidgets = [] 429 if self._selection is not None: 430 self._cframe.canvas().delete(self._selectbox) 431 self._selection = None 432 self._y = 10
433
434 - def set_chart(self, chart):
435 self.clear() 436 self._chart = chart 437 self.update()
438
439 - def set_grammar(self, grammar):
440 self.clear() 441 self._grammar = grammar 442 self.update()
443
444 - def destroy(self, *e):
445 if self._root is None: return 446 try: self._root.destroy() 447 except: pass 448 self._root = None
449
450 - def pack(self, *args, **kwargs):
451 self._root.pack(*args, **kwargs)
452 453 ####################################################################### 454 # Chart Comparer 455 ####################################################################### 456
457 -class ChartComparer(object):
458 """ 459 460 @ivar _root: The root window 461 462 @ivar _charts: A dictionary mapping names to charts. When 463 charts are loaded, they are added to this dictionary. 464 465 @ivar _left_chart: The left L{Chart}. 466 @ivar _left_name: The name C{_left_chart} (derived from filename) 467 @ivar _left_matrix: The L{ChartMatrixView} for C{_left_chart} 468 @ivar _left_selector: The drop-down C{MutableOptionsMenu} used 469 to select C{_left_chart}. 470 471 @ivar _right_chart: The right L{Chart}. 472 @ivar _right_name: The name C{_right_chart} (derived from filename) 473 @ivar _right_matrix: The L{ChartMatrixView} for C{_right_chart} 474 @ivar _right_selector: The drop-down C{MutableOptionsMenu} used 475 to select C{_right_chart}. 476 477 @ivar _out_chart: The out L{Chart}. 478 @ivar _out_name: The name C{_out_chart} (derived from filename) 479 @ivar _out_matrix: The L{ChartMatrixView} for C{_out_chart} 480 @ivar _out_label: The label for C{_out_chart}. 481 482 @ivar _op_label: A Label containing the most recent operation. 483 """ 484 485 _OPSYMBOL = {'-': '-', 486 'and': SymbolWidget.SYMBOLS['intersection'], 487 'or': SymbolWidget.SYMBOLS['union']} 488
489 - def __init__(self, *chart_filenames):
490 # This chart is displayed when we don't have a value (eg 491 # before any chart is loaded). 492 faketok = [''] * 8 493 self._emptychart = Chart(faketok) 494 495 # The left & right charts start out empty. 496 self._left_name = 'None' 497 self._right_name = 'None' 498 self._left_chart = self._emptychart 499 self._right_chart = self._emptychart 500 501 # The charts that have been loaded. 502 self._charts = {'None': self._emptychart} 503 504 # The output chart. 505 self._out_chart = self._emptychart 506 507 # The most recent operation 508 self._operator = None 509 510 # Set up the root window. 511 self._root = Tkinter.Tk() 512 self._root.title('Chart Comparison') 513 self._root.bind('<Control-q>', self.destroy) 514 self._root.bind('<Control-x>', self.destroy) 515 516 # Initialize all widgets, etc. 517 self._init_menubar(self._root) 518 self._init_chartviews(self._root) 519 self._init_divider(self._root) 520 self._init_buttons(self._root) 521 self._init_bindings(self._root) 522 523 # Load any specified charts. 524 for filename in chart_filenames: 525 self.load_chart(filename)
526
527 - def destroy(self, *e):
528 if self._root is None: return 529 try: self._root.destroy() 530 except: pass 531 self._root = None
532
533 - def mainloop(self, *args, **kwargs):
534 return 535 self._root.mainloop(*args, **kwargs)
536 537 #//////////////////////////////////////////////////////////// 538 # Initialization 539 #//////////////////////////////////////////////////////////// 540
541 - def _init_menubar(self, root):
542 menubar = Tkinter.Menu(root) 543 544 # File menu 545 filemenu = Tkinter.Menu(menubar, tearoff=0) 546 filemenu.add_command(label='Load Chart', accelerator='Ctrl-o', 547 underline=0, command=self.load_chart_dialog) 548 filemenu.add_command(label='Save Output', accelerator='Ctrl-s', 549 underline=0, command=self.save_chart_dialog) 550 filemenu.add_separator() 551 filemenu.add_command(label='Exit', underline=1, 552 command=self.destroy, accelerator='Ctrl-x') 553 menubar.add_cascade(label='File', underline=0, menu=filemenu) 554 555 # Compare menu 556 opmenu = Tkinter.Menu(menubar, tearoff=0) 557 opmenu.add_command(label='Intersection', 558 command=self._intersection, 559 accelerator='+') 560 opmenu.add_command(label='Union', 561 command=self._union, 562 accelerator='*') 563 opmenu.add_command(label='Difference', 564 command=self._difference, 565 accelerator='-') 566 opmenu.add_separator() 567 opmenu.add_command(label='Swap Charts', 568 command=self._swapcharts) 569 menubar.add_cascade(label='Compare', underline=0, menu=opmenu) 570 571 # Add the menu 572 self._root.config(menu=menubar)
573
574 - def _init_divider(self, root):
575 divider = Tkinter.Frame(root, border=2, relief='sunken') 576 divider.pack(side='top', fill='x', ipady=2)
577
578 - def _init_chartviews(self, root):
579 opfont=('symbol', -36) # Font for operator. 580 eqfont=('helvetica', -36) # Font for equals sign. 581 582 frame = Tkinter.Frame(root, background='#c0c0c0') 583 frame.pack(side='top', expand=1, fill='both') 584 585 # The left matrix. 586 cv1_frame = Tkinter.Frame(frame, border=3, relief='groove') 587 cv1_frame.pack(side='left', padx=8, pady=7, expand=1, fill='both') 588 self._left_selector = MutableOptionMenu( 589 cv1_frame, self._charts.keys(), command=self._select_left) 590 self._left_selector.pack(side='top', pady=5, fill='x') 591 self._left_matrix = ChartMatrixView(cv1_frame, self._emptychart, 592 toplevel=False, 593 show_numedges=True) 594 self._left_matrix.pack(side='bottom', padx=5, pady=5, 595 expand=1, fill='both') 596 self._left_matrix.add_callback('select', self.select_edge) 597 self._left_matrix.add_callback('select_cell', self.select_cell) 598 self._left_matrix.inactivate() 599 600 # The operator. 601 self._op_label = Tkinter.Label(frame, text=' ', width=3, 602 background='#c0c0c0', font=opfont) 603 self._op_label.pack(side='left', padx=5, pady=5) 604 605 # The right matrix. 606 cv2_frame = Tkinter.Frame(frame, border=3, relief='groove') 607 cv2_frame.pack(side='left', padx=8, pady=7, expand=1, fill='both') 608 self._right_selector = MutableOptionMenu( 609 cv2_frame, self._charts.keys(), command=self._select_right) 610 self._right_selector.pack(side='top', pady=5, fill='x') 611 self._right_matrix = ChartMatrixView(cv2_frame, self._emptychart, 612 toplevel=False, 613 show_numedges=True) 614 self._right_matrix.pack(side='bottom', padx=5, pady=5, 615 expand=1, fill='both') 616 self._right_matrix.add_callback('select', self.select_edge) 617 self._right_matrix.add_callback('select_cell', self.select_cell) 618 self._right_matrix.inactivate() 619 620 # The equals sign 621 Tkinter.Label(frame, text='=', width=3, background='#c0c0c0', 622 font=eqfont).pack(side='left', padx=5, pady=5) 623 624 # The output matrix. 625 out_frame = Tkinter.Frame(frame, border=3, relief='groove') 626 out_frame.pack(side='left', padx=8, pady=7, expand=1, fill='both') 627 self._out_label = Tkinter.Label(out_frame, text='Output') 628 self._out_label.pack(side='top', pady=9) 629 self._out_matrix = ChartMatrixView(out_frame, self._emptychart, 630 toplevel=False, 631 show_numedges=True) 632 self._out_matrix.pack(side='bottom', padx=5, pady=5, 633 expand=1, fill='both') 634 self._out_matrix.add_callback('select', self.select_edge) 635 self._out_matrix.add_callback('select_cell', self.select_cell) 636 self._out_matrix.inactivate()
637
638 - def _init_buttons(self, root):
639 buttons = Tkinter.Frame(root) 640 buttons.pack(side='bottom', pady=5, fill='x', expand=0) 641 Tkinter.Button(buttons, text='Intersection', 642 command=self._intersection).pack(side='left') 643 Tkinter.Button(buttons, text='Union', 644 command=self._union).pack(side='left') 645 Tkinter.Button(buttons, text='Difference', 646 command=self._difference).pack(side='left') 647 Tkinter.Frame(buttons, width=20).pack(side='left') 648 Tkinter.Button(buttons, text='Swap Charts', 649 command=self._swapcharts).pack(side='left') 650 651 Tkinter.Button(buttons, text='Detatch Output', 652 command=self._detatch_out).pack(side='right')
653
654 - def _init_bindings(self, root):
655 #root.bind('<Control-s>', self.save_chart) 656 root.bind('<Control-o>', self.load_chart_dialog)
657 #root.bind('<Control-r>', self.reset) 658 659 #//////////////////////////////////////////////////////////// 660 # Input Handling 661 #//////////////////////////////////////////////////////////// 662
663 - def _select_left(self, name):
664 self._left_name = name 665 self._left_chart = self._charts[name] 666 self._left_matrix.set_chart(self._left_chart) 667 if name == 'None': self._left_matrix.inactivate() 668 self._apply_op()
669
670 - def _select_right(self, name):
671 self._right_name = name 672 self._right_chart = self._charts[name] 673 self._right_matrix.set_chart(self._right_chart) 674 if name == 'None': self._right_matrix.inactivate() 675 self._apply_op()
676
677 - def _apply_op(self):
678 if self._operator == '-': self._difference() 679 elif self._operator == 'or': self._union() 680 elif self._operator == 'and': self._intersection()
681 682 683 #//////////////////////////////////////////////////////////// 684 # File 685 #//////////////////////////////////////////////////////////// 686 CHART_FILE_TYPES = [('Pickle file', '.pickle'), 687 ('All files', '*')] 688
689 - def save_chart_dialog(self, *args):
690 filename = asksaveasfilename(filetypes=self.CHART_FILE_TYPES, 691 defaultextension='.pickle') 692 if not filename: return 693 try: pickle.dump((self._out_chart), open(filename, 'w')) 694 except Exception, e: 695 tkMessageBox.showerror('Error Saving Chart', 696 'Unable to open file: %r\n%s' % 697 (filename, e))
698
699 - def load_chart_dialog(self, *args):
700 filename = askopenfilename(filetypes=self.CHART_FILE_TYPES, 701 defaultextension='.pickle') 702 if not filename: return 703 try: self.load_chart(filename) 704 except Exception, e: 705 tkMessageBox.showerror('Error Loading Chart', 706 'Unable to open file: %r\n%s' % 707 (filename, e))
708
709 - def load_chart(self, filename):
710 chart = pickle.load(open(filename, 'r')) 711 name = os.path.basename(filename) 712 if name.endswith('.pickle'): name = name[:-7] 713 if name.endswith('.chart'): name = name[:-6] 714 self._charts[name] = chart 715 self._left_selector.add(name) 716 self._right_selector.add(name) 717 718 # If either left_matrix or right_matrix is empty, then 719 # display the new chart. 720 if self._left_chart is self._emptychart: 721 self._left_selector.set(name) 722 elif self._right_chart is self._emptychart: 723 self._right_selector.set(name)
724
725 - def _update_chartviews(self):
726 self._left_matrix.update() 727 self._right_matrix.update() 728 self._out_matrix.update()
729 730 #//////////////////////////////////////////////////////////// 731 # Selection 732 #//////////////////////////////////////////////////////////// 733
734 - def select_edge(self, edge):
735 if edge in self._left_chart: 736 self._left_matrix.markonly_edge(edge) 737 else: 738 self._left_matrix.unmark_edge() 739 if edge in self._right_chart: 740 self._right_matrix.markonly_edge(edge) 741 else: 742 self._right_matrix.unmark_edge() 743 if edge in self._out_chart: 744 self._out_matrix.markonly_edge(edge) 745 else: 746 self._out_matrix.unmark_edge()
747
748 - def select_cell(self, i, j):
749 self._left_matrix.select_cell(i, j) 750 self._right_matrix.select_cell(i, j) 751 self._out_matrix.select_cell(i, j)
752 753 #//////////////////////////////////////////////////////////// 754 # Operations 755 #//////////////////////////////////////////////////////////// 756
757 - def _difference(self):
758 if not self._checkcompat(): return 759 760 out_chart = Chart(self._left_chart.tokens()) 761 for edge in self._left_chart: 762 if edge not in self._right_chart: 763 out_chart.insert(edge, []) 764 765 self._update('-', out_chart)
766
767 - def _intersection(self):
768 if not self._checkcompat(): return 769 770 out_chart = Chart(self._left_chart.tokens()) 771 for edge in self._left_chart: 772 if edge in self._right_chart: 773 out_chart.insert(edge, []) 774 775 self._update('and', out_chart)
776
777 - def _union(self):
778 if not self._checkcompat(): return 779 780 out_chart = Chart(self._left_chart.tokens()) 781 for edge in self._left_chart: 782 out_chart.insert(edge, []) 783 for edge in self._right_chart: 784 out_chart.insert(edge, []) 785 786 self._update('or', out_chart)
787
788 - def _swapcharts(self):
789 left, right = self._left_name, self._right_name 790 self._left_selector.set(right) 791 self._right_selector.set(left)
792
793 - def _checkcompat(self):
794 if (self._left_chart.tokens() != self._right_chart.tokens() or 795 self._left_chart.property_names() != 796 self._right_chart.property_names() or 797 self._left_chart == self._emptychart or 798 self._right_chart == self._emptychart): 799 # Clear & inactivate the output chart. 800 self._out_chart = self._emptychart 801 self._out_matrix.set_chart(self._out_chart) 802 self._out_matrix.inactivate() 803 self._out_label['text'] = 'Output' 804 # Issue some other warning? 805 return False 806 else: 807 return True
808
809 - def _update(self, operator, out_chart):
810 self._operator = operator 811 self._op_label['text'] = self._OPSYMBOL[operator] 812 self._out_chart = out_chart 813 self._out_matrix.set_chart(out_chart) 814 self._out_label['text'] = '%s %s %s' % (self._left_name, 815 self._operator, 816 self._right_name)
817
818 - def _clear_out_chart(self):
819 self._out_chart = self._emptychart 820 self._out_matrix.set_chart(self._out_chart) 821 self._op_label['text'] = ' ' 822 self._out_matrix.inactivate()
823
824 - def _detatch_out(self):
825 ChartMatrixView(self._root, self._out_chart, 826 title=self._out_label['text'])
827 828 829 830 831 832 833 834 835 ####################################################################### 836 # Chart View 837 ####################################################################### 838
839 -class ChartView(object):
840 """ 841 A component for viewing charts. This is used by C{ChartParserApp} to 842 allow students to interactively experiment with various chart 843 parsing techniques. It is also used by C{Chart.draw()}. 844 845 @ivar _chart: The chart that we are giving a view of. This chart 846 may be modified; after it is modified, you should call 847 C{update}. 848 @ivar _sentence: The list of tokens that the chart spans. 849 850 @ivar _root: The root window. 851 @ivar _chart_canvas: The canvas we're using to display the chart 852 itself. 853 @ivar _tree_canvas: The canvas we're using to display the tree 854 that each edge spans. May be None, if we're not displaying 855 trees. 856 @ivar _sentence_canvas: The canvas we're using to display the sentence 857 text. May be None, if we're not displaying the sentence text. 858 @ivar _edgetags: A dictionary mapping from edges to the tags of 859 the canvas elements (lines, etc) used to display that edge. 860 The values of this dictionary have the form 861 C{(linetag, rhstag1, dottag, rhstag2, lhstag)}. 862 @ivar _treetags: A list of all the tags that make up the tree; 863 used to erase the tree (without erasing the loclines). 864 @ivar _chart_height: The height of the chart canvas. 865 @ivar _sentence_height: The height of the sentence canvas. 866 @ivar _tree_height: The height of the tree 867 868 @ivar _text_height: The height of a text string (in the normal 869 font). 870 871 @ivar _edgelevels: A list of edges at each level of the chart (the 872 top level is the 0th element). This list is used to remember 873 where edges should be drawn; and to make sure that no edges 874 are overlapping on the chart view. 875 876 @ivar _unitsize: Pixel size of one unit (from the location). This 877 is determined by the span of the chart's location, and the 878 width of the chart display canvas. 879 880 @ivar _fontsize: The current font size 881 882 @ivar _marks: A dictionary from edges to marks. Marks are 883 strings, specifying colors (e.g. 'green'). 884 """ 885 886 _LEAF_SPACING = 10 887 _MARGIN = 10 888 _TREE_LEVEL_SIZE = 12 889 _CHART_LEVEL_SIZE = 40 890
891 - def __init__(self, chart, root=None, **kw):
892 """ 893 Construct a new C{Chart} display. 894 """ 895 # Process keyword args. 896 draw_tree = kw.get('draw_tree', 0) 897 draw_sentence = kw.get('draw_sentence', 1) 898 self._fontsize = kw.get('fontsize', -12) 899 900 # The chart! 901 self._chart = chart 902 903 # Callback functions 904 self._callbacks = {} 905 906 # Keep track of drawn edges 907 self._edgelevels = [] 908 self._edgetags = {} 909 910 # Keep track of which edges are marked. 911 self._marks = {} 912 913 # These are used to keep track of the set of tree tokens 914 # currently displayed in the tree canvas. 915 self._treetoks = [] 916 self._treetoks_edge = None 917 self._treetoks_index = 0 918 919 # Keep track of the tags used to draw the tree 920 self._tree_tags = [] 921 922 # Put multiple edges on each level? 923 self._compact = 0 924 925 # If they didn't provide a main window, then set one up. 926 if root is None: 927 top = Tkinter.Tk() 928 top.title('Chart View') 929 def destroy1(e, top=top): top.destroy() 930 def destroy2(top=top): top.destroy() 931 top.bind('q', destroy1) 932 b = Tkinter.Button(top, text='Done', command=destroy2) 933 b.pack(side='bottom') 934 self._root = top 935 else: 936 self._root = root 937 938 # Create some fonts. 939 self._init_fonts(root) 940 941 # Create the chart canvas. 942 (self._chart_sb, self._chart_canvas) = self._sb_canvas(self._root) 943 self._chart_canvas['height'] = 300 944 self._chart_canvas['closeenough'] = 15 945 946 # Create the sentence canvas. 947 if draw_sentence: 948 cframe = Tkinter.Frame(self._root, relief='sunk', border=2) 949 cframe.pack(fill='both', side='bottom') 950 self._sentence_canvas = Tkinter.Canvas(cframe, height=50) 951 self._sentence_canvas['background'] = '#e0e0e0' 952 self._sentence_canvas.pack(fill='both') 953 #self._sentence_canvas['height'] = self._sentence_height 954 else: 955 self._sentence_canvas = None 956 957 # Create the tree canvas. 958 if draw_tree: 959 (sb, canvas) = self._sb_canvas(self._root, 'n', 'x') 960 (self._tree_sb, self._tree_canvas) = (sb, canvas) 961 self._tree_canvas['height'] = 200 962 else: 963 self._tree_canvas = None 964 965 # Do some analysis to figure out how big the window should be 966 self._analyze() 967 self.draw() 968 self._resize() 969 self._grow() 970 971 # Set up the configure callback, which will be called whenever 972 # the window is resized. 973 self._chart_canvas.bind('<Configure>', self._configure)
974
975 - def _init_fonts(self, root):
976 self._boldfont = tkFont.Font(family='helvetica', weight='bold', 977 size=self._fontsize) 978 self._font = tkFont.Font(family='helvetica', 979 size=self._fontsize) 980 # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html> 981 self._sysfont = tkFont.Font(font=Tkinter.Button()["font"]) 982 root.option_add("*Font", self._sysfont)
983
984 - def _sb_canvas(self, root, expand='y', 985 fill='both', side='bottom'):
986 """ 987 Helper for __init__: construct a canvas with a scrollbar. 988 """ 989 cframe =Tkinter.Frame(root, relief='sunk', border=2) 990 cframe.pack(fill=fill, expand=expand, side=side) 991 canvas = Tkinter.Canvas(cframe, background='#e0e0e0') 992 993 # Give the canvas a scrollbar. 994 sb = Tkinter.Scrollbar(cframe, orient='vertical') 995 sb.pack(side='right', fill='y') 996 canvas.pack(side='left', fill=fill, expand='yes') 997 998 # Connect the scrollbars to the canvas. 999 sb['command']= canvas.yview 1000 canvas['yscrollcommand'] = sb.set 1001 1002 return (sb, canvas)
1003
1004 - def scroll_up(self, *e):
1005 self._chart_canvas.yview('scroll', -1, 'units')
1006
1007 - def scroll_down(self, *e):
1008 self._chart_canvas.yview('scroll', 1, 'units')
1009
1010 - def page_up(self, *e):
1011 self._chart_canvas.yview('scroll', -1, 'pages')
1012
1013 - def page_down(self, *e):
1014 self._chart_canvas.yview('scroll', 1, 'pages')
1015
1016 - def _grow(self):
1017 """ 1018 Grow the window, if necessary 1019 """ 1020 # Grow, if need-be 1021 N = self._chart.num_leaves() 1022 width = max(int(self._chart_canvas['width']), 1023 N * self._unitsize + ChartView._MARGIN * 2 ) 1024 1025 # It won't resize without the second (height) line, but I 1026 # don't understand why not. 1027 self._chart_canvas.configure(width=width) 1028 self._chart_canvas.configure(height=self._chart_canvas['height']) 1029 1030 self._unitsize = (width - 2*ChartView._MARGIN) / N 1031 1032 # Reset the height for the sentence window. 1033 if self._sentence_canvas is not None: 1034 self._sentence_canvas['height'] = self._sentence_height
1035
1036 - def set_font_size(self, size):
1037 self._font.configure(size=-abs(size)) 1038 self._boldfont.configure(size=-abs(size)) 1039 self._sysfont.configure(size=-abs(size)) 1040 self._analyze() 1041 self._grow() 1042 self.draw()
1043
1044 - def get_font_size(self):
1045 return abs(self._fontsize)
1046
1047 - def _configure(self, e):
1048 """ 1049 The configure callback. This is called whenever the window is 1050 resized. It is also called when the window is first mapped. 1051 It figures out the unit size, and redraws the contents of each 1052 canvas. 1053 """ 1054 N = self._chart.num_leaves() 1055 self._unitsize = (e.width - 2*ChartView._MARGIN) / N 1056 self.draw()
1057
1058 - def update(self, chart=None):
1059 """ 1060 Draw any edges that have not been drawn. This is typically 1061 called when a after modifies the canvas that a CanvasView is 1062 displaying. C{update} will cause any edges that have been 1063 added to the chart to be drawn. 1064 1065 If update is given a C{chart} argument, then it will replace 1066 the current chart with the given chart. 1067 """ 1068 if chart is not None: 1069 self._chart = chart 1070 self._edgelevels = [] 1071 self._marks = {} 1072 self._analyze() 1073 self._grow() 1074 self.draw() 1075 self.erase_tree() 1076 self._resize() 1077 else: 1078 for edge in self._chart: 1079 if not self._edgetags.has_key(edge): 1080 self._add_edge(edge) 1081 self._resize()
1082 1083
1084 - def _edge_conflict(self, edge, lvl):
1085 """ 1086 Return 1 if the given edge overlaps with any edge on the given 1087 level. This is used by _add_edge to figure out what level a 1088 new edge should be added to. 1089 """ 1090 (s1, e1) = edge.span() 1091 for otheredge in self._edgelevels[lvl]: 1092 (s2, e2) = otheredge.span() 1093 if (s1 <= s2 < e1) or (s2 <= s1 < e2) or (s1==s2==e1==e2): 1094 return 1 1095 return 0
1096
1097 - def _analyze_edge(self, edge):
1098 """ 1099 Given a new edge, recalculate: 1100 1101 - _text_height 1102 - _unitsize (if the edge text is too big for the current 1103 _unitsize, then increase _unitsize) 1104 """ 1105 c = self._chart_canvas 1106 1107 if isinstance(edge, TreeEdge): 1108 lhs = edge.lhs() 1109 rhselts = [] 1110 for elt in edge.rhs(): 1111 if isinstance(elt, Nonterminal): 1112 rhselts.append(str(elt.symbol())) 1113 else: 1114 rhselts.append(repr(elt)) 1115 rhs = string.join(rhselts) 1116 else: 1117 lhs = edge.lhs() 1118 rhs = '' 1119 1120 for s in (lhs, rhs): 1121 tag = c.create_text(0,0, text=s, 1122 font=self._boldfont, 1123 anchor='nw', justify='left') 1124 bbox = c.bbox(tag) 1125 c.delete(tag) 1126 width = bbox[2] #+ ChartView._LEAF_SPACING 1127 edgelen = max(edge.length(), 1) 1128 self._unitsize = max(self._unitsize, width/edgelen) 1129 self._text_height = max(self._text_height, bbox[3] - bbox[1])
1130
1131 - def _add_edge(self, edge, minlvl=0):
1132 """ 1133 Add a single edge to the ChartView: 1134 1135 - Call analyze_edge to recalculate display parameters 1136 - Find an available level 1137 - Call _draw_edge 1138 """ 1139 # Do NOT show leaf edges in the chart. 1140 if isinstance(edge, LeafEdge): return 1141 1142 if self._edgetags.has_key(edge): return 1143 self._analyze_edge(edge) 1144 self._grow() 1145 1146 if not self._compact: 1147 self._edgelevels.append([edge]) 1148 lvl = len(self._edgelevels)-1 1149 self._draw_edge(edge, lvl) 1150 self._resize() 1151 return 1152 1153 # Figure out what level to draw the edge on. 1154 lvl = 0 1155 while 1: 1156 # If this level doesn't exist yet, create it. 1157 while lvl >= len(self._edgelevels): 1158 self._edgelevels.append([]) 1159 self._resize() 1160 1161 # Check if we can fit the edge in this level. 1162 if lvl>=minlvl and not self._edge_conflict(edge, lvl): 1163 # Go ahead and draw it. 1164 self._edgelevels[lvl].append(edge) 1165 break 1166 1167 # Try the next level. 1168 lvl += 1 1169 1170 self._draw_edge(edge, lvl)
1171
1172 - def view_edge(self, edge):
1173 level = None 1174 for i in range(len(self._edgelevels)): 1175 if edge in self._edgelevels[i]: 1176 level = i 1177 break 1178 if level == None: return 1179 # Try to view the new edge.. 1180 y = (level+1) * self._chart_level_size 1181 dy = self._text_height + 10 1182 self._chart_canvas.yview('moveto', 1.0) 1183 if self._chart_height != 0: 1184 self._chart_canvas.yview('moveto', 1185 float(y-dy)/self._chart_height)
1186
1187 - def _draw_edge(self, edge, lvl):
1188 """ 1189 Draw a single edge on the ChartView. 1190 """ 1191 c = self._chart_canvas 1192 1193 # Draw the arrow. 1194 x1 = (edge.start() * self._unitsize + ChartView._MARGIN) 1195 x2 = (edge.end() * self._unitsize + ChartView._MARGIN) 1196 if x2 == x1: x2 += max(4, self._unitsize/5) 1197 y = (lvl+1) * self._chart_level_size 1198 linetag = c.create_line(x1, y, x2, y, arrow='last', width=3) 1199 1200 # Draw a label for the edge. 1201 if isinstance(edge, TreeEdge): 1202 rhs = [] 1203 for elt in edge.rhs(): 1204 if isinstance(elt, Nonterminal): 1205 rhs.append(str(elt.symbol())) 1206 else: 1207 rhs.append(repr(elt)) 1208 pos = edge.dot() 1209 else: 1210 rhs = [] 1211 pos = 0 1212 1213 rhs1 = string.join(rhs[:pos]) 1214 rhs2 = string.join(rhs[pos:]) 1215 rhstag1 = c.create_text(x1+3, y, text=rhs1, 1216 font=self._font, 1217 anchor='nw') 1218 dotx = c.bbox(rhstag1)[2] + 6 1219 doty = (c.bbox(rhstag1)[1]+c.bbox(rhstag1)[3])/2 1220 dottag = c.create_oval(dotx-2, doty-2, dotx+2, doty+2) 1221 rhstag2 = c.create_text(dotx+6, y, text=rhs2, 1222 font=self._font, 1223 anchor='nw') 1224 lhstag = c.create_text((x1+x2)/2, y, text=str(edge.lhs()), 1225 anchor='s', 1226 font=self._boldfont) 1227 1228 # Keep track of the edge's tags. 1229 self._edgetags[edge] = (linetag, rhstag1, 1230 dottag, rhstag2, lhstag) 1231 1232 # Register a callback for clicking on the edge. 1233 def cb(event, self=self, edge=edge): 1234 self._fire_callbacks('select', edge)
1235 c.tag_bind(rhstag1, '<Button-1>', cb) 1236 c.tag_bind(rhstag2, '<Button-1>', cb) 1237 c.tag_bind(linetag, '<Button-1>', cb) 1238 c.tag_bind(dottag, '<Button-1>', cb) 1239 c.tag_bind(lhstag, '<Button-1>', cb) 1240 1241 self._color_edge(edge)
1242
1243 - def _color_edge(self, edge, linecolor=None, textcolor=None):
1244 """ 1245 Color in an edge with the given colors. 1246 If no colors are specified, use intelligent defaults 1247 (dependant on selection, etc.) 1248 """ 1249 if not self._edgetags.has_key(edge): return 1250 c = self._chart_canvas 1251 1252 if linecolor is not None and textcolor is not None: 1253 if self._marks.has_key(edge): 1254 linecolor = self._marks[edge] 1255 tags = self._edgetags[edge] 1256 c.itemconfig(tags[0], fill=linecolor) 1257 c.itemconfig(tags[1], fill=textcolor) 1258 c.itemconfig(tags[2], fill=textcolor, 1259 outline=textcolor) 1260 c.itemconfig(tags[3], fill=textcolor) 1261 c.itemconfig(tags[4], fill=textcolor) 1262 return 1263 else: 1264 N = self._chart.num_leaves() 1265 if self._marks.has_key(edge): 1266 self._color_edge(self._marks[edge]) 1267 if (edge.is_complete() and edge.span() == (0, N)): 1268 self._color_edge(edge, '#084', '#042') 1269 elif isinstance(edge, LeafEdge): 1270 self._color_edge(edge, '#48c', '#246') 1271 else: 1272 self._color_edge(edge, '#00f', '#008')
1273
1274 - def mark_edge(self, edge, mark='#0df'):
1275 """ 1276 Mark an edge 1277 """ 1278 self._marks[edge] = mark 1279 self._color_edge(edge)
1280
1281 - def unmark_edge(self, edge=None):
1282 """ 1283 Unmark an edge (or all edges) 1284 """ 1285 if edge == None: 1286 old_marked_edges = self._marks.keys() 1287 self._marks = {} 1288 for edge in old_marked_edges: 1289 self._color_edge(edge) 1290 else: 1291 del self._marks[edge] 1292 self._color_edge(edge)
1293
1294 - def markonly_edge(self, edge, mark='#0df'):
1295 self.unmark_edge() 1296 self.mark_edge(edge, mark)
1297
1298 - def _analyze(self):
1299 """ 1300 Analyze the sentence string, to figure out how big a unit needs 1301 to be, How big the tree should be, etc. 1302 """ 1303 # Figure out the text height and the unit size. 1304 unitsize = 70 # min unitsize 1305 text_height = 0 1306 c = self._chart_canvas 1307 1308 # Check against all tokens 1309 for leaf in self._chart.leaves(): 1310 tag = c.create_text(0,0, text=repr(leaf), 1311 font=self._font, 1312 anchor='nw', justify='left') 1313 bbox = c.bbox(tag) 1314 c.delete(tag) 1315 width = bbox[2] + ChartView._LEAF_SPACING 1316 unitsize = max(width, unitsize) 1317 text_height = max(text_height, bbox[3] - bbox[1]) 1318 1319 self._unitsize = unitsize 1320 self._text_height = text_height 1321 self._sentence_height = (self._text_height + 1322 2*ChartView._MARGIN) 1323 1324 # Check against edges. 1325 for edge in self._chart.edges(): 1326 self._analyze_edge(edge) 1327 1328 # Size of chart levels 1329 self._chart_level_size = self._text_height * 2 1330 1331 # Default tree size.. 1332 self._tree_height = (3 * (ChartView._TREE_LEVEL_SIZE + 1333 self._text_height)) 1334 1335 # Resize the scrollregions. 1336 self._resize()
1337
1338 - def _resize(self):
1339 """ 1340 Update the scroll-regions for each canvas. This ensures that 1341 everything is within a scroll-region, so the user can use the 1342 scrollbars to view the entire display. This does I{not} 1343 resize the window. 1344 """ 1345 c = self._chart_canvas 1346 1347 # Reset the chart scroll region 1348 width = ( self._chart.num_leaves() * self._unitsize + 1349 ChartView._MARGIN * 2 ) 1350 1351 levels = len(self._edgelevels) 1352 self._chart_height = (levels+2)*self._chart_level_size 1353 c['scrollregion']=(0,0,width,self._chart_height) 1354 1355 # Reset the tree scroll region 1356 if self._tree_canvas: 1357 self._tree_canvas['scrollregion'] = (0, 0, width, 1358 self._tree_height)
1359
1360 - def _draw_loclines(self):
1361 """ 1362 Draw location lines. These are vertical gridlines used to 1363 show where each location unit is. 1364 """ 1365 BOTTOM = 50000 1366 c1 = self._tree_canvas 1367 c2 = self._sentence_canvas 1368 c3 = self._chart_canvas 1369 margin = ChartView._MARGIN 1370 self._loclines = [] 1371 for i in range(0, self._chart.num_leaves()+1): 1372 x = i*self._unitsize + margin 1373 1374 if c1: 1375 t1=c1.create_line(x, 0, x, BOTTOM) 1376 c1.tag_lower(t1) 1377 if c2: 1378 t2=c2.create_line(x, 0, x, self._sentence_height) 1379 c2.tag_lower(t2) 1380 t3=c3.create_line(x, 0, x, BOTTOM) 1381 c3.tag_lower(t3) 1382 t4=c3.create_text(x+2, 0, text=`i`, anchor='nw', 1383 font=self._font) 1384 c3.tag_lower(t4) 1385 #if i % 4 == 0: 1386 # if c1: c1.itemconfig(t1, width=2, fill='gray60') 1387 # if c2: c2.itemconfig(t2, width=2, fill='gray60') 1388 # c3.itemconfig(t3, width=2, fill='gray60') 1389 if i % 2 == 0: 1390 if c1: c1.itemconfig(t1, fill='gray60') 1391 if c2: c2.itemconfig(t2, fill='gray60') 1392 c3.itemconfig(t3, fill='gray60') 1393 else: 1394 if c1: c1.itemconfig(t1, fill='gray80') 1395 if c2: c2.itemconfig(t2, fill='gray80') 1396 c3.itemconfig(t3, fill='gray80')
1397
1398 - def _draw_sentence(self):
1399 """Draw the sentence string.""" 1400 if self._chart.num_leaves() == 0: return 1401 c = self._sentence_canvas 1402 margin = ChartView._MARGIN 1403 y = ChartView._MARGIN 1404 1405 for i, leaf in enumerate(self._chart.leaves()): 1406 x1 = i * self._unitsize + margin 1407 x2 = x1 + self._unitsize 1408 x = (x1+x2)/2 1409 tag = c.create_text(x, y, text=repr(leaf), 1410 font=self._font, 1411 anchor='n', justify='left') 1412 bbox = c.bbox(tag) 1413 rt=c.create_rectangle(x1+2, bbox[1]-(ChartView._LEAF_SPACING/2), 1414 x2-2, bbox[3]+(ChartView._LEAF_SPACING/2), 1415 fill='#f0f0f0', outline='#f0f0f0') 1416 c.tag_lower(rt)
1417
1418 - def erase_tree(self):
1419 for tag in self._tree_tags: self._tree_canvas.delete(tag) 1420 self._treetoks = [] 1421 self._treetoks_edge = None 1422 self._treetoks_index = 0
1423
1424 - def draw_tree(self, edge=None):
1425 if edge is None and self._treetoks_edge is None: return 1426 if edge is None: edge = self._treetoks_edge 1427 1428 # If it's a new edge, then get a new list of treetoks. 1429 if self._treetoks_edge != edge: 1430 self._treetoks = [t for t in self._chart.trees(edge) 1431 if isinstance(t, Tree)] 1432 self._treetoks_edge = edge 1433 self._treetoks_index = 0 1434 1435 # Make sure there's something to draw. 1436 if len(self._treetoks) == 0: return 1437 1438 # Erase the old tree. 1439 for tag in self._tree_tags: self._tree_canvas.delete(tag) 1440 1441 # Draw the new tree. 1442 tree = self._treetoks[self._treetoks_index] 1443 self._draw_treetok(tree, edge.start()) 1444 1445 # Show how many trees are available for the edge. 1446 self._draw_treecycle() 1447 1448 # Update the scroll region. 1449 w = self._chart.num_leaves()*self._unitsize+2*ChartView._MARGIN 1450 h = tree.height() * (ChartView._TREE_LEVEL_SIZE+self._text_height) 1451 self._tree_canvas['scrollregion'] = (0, 0, w, h)
1452
1453 - def cycle_tree(self):
1454 self._treetoks_index = (self._treetoks_index+1)%len(self._treetoks) 1455 self.draw_tree(self._treetoks_edge)
1456
1457 - def _draw_treecycle(self):
1458 if len(self._treetoks) <= 1: return 1459 1460 # Draw the label. 1461 label = '%d Trees' % len(self._treetoks) 1462 c = self._tree_canvas 1463 margin = ChartView._MARGIN 1464 right = self._chart.num_leaves()*self._unitsize+margin-2 1465 tag = c.create_text(right, 2, anchor='ne', text=label, 1466 font=self._boldfont) 1467 self._tree_tags.append(tag) 1468 _, _, _, y = c.bbox(tag) 1469 1470 # Draw the triangles. 1471 for i in range(len(self._treetoks)): 1472 x = right - 20*(len(self._treetoks)-i-1) 1473 if i == self._treetoks_index: fill = '#084' 1474 else: fill = '#fff' 1475 tag = c.create_polygon(x, y+10, x-5, y, x-10, y+10, 1476 fill=fill, outline='black') 1477 self._tree_tags.append(tag) 1478 1479 # Set up a callback: show the tree if they click on its 1480 # triangle. 1481 def cb(event, self=self, i=i): 1482 self._treetoks_index = i 1483 self.draw_tree()
1484 c.tag_bind(tag, '<Button-1>', cb) 1485
1486 - def _draw_treetok(self, treetok, index, depth=0):
1487 """ 1488 @param index: The index of the first leaf in the tree. 1489 @return: The index of the first leaf after the tree. 1490 """ 1491 c = self._tree_canvas 1492 margin = ChartView._MARGIN 1493 1494 # Draw the children 1495 child_xs = [] 1496 for child in treetok: 1497 if isinstance(child, Tree): 1498 child_x, index = self._draw_treetok(child, index, depth+1) 1499 child_xs.append(child_x) 1500 else: 1501 child_xs.append((2*index+1)*self._unitsize/2 + margin) 1502 index += 1 1503 1504 # If we have children, then get the node's x by averaging their 1505 # node x's. Otherwise, make room for ourselves. 1506 if child_xs: 1507 nodex = sum(child_xs)/len(child_xs) 1508 else: 1509 # [XX] breaks for null productions. 1510 nodex = (2*index+1)*self._unitsize/2 + margin 1511 index += 1 1512 1513 # Draw the node 1514 nodey = depth * (ChartView._TREE_LEVEL_SIZE + self._text_height) 1515 tag = c.create_text(nodex, nodey, anchor='n', justify='center', 1516 text=str(treetok.node), fill='#042', 1517 font=self._boldfont) 1518 self._tree_tags.append(tag) 1519 1520 # Draw lines to the children. 1521 childy = nodey + ChartView._TREE_LEVEL_SIZE + self._text_height 1522 for childx, child in zip(child_xs, treetok): 1523 if isinstance(child, Tree) and child: 1524 # A "real" tree token: 1525 tag = c.create_line(nodex, nodey + self._text_height, 1526 childx, childy, width=2, fill='#084') 1527 self._tree_tags.append(tag) 1528 if isinstance(child, Tree) and not child: 1529 # An unexpanded tree token: 1530 tag = c.create_line(nodex, nodey + self._text_height, 1531 childx, childy, width=2, 1532 fill='#048', dash='2 3') 1533 self._tree_tags.append(tag) 1534 if not isinstance(child, Tree): 1535 # A leaf: 1536 tag = c.create_line(nodex, nodey + self._text_height, 1537 childx, 10000, width=2, fill='#084') 1538 self._tree_tags.append(tag) 1539 1540 return nodex, index
1541
1542 - def draw(self):
1543 """ 1544 Draw everything (from scratch). 1545 """ 1546 if self._tree_canvas: 1547 self._tree_canvas.delete('all') 1548 self.draw_tree() 1549 1550 if self._sentence_canvas: 1551 self._sentence_canvas.delete('all') 1552 self._draw_sentence() 1553 1554 self._chart_canvas.delete('all') 1555 self._edgetags = {} 1556 1557 # Redraw any edges we erased. 1558 for lvl in range(len(self._edgelevels)): 1559 for edge in self._edgelevels[lvl]: 1560 self._draw_edge(edge, lvl) 1561 1562 for edge in self._chart: 1563 self._add_edge(edge) 1564 1565 self._draw_loclines()
1566
1567 - def add_callback(self, event, func):
1568 self._callbacks.setdefault(event,{})[func] = 1
1569
1570 - def remove_callback(self, event, func=None):
1571 if func is None: del self._callbacks[event] 1572 else: 1573 try: del self._callbacks[event][func] 1574 except: pass
1575
1576 - def _fire_callbacks(self, event, *args):
1577 if not self._callbacks.has_key(event): return 1578 for cb_func in self._callbacks[event].keys(): cb_func(*args)
1579 1580 ####################################################################### 1581 # Edge Rules 1582 ####################################################################### 1583 # These version of the chart rules only apply to a specific edge. 1584 # This lets the user select an edge, and then apply a rule. 1585
1586 -class EdgeRule(object):
1587 """ 1588 To create an edge rule, make an empty base class that uses 1589 EdgeRule as the first base class, and the basic rule as the 1590 second base class. (Order matters!) 1591 """
1592 - def __init__(self, edge):
1593 super = self.__class__.__bases__[1] 1594 self._edge = edge 1595 self.NUM_EDGES = super.NUM_EDGES-1
1596 - def apply_iter(self, chart, grammar, *edges):
1597 super = self.__class__.__bases__[1] 1598 edges += (self._edge,) 1599 for e in super.apply_iter(self, chart, grammar, *edges): yield e
1600 - def __str__(self):
1601 super = self.__class__.__bases__[1] 1602 return super.__str__(self)
1603
1604 -class TopDownPredictEdgeRule(EdgeRule, TopDownPredictRule):
1605 pass
1606 -class BottomUpEdgeRule(EdgeRule, BottomUpPredictRule):
1607 pass
1608 -class BottomUpLeftCornerEdgeRule(EdgeRule, BottomUpPredictCombineRule):
1609 pass
1610 -class FundamentalEdgeRule(EdgeRule, SingleEdgeFundamentalRule):
1611 pass 1612 1613 ####################################################################### 1614 # Chart Parser Application 1615 ####################################################################### 1616
1617 -class ChartParserApp(object):
1618 - def __init__(self, grammar, tokens, title='Chart Parser Application'):
1619 # Initialize the parser 1620 self._init_parser(grammar, tokens) 1621 1622 self._root = None 1623 try: 1624 # Create the root window. 1625 self._root = Tkinter.Tk() 1626 self._root.title(title) 1627 self._root.bind('<Control-q>', self.destroy) 1628 1629 # Set up some frames. 1630 frame3 = Tkinter.Frame(self._root) 1631 frame2 = Tkinter.Frame(self._root) 1632 frame1 = Tkinter.Frame(self._root) 1633 frame3.pack(side='bottom', fill='none') 1634 frame2.pack(side='bottom', fill='x') 1635 frame1.pack(side='bottom', fill='both', expand=1) 1636 1637 self._init_fonts(self._root) 1638 self._init_animation() 1639 self._init_chartview(frame1) 1640 self._init_rulelabel(frame2) 1641 self._init_buttons(frame3) 1642 self._init_menubar() 1643 1644 self._matrix = None 1645 self._results = None 1646 1647 # Set up keyboard bindings. 1648 self._init_bindings() 1649 1650 except: 1651 print 'Error creating Tree View' 1652 self.destroy() 1653 raise
1654
1655 - def destroy(self, *args):
1656 if self._root is None: return 1657 self._root.destroy() 1658 self._root = None
1659
1660 - def mainloop(self, *args, **kwargs):
1661 """ 1662 Enter the Tkinter mainloop. This function must be called if 1663 this demo is created from a non-interactive program (e.g. 1664 from a secript); otherwise, the demo will close as soon as 1665 the script completes. 1666 """ 1667 if in_idle(): return 1668 self._root.mainloop(*args, **kwargs)
1669 1670 #//////////////////////////////////////////////////////////// 1671 # Initialization Helpers 1672 #//////////////////////////////////////////////////////////// 1673
1674 - def _init_parser(self, grammar, tokens):
1675 self._grammar = grammar 1676 self._tokens = tokens 1677 self._reset_parser()
1678
1679 - def _reset_parser(self):
1680 self._cp = SteppingChartParser(self._grammar) 1681 self._cp.initialize(self._tokens) 1682 self._chart = self._cp.chart() 1683 1684 # Insert LeafEdges before the parsing starts. 1685 LeafInitRule().apply(self._chart, self._grammar) 1686 1687 # The step iterator -- use this to generate new edges 1688 self._cpstep = self._cp.step() 1689 1690 # The currently selected edge 1691 self._selection = None
1692
1693 - def _init_fonts(self, root):
1694 # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html> 1695 self._sysfont = tkFont.Font(font=Tkinter.Button()["font"]) 1696 root.option_add("*Font", self._sysfont) 1697 1698 # TWhat's our font size (default=same as sysfont) 1699 self._size = Tkinter.IntVar(root) 1700 self._size.set(self._sysfont.cget('size')) 1701 1702 self._boldfont = tkFont.Font(family='helvetica', weight='bold', 1703 size=self._size.get()) 1704 self._font = tkFont.Font(family='helvetica', 1705 size=self._size.get())
1706
1707 - def _init_animation(self):
1708 # Are we stepping? (default=yes) 1709 self._step = Tkinter.IntVar(self._root) 1710 self._step.set(1) 1711 1712 # What's our animation speed (default=fast) 1713 self._animate = Tkinter.IntVar(self._root) 1714 self._animate.set(3) # Default speed = fast 1715 1716 # Are we currently animating? 1717 self._animating = 0
1718
1719 - def _init_chartview(self, parent):
1720 self._cv = ChartView(self._chart, parent, 1721 draw_tree=1, draw_sentence=1) 1722 self._cv.add_callback('select', self._click_cv_edge)
1723
1724 - def _init_rulelabel(self, parent):
1725 ruletxt = 'Last edge generated by:' 1726 1727 self._rulelabel1 = Tkinter.Label(parent,text=ruletxt, 1728 font=self._boldfont) 1729 self._rulelabel2 = Tkinter.Label(parent, width=40, 1730 relief='groove', anchor='w', 1731 font=self._boldfont) 1732 self._rulelabel1.pack(side='left') 1733 self._rulelabel2.pack(side='left') 1734 step = Tkinter.Checkbutton(parent, variable=self._step, 1735 text='Step') 1736 step.pack(side='right')
1737
1738 - def _init_buttons(self, parent):
1739 frame1 = Tkinter.Frame(parent) 1740 frame2 = Tkinter.Frame(parent) 1741 frame1.pack(side='bottom', fill='x') 1742 frame2.pack(side='top', fill='none') 1743 1744 Tkinter.Button(frame1, text='Reset\nParser', 1745 background='#90c0d0', foreground='black', 1746 command=self.reset).pack(side='right') 1747 #Tkinter.Button(frame1, text='Pause', 1748 # background='#90c0d0', foreground='black', 1749 # command=self.pause).pack(side='left') 1750 1751 Tkinter.Button(frame1, text='Top Down\nStrategy', 1752 background='#90c0d0', foreground='black', 1753 command=self.top_down_strategy).pack(side='left') 1754 Tkinter.Button(frame1, text='Bottom Up\nStrategy', 1755 background='#90c0d0', foreground='black', 1756 command=self.bottom_up_strategy).pack(side='left') 1757 Tkinter.Button(frame1, text='Bottom Up\nLeft-Corner Strategy', 1758 background='#90c0d0', foreground='black', 1759 command=self.bottom_up_leftcorner_strategy).pack(side='left') 1760 1761 Tkinter.Button(frame2, text='Top Down Init\nRule', 1762 background='#90f090', foreground='black', 1763 command=self.top_down_init).pack(side='left') 1764 Tkinter.Button(frame2, text='Top Down Predict\nRule', 1765 background='#90f090', foreground='black', 1766 command=self.top_down_predict).pack(side='left') 1767 Tkinter.Frame(frame2, width=20).pack(side='left') 1768 1769 Tkinter.Button(frame2, text='Bottom Up Predict\nRule', 1770 background='#90f090', foreground='black', 1771 command=self.bottom_up).pack(side='left') 1772 Tkinter.Frame(frame2, width=20).pack(side='left') 1773 1774 Tkinter.Button(frame2, text='Bottom Up Left-Corner\nPredict Rule', 1775 background='#90f090', foreground='black', 1776 command=self.bottom_up_leftcorner).pack(side='left') 1777 Tkinter.Frame(frame2, width=20).pack(side='left') 1778 1779 Tkinter.Button(frame2, text='Fundamental\nRule', 1780 background='#90f090', foreground='black', 1781 command=self.fundamental).pack(side='left')
1782
1783 - def _init_bindings(self):
1784 self._root.bind('<Up>', self._cv.scroll_up) 1785 self._root.bind('<Down>', self._cv.scroll_down) 1786 self._root.bind('<Prior>', self._cv.page_up) 1787 self._root.bind('<Next>', self._cv.page_down) 1788 self._root.bind('<Control-q>', self.destroy) 1789 self._root.bind('<Control-x>', self.destroy) 1790 self._root.bind('<F1>', self.help) 1791 1792 self._root.bind('<Control-s>', self.save_chart) 1793 self._root.bind('<Control-o>', self.load_chart) 1794 self._root.bind('<Control-r>', self.reset) 1795 1796 self._root.bind('t', self.top_down_strategy) 1797 self._root.bind('b', self.bottom_up_strategy) 1798 self._root.bind('c', self.bottom_up_leftcorner_strategy) 1799 self._root.bind('<space>', self._stop_animation) 1800 1801 self._root.bind('<Control-g>', self.edit_grammar) 1802 self._root.bind('<Control-t>', self.edit_sentence) 1803 1804 # Animation speed control 1805 self._root.bind('-', lambda e,a=self._animate:a.set(1)) 1806 self._root.bind('=', lambda e,a=self._animate:a.set(2)) 1807 self._root.bind('+', lambda e,a=self._animate:a.set(3)) 1808 1809 # Step control 1810 self._root.bind('s', lambda e,s=self._step:s.set(not s.get()))
1811
1812 - def _init_menubar(self):
1813 menubar = Tkinter.Menu(self._root) 1814 1815 filemenu = Tkinter.Menu(menubar, tearoff=0) 1816 filemenu.add_command(label='Save Chart', underline=0, 1817 command=self.save_chart, accelerator='Ctrl-s') 1818 filemenu.add_command(label='Load Chart', underline=0, 1819 command=self.load_chart, accelerator='Ctrl-o') 1820 filemenu.add_command(label='Reset Chart', underline=0, 1821 command=self.reset, accelerator='Ctrl-r') 1822 filemenu.add_separator() 1823 filemenu.add_command(label='Save Grammar', 1824 command=self.save_grammar) 1825 filemenu.add_command(label='Load Grammar', 1826 command=self.load_grammar) 1827 filemenu.add_separator() 1828 filemenu.add_command(label='Exit', underline=1, 1829 command=self.destroy, accelerator='Ctrl-x') 1830 menubar.add_cascade(label='File', underline=0, menu=filemenu) 1831 1832 editmenu = Tkinter.Menu(menubar, tearoff=0) 1833 editmenu.add_command(label='Edit Grammar', underline=5, 1834 command=self.edit_grammar, 1835 accelerator='Ctrl-g') 1836 editmenu.add_command(label='Edit Text', underline=5, 1837 command=self.edit_sentence, 1838 accelerator='Ctrl-t') 1839 menubar.add_cascade(label='Edit', underline=0, menu=editmenu) 1840 1841 viewmenu = Tkinter.Menu(menubar, tearoff=0) 1842 viewmenu.add_command(label='Chart Matrix', underline=6, 1843 command=self.view_matrix) 1844 viewmenu.add_command(label='Results', underline=0, 1845 command=self.view_results) 1846 menubar.add_cascade(label='View', underline=0, menu=viewmenu) 1847 1848 rulemenu = Tkinter.Menu(menubar, tearoff=0) 1849 rulemenu.add_command(label='Top Down Strategy', underline=0, 1850 command=self.top_down_strategy, 1851 accelerator='t') 1852 rulemenu.add_command(label='Bottom Up Strategy', underline=0, 1853 command=self.bottom_up_strategy, 1854 accelerator='b') 1855 rulemenu.add_command(label='Bottom Up Left-Corner Strategy', underline=0, 1856 command=self.bottom_up_leftcorner_strategy, 1857 accelerator='c') 1858 rulemenu.add_separator() 1859 rulemenu.add_command(label='Bottom Up Rule', 1860 command=self.bottom_up) 1861 rulemenu.add_command(label='Bottom Up Left-Corner Rule', 1862 command=self.bottom_up_leftcorner) 1863 rulemenu.add_command(label='Top Down Init Rule', 1864 command=self.top_down_init) 1865 rulemenu.add_command(label='Top Down Predict Rule', 1866 command=self.top_down_predict) 1867 rulemenu.add_command(label='Fundamental Rule', 1868 command=self.fundamental) 1869 menubar.add_cascade(label='Apply', underline=0, menu=rulemenu) 1870 1871 animatemenu = Tkinter.Menu(menubar, tearoff=0) 1872 animatemenu.add_checkbutton(label="Step", underline=0, 1873 variable=self._step, 1874 accelerator='s') 1875 animatemenu.add_separator() 1876 animatemenu.add_radiobutton(label="No Animation", underline=0, 1877 variable=self._animate, value=0) 1878 animatemenu.add_radiobutton(label="Slow Animation", underline=0, 1879 variable=self._animate, value=1, 1880 accelerator='-') 1881 animatemenu.add_radiobutton(label="Normal Animation", underline=0, 1882 variable=self._animate, value=2, 1883 accelerator='=') 1884 animatemenu.add_radiobutton(label="Fast Animation", underline=0, 1885 variable=self._animate, value=3, 1886 accelerator='+') 1887 menubar.add_cascade(label="Animate", underline=1, menu=animatemenu) 1888 1889 zoommenu = Tkinter.Menu(menubar, tearoff=0) 1890 zoommenu.add_radiobutton(label='Tiny', variable=self._size, 1891 underline=0, value=10, command=self.resize) 1892 zoommenu.add_radiobutton(label='Small', variable=self._size, 1893 underline=0, value=12, command=self.resize) 1894 zoommenu.add_radiobutton(label='Medium', variable=self._size, 1895 underline=0, value=14, command=self.resize) 1896 zoommenu.add_radiobutton(label='Large', variable=self._size, 1897 underline=0, value=18, command=self.resize) 1898 zoommenu.add_radiobutton(label='Huge', variable=self._size, 1899 underline=0, value=24, command=self.resize) 1900 menubar.add_cascade(label='Zoom', underline=0, menu=zoommenu) 1901 1902 helpmenu = Tkinter.Menu(menubar, tearoff=0) 1903 helpmenu.add_command(label='About', underline=0, 1904 command=self.about) 1905 helpmenu.add_command(label='Instructions', underline=0, 1906 command=self.help, accelerator='F1') 1907 menubar.add_cascade(label='Help', underline=0, menu=helpmenu) 1908 1909 self._root.config(menu=menubar)
1910 1911 #//////////////////////////////////////////////////////////// 1912 # Selection Handling 1913 #//////////////////////////////////////////////////////////// 1914
1915 - def _click_cv_edge(self, edge):
1916 if edge != self._selection: 1917 # Clicking on a new edge selects it. 1918 self._select_edge(edge) 1919 else: 1920 # Repeated clicks on one edge cycle its trees. 1921 self._cv.cycle_tree()
1922 # [XX] this can get confused if animation is running 1923 # faster than the callbacks... 1924
1925 - def _select_matrix_edge(self, edge):
1926 self._select_edge(edge) 1927 self._cv.view_edge(edge)
1928
1929 - def _select_edge(self, edge):
1930 self._selection = edge 1931 # Update the chart view. 1932 self._cv.markonly_edge(edge, '#f00') 1933 self._cv.draw_tree(edge) 1934 # Update the matrix view. 1935 if self._matrix: self._matrix.markonly_edge(edge) 1936 if self._matrix: self._matrix.view_edge(edge)
1937
1938 - def _deselect_edge(self):
1939 self._selection = None 1940 # Update the chart view. 1941 self._cv.unmark_edge() 1942 self._cv.erase_tree() 1943 # Update the matrix view 1944 if self._matrix: self._matrix.unmark_edge()
1945
1946 - def _show_new_edge(self, edge):
1947 self._display_rule(self._cp.current_chartrule()) 1948 # Update the chart view. 1949 self._cv.update() 1950 self._cv.draw_tree(edge) 1951 self._cv.markonly_edge(edge, '#0df') 1952 self._cv.view_edge(edge) 1953 # Update the matrix view. 1954 if self._matrix: self._matrix.update() 1955 if self._matrix: self._matrix.markonly_edge(edge) 1956 if self._matrix: self._matrix.view_edge(edge) 1957 # Update the results view. 1958 if self._results: self._results.update(edge)
1959 1960 #//////////////////////////////////////////////////////////// 1961 # Help/usage 1962 #//////////////////////////////////////////////////////////// 1963
1964 - def help(self, *e):
1965 self._animating = 0 1966 # The default font's not very legible; try using 'fixed' instead. 1967 try: 1968 ShowText(self._root, 'Help: Chart Parser Application', 1969 (__doc__).strip(), width=75, font='fixed') 1970 except: 1971 ShowText(self._root, 'Help: Chart Parser Application', 1972 (__doc__).strip(), width=75)
1973
1974 - def about(self, *e):
1975 ABOUT = ("NLTK Chart Parser Application\n"+ 1976 "Written by Edward Loper") 1977 tkMessageBox.showinfo('About: Chart Parser Application', ABOUT)
1978 1979 #//////////////////////////////////////////////////////////// 1980 # File Menu 1981 #//////////////////////////////////////////////////////////// 1982 1983 CHART_FILE_TYPES = [('Pickle file', '.pickle'), 1984 ('All files', '*')] 1985 GRAMMAR_FILE_TYPES = [('Plaintext grammar file', '.cfg'), 1986 ('Pickle file', '.pickle'), 1987 ('All files', '*')] 1988
1989 - def load_chart(self, *args):
1990 "Load a chart from a pickle file" 1991 filename = askopenfilename(filetypes=self.CHART_FILE_TYPES, 1992 defaultextension='.pickle') 1993 if not filename: return 1994 try: 1995 chart = pickle.load(open(filename, 'r')) 1996 self._chart = chart 1997 self._cv.update(chart) 1998 if self._matrix: self._matrix.set_chart(chart) 1999 if self._matrix: self._matrix.deselect_cell() 2000 if self._results: self._results.set_chart(chart) 2001 self._cp.set_chart(chart) 2002 except Exception, e: 2003 raise 2004 tkMessageBox.showerror('Error Loading Chart', 2005 'Unable to open file: %r' % filename)
2006
2007 - def save_chart(self, *args):
2008 "Save a chart to a pickle file" 2009 filename = asksaveasfilename(filetypes=self.CHART_FILE_TYPES, 2010 defaultextension='.pickle') 2011 if not filename: return 2012 try: 2013 pickle.dump(self._chart, open(filename, 'w')) 2014 except Exception, e: 2015 raise 2016 tkMessageBox.showerror('Error Saving Chart', 2017 'Unable to open file: %r' % filename)
2018
2019 - def load_grammar(self, *args):
2020 "Load a grammar from a pickle file" 2021 filename = askopenfilename(filetypes=self.GRAMMAR_FILE_TYPES, 2022 defaultextension='.cfg') 2023 if not filename: return 2024 try: 2025 if filename.endswith('.pickle'): 2026 grammar = pickle.load(open(filename, 'r')) 2027 else: 2028 grammar = parse_cfg(open(filename, 'r').read()) 2029 self.set_grammar(grammar) 2030 except Exception, e: 2031 tkMessageBox.showerror('Error Loading Grammar', 2032 'Unable to open file: %r' % filename)
2033
2034 - def save_grammar(self, *args):
2035 filename = asksaveasfilename(filetypes=self.GRAMMAR_FILE_TYPES, 2036 defaultextension='.cfg') 2037 if not filename: return 2038 try: 2039 if filename.endswith('.pickle'): 2040 pickle.dump((self._chart, self._tokens), open(filename, 'w')) 2041 else: 2042 file = open(filename, 'w') 2043 prods = self._grammar.productions() 2044 start = [p for p in prods if p.lhs() == self._grammar.start()] 2045 rest = [p for p in prods if p.lhs() != self._grammar.start()] 2046 for prod in start: file.write('%s\n' % prod) 2047 for prod in rest: file.write('%s\n' % prod) 2048 file.close() 2049 except Exception, e: 2050 tkMessageBox.showerror('Error Saving Grammar', 2051 'Unable to open file: %r' % filename)
2052
2053 - def reset(self, *args):
2054 self._animating = 0 2055 self._reset_parser() 2056 self._cv.update(self._chart) 2057 if self._matrix: self._matrix.set_chart(self._chart) 2058 if self._matrix: self._matrix.deselect_cell() 2059 if self._results: self._results.set_chart(self._chart)
2060 2061 #//////////////////////////////////////////////////////////// 2062 # Edit 2063 #//////////////////////////////////////////////////////////// 2064
2065 - def edit_grammar(self, *e):
2066 CFGEditor(self._root, self._grammar, self.set_grammar)
2067
2068 - def set_grammar(self, grammar):
2069 self._grammar = grammar 2070 self._cp.set_grammar(grammar) 2071 if self._results: self._results.set_grammar(grammar)
2072
2073 - def edit_sentence(self, *e):
2074 sentence = string.join(self._tokens) 2075 title = 'Edit Text' 2076 instr = 'Enter a new sentence to parse.' 2077 EntryDialog(self._root, sentence, instr, self.set_sentence, title)
2078
2079 - def set_sentence(self, sentence):
2080 self._tokens = list(sentence.split()) 2081 self.reset()
2082 2083 #//////////////////////////////////////////////////////////// 2084 # View Menu 2085 #//////////////////////////////////////////////////////////// 2086
2087 - def view_matrix(self, *e):
2088 if self._matrix is not None: self._matrix.destroy() 2089 self._matrix = ChartMatrixView(self._root, self._chart) 2090 self._matrix.add_callback('select', self._select_matrix_edge)
2091
2092 - def view_results(self, *e):
2093 if self._results is not None: self._results.destroy() 2094 self._results = ChartResultsView(self._root, self._chart, 2095 self._grammar)
2096 2097 #//////////////////////////////////////////////////////////// 2098 # Zoom Menu 2099 #//////////////////////////////////////////////////////////// 2100
2101 - def resize(self):
2102 self._animating = 0 2103 self.set_font_size(self._size.get())
2104
2105 - def set_font_size(self, size):
2106 self._cv.set_font_size(size) 2107 self._font.configure(size=-abs(size)) 2108 self._boldfont.configure(size=-abs(size)) 2109 self._sysfont.configure(size=-abs(size))
2110
2111 - def get_font_size(self):
2112 return abs(self._size.get())
2113 2114 #//////////////////////////////////////////////////////////// 2115 # Parsing 2116 #//////////////////////////////////////////////////////////// 2117
2118 - def apply_strategy(self, strategy, edge_strategy=None):
2119 # If we're animating, then stop. 2120 if self._animating: 2121 self._animating = 0 2122 return 2123 2124 # Clear the rule display & mark. 2125 self._display_rule(None) 2126 #self._cv.unmark_edge() 2127 2128 if self._step.get(): 2129 selection = self._selection 2130 if (selection is not None) and (edge_strategy is not None): 2131 # Apply the given strategy to the selected edge. 2132 self._cp.set_strategy([edge_strategy(selection)]) 2133 newedge = self._apply_strategy() 2134 2135 # If it failed, then clear the selection. 2136 if newedge is None: 2137 self._cv.unmark_edge() 2138 self._selection = None 2139 else: 2140 self._cp.set_strategy(strategy) 2141 self._apply_strategy() 2142 2143 else: 2144 self._cp.set_strategy(strategy) 2145 if self._animate.get(): 2146 self._animating = 1 2147 self._animate_strategy() 2148 else: 2149 for edge in self._cpstep: 2150 if edge is None: break 2151 self._cv.update() 2152 if self._matrix: self._matrix.update() 2153 if self._results: self._results.update()
2154
2155 - def _stop_animation(self, *e):
2156 self._animating = 0
2157
2158 - def _animate_strategy(self, speed=1):
2159 if self._animating == 0: return 2160 if self._apply_strategy() is not None: 2161 if self._animate.get() == 0 or self._step.get() == 1: 2162 return 2163 if self._animate.get() == 1: 2164 self._root.after(3000, self._animate_strategy) 2165 elif self._animate.get() == 2: 2166 self._root.after(1000, self._animate_strategy) 2167 else: 2168 self._root.after(20, self._animate_strategy)
2169
2170 - def _apply_strategy(self):
2171