Package topo :: Package tkgui :: Module plotgrouppanel
[hide private]
[frames] | no frames]

Source Code for Module topo.tkgui.plotgrouppanel

  1  """ 
  2  Classes providing GUI windows for PlotGroups, allowing sets of related plots 
  3  to be displayed. 
  4   
  5  $Id: plotgrouppanel.py 11310 2010-07-27 16:56:14Z ceball $ 
  6  """ 
  7  __version__='$Revision: 11310 $' 
  8   
  9   
 10  import copy 
 11   
 12  import ImageTk 
 13   
 14  import Tkinter 
 15  from Tkinter import  Frame, TOP, YES, BOTH, X, LEFT, \ 
 16       RIGHT, DISABLED, NORMAL, Canvas, Label, NSEW, \ 
 17       NO, NONE,TclError 
 18   
 19  import param 
 20  # CEB: maybe bad, since people reading code will probably assume tk is Tkinter 
 21  from param import tk 
 22   
 23  from topo.base.sheet import Sheet 
 24  from topo.base.cf import CFSheet 
 25   
 26  from topo.command.pylabplot import matrixplot 
 27   
 28  from topo.misc.generatorsheet import GeneratorSheet 
 29   
 30  import topo 
31 32 -def with_busy_cursor(fn):
33 """ 34 Decorator to show busy cursor for duration of fn call. 35 """ 36 def busy_fn(widget,*args,**kw): 37 if 'cursor' in widget.configure(): 38 old_cursor=widget['cursor'] 39 widget['cursor']='watch' 40 widget.update_idletasks() 41 42 try: 43 fn(widget,*args,**kw) 44 finally: 45 # ensure old cursor is replaced even if fn() raises an 46 # error 47 if 'cursor' in widget.configure(): 48 widget['cursor']=old_cursor
49 50 return busy_fn 51
52 53 -class PlotGroupPanel(tk.TkParameterized,Frame):
54 55 __abstract = True 56 57 58 dock = param.Boolean(default=False,doc="on console or not") 59 60 # Default size for images used on buttons 61 button_image_size=(20,20) 62 63 Refresh = tk.Button(image_path="tkgui/icons/redo-small.png", 64 size=button_image_size, 65 doc=""" 66 Refresh the current plot (i.e. force the current plot to be regenerated 67 by executing pre_plot_hooks and plot_hooks).""") 68 69 Redraw = tk.Button(image_path="tkgui/icons/redo-small.png", 70 size=button_image_size, 71 doc="""Redraw the plot from existing data (i.e. execute plot_hooks only).""") 72 73 Enlarge = tk.Button(image_path="tkgui/icons/viewmag+_2.2.png", 74 size=button_image_size, 75 doc="""Increase the displayed size of the current plots by about 20%.""") 76 77 Reduce = tk.Button(image_path="tkgui/icons/viewmag-_2.1.png", 78 size=button_image_size,doc=""" 79 Reduce the displayed size of the current plots by about 20%. 80 A minimum size that preserves at least one pixel per unit is 81 enforced, to ensure that no data is lost when displaying.""") 82 83 Fwd = tk.Button(image_path="tkgui/icons/forward-2.0.png", 84 size=button_image_size,doc=""" 85 Move forward through the history of all the plots shown in this window.""") 86 87 Back = tk.Button(image_path="tkgui/icons/back-2.0.png", 88 size=button_image_size,doc=""" 89 Move backward through the history of all the plots shown in 90 this window. When showing a historical plot, some functions 91 will be disabled, because the original data is no longer 92 available.""") 93 94 gui_desired_maximum_plot_height = param.Integer(default=150,bounds=(0,None),doc=""" 95 Value to provide for PlotGroup.desired_maximum_plot_height for 96 PlotGroups opened by the GUI. Determines the initial, default 97 scaling for the PlotGroup.""") 98 99 # CB: is there a better way than using a property?
100 - def get_plotgroup(self):
101 return self._extraPO
102 - def set_plotgroup(self,new_pg):
103 self.change_PO(new_pg)
104 105 plotgroup = property(get_plotgroup,set_plotgroup) 106 107
108 - def __init__(self,master,plotgroup,**params):
109 """ 110 If your parameter should be available in history, add its name 111 to the params_in_history list, otherwise it will be disabled 112 in historical views. 113 """ 114 115 tk.TkParameterized.__init__(self,master,extraPO=plotgroup, 116 msg_handler=master.status, 117 **params) 118 Frame.__init__(self,master.content) 119 120 self.parent = master #CEBALERT 121 self.setup_plotgroup() 122 123 124 125 126 127 self.canvases = [] 128 self.plot_labels = [] 129 130 ### JCALERT! Figure out why we need that! 131 self._num_labels = 0 132 133 self.plotgroups_history=[] 134 self.history_index = 0 135 self.params_in_history = [] # parameters valid to adjust in history 136 137 # Factor for reducing or enlarging the Plots (where 1.2 = 20% change) 138 self.zoom_factor = 1.2 139 140 # CEBALERT: rename these frames 141 self.control_frame_1 = Frame(master.noscroll) 142 self.control_frame_1.pack(side=TOP,expand=NO,fill=X) 143 144 self.control_frame_2 = Frame(master.noscroll) 145 self.control_frame_2.pack(side=TOP,expand=NO,fill=X) 146 147 self.plot_frame = Tkinter.LabelFrame(self,text=self.plotgroup.name) 148 self.plot_frame.pack(side=TOP,expand=YES,fill=BOTH)#,padx=5,pady=5) 149 150 # CB: why did I need a new frame after switching to 8.5? 151 # I've forgotten what i changed. 152 self.plot_container = Tkinter.Frame(self.plot_frame) 153 self.plot_container.pack(anchor="center") 154 155 156 # Label does have a wraplength option...but it's in screen 157 # units. Surely tk has a function to convert between 158 # text and screen units? 159 no_plot_note_text = """ 160 Press Refresh on the pre-plot hooks to generate the plot, after modifying the hooks below if necessary. Note that Refreshing may take some time. 161 162 Many hooks accept 'display=True' so that the progress can be viewed in an open Activity window, e.g. for debugging. 163 """ 164 165 self.no_plot_note=Label(self.plot_container,text=no_plot_note_text, 166 justify="center",wraplength=350) 167 self.no_plot_note_enabled=False 168 169 170 self.control_frame_3 = Frame(master.noscroll_bottom) 171 self.control_frame_3.pack(side=TOP,expand=NO,fill=X) 172 173 self.control_frame_4 = Frame(self) 174 self.control_frame_4.pack(side=TOP,expand=NO,fill=NONE) 175 176 self.updatecommand_frame = Frame(self.control_frame_3) 177 self.updatecommand_frame.pack(side=TOP,expand=YES,fill=X) 178 179 self.plotcommand_frame = Frame(self.control_frame_3) 180 self.plotcommand_frame.pack(side=TOP,expand=YES,fill=X) 181 182 183 # CEBALERT: replace 184 self.messageBar = self.parent.status 185 186 self.pack_param('pre_plot_hooks',parent=self.updatecommand_frame, 187 expand='yes',fill='x',side='left') 188 189 self.pack_param('Refresh',parent=self.updatecommand_frame, 190 on_set=self.refresh,side='right') 191 self.params_in_history.append('Refresh') 192 193 self.pack_param('plot_hooks',parent=self.plotcommand_frame, 194 expand='yes',fill='x',side='left') 195 # CEBALERT: should disable unless data exists. 196 self.pack_param('Redraw',parent=self.plotcommand_frame, 197 on_set=self.redraw_plots,side='right') 198 199 200 self.pack_param('Enlarge',parent=self.control_frame_1, 201 on_set=self.enlarge_plots,side=LEFT) 202 self.params_in_history.append('Enlarge') # CEBNOTE: while it's a GUI op 203 204 self.pack_param('Reduce',parent=self.control_frame_1, 205 on_set=self.reduce_plots,side=LEFT) 206 self.params_in_history.append('Reduce') 207 208 209 if topo.tkgui.TK_SUPPORTS_DOCK: 210 self.pack_param("dock",parent=self.control_frame_1, 211 on_set=self.set_dock,side=LEFT) 212 213 214 # Don't need to add these two to params_in_history because their 215 # availability is controlled separately (determined by what's 216 # in the history) 217 self.pack_param('Back',parent=self.control_frame_2, 218 on_set=lambda x=-1: self.navigate_pg_history(x), 219 side=LEFT) 220 221 self.pack_param('Fwd',parent=self.control_frame_2, 222 on_set=lambda x=+1: self.navigate_pg_history(x), 223 side=LEFT) 224 225 226 227 228 #################### RIGHT-CLICK MENU STUFF #################### 229 ### Right-click menu for canvases; subclasses can add cascades 230 ### or insert commands on the existing cascades. 231 self._canvas_menu = tk.Menu(self, tearoff=0) #self.context_menu 232 233 self._unit_menu = tk.Menu(self._canvas_menu, tearoff=0) 234 self._canvas_menu.add_cascade(menu=self._unit_menu,state=DISABLED, 235 indexname='unit_menu') 236 237 self._canvas_menu.add_separator() 238 239 # CEBALERT: scheme for enabling/disabling menu items ('disable 240 # items hack') needs to be generalized. What we have now is 241 # just a mechanism to disable/enable cfs/rfs plots as 242 # necessary. Hack includes the attribute below as well as 243 # other items marked 'disable items hack'. 244 # (Note that tk 8.5 has better handling of state switching 245 # (using flags for each state, I think), so presumably this 246 # can be cleaned up easily.) 247 self._unit_menu_updaters = {} 248 249 self._sheet_menu = tk.Menu(self._canvas_menu, tearoff=0) 250 self._canvas_menu.add_cascade(menu=self._sheet_menu,state=DISABLED, 251 indexname='sheet_menu') 252 self._canvas_menu.add_separator() 253 254 255 self.update_plot_frame(plots=False)
256 257 ################################################################# 258 259 # CB: don't forget to include ctrl-q 260 # import __main__; __main__.__dict__['qqq']=self 261 262
263 - def set_dock(self):
264 if self.dock: 265 topo.guimain.some_area.consume(self.parent) 266 self.refresh_title() 267 else: 268 topo.guimain.some_area.eject(self.parent) 269 self.refresh_title()
270 271 272
273 - def setup_plotgroup(self):
274 """ 275 Perform any necessary initialization of the plotgroup. 276 277 Subclasses can use this to set Parameters on their PlotGroups. 278 """ 279 self.plotgroup.desired_maximum_plot_height=self.gui_desired_maximum_plot_height
280 281
282 - def __process_canvas_event(self,event,func):
283 """ 284 Return a dictionary containing the event itself, and, if the 285 event occurs on a plot of a sheet, store the plot and the 286 coordinates ((r,c) and (x,y) for the cell center) on the sheet. 287 288 Then, call func. 289 """ 290 # CB: I want this to be called for all the canvas events - see 291 # ALERT by canvas button bindings. Surely can do better than 292 # just passing func through. 293 plot=event.widget.plot 294 event_info = {'event':event} # store event in case more info needed elsewhere 295 296 # Later functions assume that if event_info does not contain 297 # 'plot', then the event did not occur on a plot of a sheet. 298 if plot.plot_src_name is not '': 299 plot_width,plot_height=plot.bitmap.width(),plot.bitmap.height() 300 if 0<=event.x<plot_width and 0<=event.y<plot_height: 301 left,bottom,right,top=plot.plot_bounding_box.lbrt() 302 # float() to avoid integer division 303 x = (right-left)*float(event.x)/plot_width + left 304 y = top - (top-bottom)*float(event.y)/plot_height 305 r,c = topo.sim[plot.plot_src_name].sheet2matrixidx(x,y) 306 event_info['plot'] = plot 307 event_info['coords'] = [(r,c),(x,y)] 308 309 func(event_info)
310 311
312 - def _canvas_right_click(self,event_info,show_menu=True):
313 """ 314 Update labels on right-click menu and popup the menu, plus store the event info 315 for access by any menu commands that require it. 316 317 If show_menu is False, popup menu is not displayed (in case subclasses 318 wish to add extra menu items first). 319 """ 320 if 'plot' in event_info: 321 plot = event_info['plot'] 322 323 self._canvas_menu.entryconfig("sheet_menu", 324 label="Combined plot: %s %s"%(plot.plot_src_name,plot.name), 325 state=NORMAL) 326 (r,c),(x,y) = event_info['coords'] 327 sheet = topo.sim[plot.plot_src_name] 328 self._canvas_menu.entryconfig("unit_menu", 329 label="Single unit:(% 3d,% 3d) Coord:(% 2.2f,% 2.2f)"%(r,c,x,y), 330 state=NORMAL) 331 self._right_click_info = event_info 332 333 # CB: part of disable items hack 334 for v in self._unit_menu_updaters.values(): v(plot) 335 336 if show_menu: 337 self._canvas_menu.tk_popup(event_info['event'].x_root, 338 event_info['event'].y_root)
339 340 341 342
343 - def _update_dynamic_info(self,event_info):
344 """ 345 Update dynamic information. 346 """ 347 if 'plot' in event_info: 348 plot = event_info['plot'] 349 (r,c),(x,y) = event_info['coords'] 350 location_string="%s Unit:(% 3d,% 3d) Coord:(% 2.2f,% 2.2f)"%(plot.plot_src_name,r,c,x,y) 351 # CB: isn't there a nicer way to allow more info to be added? 352 self.messageBar.dynamicinfo(self._dynamic_info_string(event_info,location_string)) 353 else: 354 self.messageBar.dynamicinfo('')
355 356 357 358
359 - def _dynamic_info_string(self,event_info,x):
360 """ 361 Subclasses can override to add extra relevant information. 362 """ 363 return x
364 365 366 # rename (not specific to plot_frame) 367 # document, and make display_* methods semi-private methods
368 - def update_plot_frame(self,plots=True,labels=True,geom=False):
369 """ 370 371 set geom True for any action that user would expect to lose 372 his/her manual window size (e.g. pressing enlarge button) 373 """ 374 375 if plots: 376 self.plotgroup.scale_images() 377 self.display_plots() 378 if labels: self.display_labels() 379 self.refresh_title() 380 381 if len(self.canvases)==0: 382 # CEB: check that pack's ok here 383 self.no_plot_note.grid(row=1,column=0,sticky='nsew') 384 self.no_plot_note_enabled=True 385 self.representations['Enlarge']['widget']['state']=DISABLED 386 self.representations['Reduce' ]['widget']['state']=DISABLED 387 388 elif self.no_plot_note_enabled: 389 self.no_plot_note.grid_forget() 390 self.no_plot_note_enabled=False 391 self.representations['Enlarge']['widget']['state']=NORMAL 392 self.representations['Reduce' ]['widget']['state']=NORMAL 393 394 self.__update_widgets_for_history() 395 # have a general update_widgets method instead (that calls 396 # update_widgets_for_history; can it also include 397 # enlarge/reduce alterations?) 398 399 # CBALERT: problem when docked: this event isn't being caught, 400 # ie it doesn't end up going to the right place... (i.e. no 401 # scrollbars when docked). 402 #self.event_generate("<<SizeRight>>") 403 self.parent.sizeright() 404 if geom: 405 try: 406 self.parent.geometry('') 407 except TclError: 408 pass
409 410 411 @with_busy_cursor
412 - def refresh_plots(self):
413 """ 414 Call plotgroup's make_plots with update=True (i.e. run 415 pre_plot_hooks and plot_hooks), then display the result. 416 """ 417 self.plotgroup.make_plots(update=True) 418 self.update_plot_frame() 419 self.add_to_plotgroups_history()
420 421 422 @with_busy_cursor
423 - def redraw_plots(self):
424 """ 425 Call plotgroup's make_plots with update=False (i.e. run only 426 plot_hooks, not pre_plot_hooks), then display the result. 427 """ 428 self.plotgroup.make_plots(update=False) 429 self.update_plot_frame(labels=False)
430 431
432 - def rescale_plots(self):
433 """ 434 Rescale the existing plots, without calling either the 435 plot_hooks or the pre_plot_hooks, then display the result. 436 """ 437 self.plotgroup.scale_images() 438 self.update_plot_frame(labels=False,geom=True)
439 440
441 - def refresh(self,update=True):
442 """ 443 Main steps for generating plots in the Frame. 444 445 # if update is True, the SheetViews are re-generated 446 """ 447 448 # if we've been looking in the history, now need to return to the "current time" 449 # plotgroup (but copy it: don't update the old one, which is a record of the previous state) 450 if self.history_index!=0: 451 self._switch_plotgroup(copy.copy(self.plotgroups_history[-1])) 452 self.history_index = 0 453 454 if update: 455 self.refresh_plots() 456 else: 457 self.redraw_plots()
458 459 460 ### JABALERT: Can we make it simpler to make plots be put onto multiple lines? 461 # (because this is just the smallest change I cpuld think of to 462 # support row precedence, without altering the existing code!)
463 - def _determine_layout_of_plots(self,plots):
464 """Calculate self._rows and self._cols, together giving the grid position of each plot.""" 465 distinct_precedences = sorted(set([p.row_precedence for p in plots])) 466 467 # 2*i because labels will occupy odd rows 468 precedence2row = dict([ (precedence,2*i) 469 for precedence,i in zip(distinct_precedences, 470 range(len(distinct_precedences)))]) 471 # CB: a 2d array might have been clearer... 472 self._rows = [precedence2row[p.row_precedence] for p in plots] 473 self._cols = [] 474 475 row_counts = dict([(row,0) for row in self._rows]) 476 for row in self._rows: 477 self._cols.append(row_counts[row]) 478 row_counts[row]+=1
479 480 481 # CEBALERT: this method needs cleaning, along with its versions in subclasses.
482 - def display_plots(self):
483 """ 484 485 This function should be redefined in subclasses for interesting 486 things such as 2D grids. 487 """ 488 plots = self.plotgroup.plots 489 self._determine_layout_of_plots(plots) 490 491 self.zoomed_images = [ImageTk.PhotoImage(p.bitmap.image) for p in plots] 492 493 new_sizes = [(str(zi.width()), 494 str(zi.height())) 495 for zi in self.zoomed_images] 496 old_sizes = [(canvas['width'],canvas['height']) 497 for canvas in self.canvases] 498 499 # If the number of canvases or their sizes has changed, then 500 # create a new set of canvases. If the new images will fit into the 501 # old canvases, reuse them (prevents flicker) 502 503 504 505 if len(self.zoomed_images) != len(self.canvases) or \ 506 new_sizes != old_sizes: 507 # Need new canvases... 508 old_canvases = self.canvases 509 self.canvases = [Canvas(self.plot_container, 510 width=image.width(), 511 height=image.height(), 512 borderwidth=1,highlightthickness=0, 513 relief='groove') 514 for image in self.zoomed_images] 515 for i,image,canvas in zip(range(len(self.zoomed_images)), 516 self.zoomed_images,self.canvases): 517 canvas.create_image(1,1,anchor="nw",image=image) 518 canvas.grid(row=self._rows[i],column=self._cols[i],padx=5) 519 520 521 for c in old_canvases: 522 c.grid_forget() 523 524 525 else: 526 # Don't need new canvases... 527 for i,image,canvas in zip(range(len(self.zoomed_images)), 528 self.zoomed_images,self.canvases): 529 canvas.create_image(1,1,anchor="nw",image=image) 530 canvas.grid(row=self._rows[i],column=self._cols[i],padx=5) 531 532 self._add_canvas_bindings()
533 534
535 - def _add_canvas_bindings(self):
536 ### plotting over; bind events to each canvas 537 for plot,canvas in zip(self.plotgroup.plots,self.canvases): 538 # Store the corresponding plot with each canvas so that the 539 # plot information (e.g. scale_factor) will be available 540 # for the right_click menu. 541 canvas.plot=plot 542 # CEBALERT: I want process_canvas_event to be called for 543 # all of these bindings, with an additional method also 544 # called to do something specific to the action. I'm sure 545 # python has something that lets this be done in a clearer 546 # way. 547 canvas.bind('<<right-click>>',lambda event: \ 548 self.__process_canvas_event(event,self._canvas_right_click)) 549 canvas.bind('<Motion>',lambda event: \ 550 self.__process_canvas_event(event,self._update_dynamic_info)) 551 552 canvas.bind('<Leave>',lambda event: \ 553 self.__process_canvas_event(event,self._update_dynamic_info)) 554 # When user has a menu up, it's often natural to click 555 # elsewhere to make the menu disappear. Need to update the 556 # dynamic information in that case. (Happens on OS X 557 # anyway, but needed on Win and linux.) 558 canvas.bind('<Button-1>',lambda event: \ 559 self.__process_canvas_event(event,self._update_dynamic_info))
560 561 562 563 564 565
566 - def display_labels(self):
567 """ 568 This function should be redefined by subclasses to match any 569 changes made to display__plots(). Depending on the situation, 570 it may be useful to make this function a stub, and display the 571 labels at the same time the images are displayed. 572 """ 573 574 if len(self.canvases) == 0: 575 pass 576 elif self._num_labels != len(self.canvases): 577 old_labels = self.plot_labels 578 self.plot_labels = [Label(self.plot_container,text=each) 579 for each in self.plotgroup.labels] 580 for i in range(len(self.plot_labels)): 581 self.plot_labels[i].grid(row=self._rows[i]+1,column=self._cols[i],sticky=NSEW) 582 for l in old_labels: 583 l.grid_forget() 584 self._num_labels = len(self.canvases) 585 else: # Same number of labels; reuse to avoid flickering. 586 for i in range(len(self.plot_labels)): 587 self.plot_labels[i].configure(text=self.plotgroup.labels[i])
588 589 590 591 # CEBERRORALERT (minor): if no plot's displayed and I click 592 # enlarge, then the enlarge button gets disabled. If I then press 593 # refresh to get a plot, I can't enlarge it because the button's 594 # disabled. Probably need to reset button status if the plots 595 # change.
596 - def reduce_plots(self):
597 """Function called by widget to reduce the plot size, when possible.""" 598 if (not self.plotgroup.scale_images(1.0/self.zoom_factor)): 599 self.representations['Reduce']['widget']['state']=DISABLED 600 self.representations['Enlarge']['widget']['state']=NORMAL 601 self.update_plot_frame(labels=False,geom=True)
602
603 - def enlarge_plots(self):
604 """Function called by widget to increase the plot size, when possible.""" 605 if (not self.plotgroup.scale_images(self.zoom_factor)): 606 self.representations['Enlarge']['widget']['state']=DISABLED 607 self.representations['Reduce']['widget']['state']=NORMAL 608 self.update_plot_frame(labels=False,geom=True)
609 610 611 ###################################################################### 612 ### HISTORY METHODS 613 614 # CEBERRORALERT: history grows and grows! Consider what happens when 615 # a window's open with auto-refresh and many plots are generated 616 # (e.g. measure_rfs). And plotgroups might be much bigger than they 617 # need to be. 618 619 # CEBALERT: in a history research, a disabled widget does not display 620 # up-to-date information (e.g. normalize checkbutton doesn't change).
622 """ 623 If there are plots on display, and we're not doing a history research, 624 the plotgroup is stored in the history. 625 """ 626 if self.history_index==0 and not len(self.canvases)==0: 627 self.plotgroups_history.append(copy.copy(self.plotgroup)) 628 self.__update_widgets_for_history()
629
630 - def __set_widget_state(self,widget,state):
631 # sets the widget's state to state, unless state=='normal' 632 # and the widget's current state is 'readonly', in which 633 # case readonly is preserved. 634 # If a widget state was set to 'disabled' deliberately, this 635 # will have the unwanted effect of enabling that widget. 636 # Surely there's a better way than this! 637 # (Probably the history stuff should store the old state 638 # on the widget somewhere. That would also eliminate the 639 # combobox-specific hack.) 640 641 # CEBALERT: I guess some widgets don't have state? 642 try: 643 current_state = widget.configure('state')[3] 644 except TclError: 645 return 646 647 ### hack to deal with combobox: see tkparameterizedobject's 648 ### create_selector_widget(). 649 if state=='normal': 650 if hasattr(widget,'_readonly_'): 651 state='readonly' 652 ########################################################### 653 654 widget.configure(state=state)
655 656
658 """ 659 The plotgroup's non-history widgets are all irrelevant when the plotgroup's from 660 history. 661 """ 662 if self.history_index!=0: 663 state= 'disabled' 664 else: 665 state = 'normal' 666 667 widgets_to_update = [self.representations[p_name]['widget'] 668 for p_name in self.representations 669 if p_name not in self.params_in_history] 670 671 for widget in widgets_to_update: 672 self.__set_widget_state(widget,state) 673 674 self.__update_history_buttons()
675 676
677 - def __update_history_buttons(self):
678 """ 679 Enable/disable the back and forward buttons depending on 680 where we are in a history research. 681 """ 682 space_back = len(self.plotgroups_history)+self.history_index-1 683 space_fwd = -self.history_index 684 685 back_button = self.representations['Back']['widget'] 686 forward_button = self.representations['Fwd']['widget'] 687 688 if space_back>0: 689 back_button['state']='normal' 690 else: 691 back_button['state']='disabled' 692 693 if space_fwd>0: 694 forward_button['state']='normal' 695 else: 696 forward_button['state']='disabled'
697 698 # JLENHANCEMENT: It would be nice to be able to scroll back through many 699 # iterations. Could put in a box for entering either the iteration 700 # number you want to view, or perhaps how many you want to jump...
701 - def navigate_pg_history(self,steps):
702 self.history_index+=steps 703 self._switch_plotgroup(self.plotgroups_history[len(self.plotgroups_history)+self.history_index-1]) 704 self.update_plot_frame()
705 706 ###################################################################### 707 708
709 - def _switch_plotgroup(self,newpg):
710 """ 711 Switch to a different plotgroup, e.g. one from the history buffer. 712 Preserves some attributes from the current plotgroup that can apply 713 across history, but leaves the others as-is. 714 """ 715 oldpg=self.plotgroup 716 717 newpg.desired_maximum_plot_height=oldpg.desired_maximum_plot_height 718 newpg.sheet_coords=oldpg.sheet_coords 719 newpg.integer_scaling=oldpg.integer_scaling 720 721 self.plotgroup=newpg
722 723 724 ########################################################### 725 726 727
728 - def _plot_title(self):
729 """ 730 Provide a string describing the current set of plots. 731 732 Override in subclasses to provide more information. 733 """ 734 return "%s at time %s"%(self.plotgroup.name,topo.sim.timestr(self.plotgroup.time))
735 736 737 # rename to refresh_titles
738 - def refresh_title(self):
739 """ 740 Set Window title and plot frame's title. 741 """ 742 title = self._plot_title() 743 744 self.plot_frame.configure(text=title) 745 self.parent.title(str(topo.sim.name)+": "+title)
746 # JABALERT: Used to say .replace(" at time ","/"); was there a reason? 747
748 - def destroy(self):
749 """overrides toplevel destroy, adding removal from autorefresh panels""" 750 if self in topo.guimain.auto_refresh_panels: 751 topo.guimain.auto_refresh_panels.remove(self) 752 Frame.destroy(self)
753
754 755 756 -class SheetPanel(PlotGroupPanel):
757 758 sheet_type = Sheet 759 760 @classmethod
761 - def valid_context(cls):
762 """ 763 Return true if there appears to be data available for this type of plot. 764 765 To avoid confusing error messages, this method should be 766 defined to return False in the case where there is no 767 appropriate data to plot. This information can be used to, 768 e.g., gray out the appropriate menu item. 769 By default, PlotPanels are assumed to be valid only for 770 simulations that contain at least one Sheet. Subclasses with 771 more specific requirements should override this method with 772 something more appropriate. 773 """ 774 if topo.sim.objects(cls.sheet_type).items(): 775 return True 776 else: 777 return False
778 779
780 - def __init__(self,master,plotgroup,**params):
781 super(SheetPanel,self).__init__(master,plotgroup,**params) 782 783 self.pack_param('auto_refresh',parent=self.control_frame_1, 784 on_set=self.set_auto_refresh, 785 side=RIGHT) 786 self.params_in_history.append('auto_refresh') 787 788 if self.auto_refresh: 789 topo.guimain.auto_refresh_panels.append(self) 790 791 792 self.pack_param('normalize',parent=self.control_frame_1, 793 on_set=self.redraw_plots,side="right") 794 self.pack_param('integer_scaling',parent=self.control_frame_2, 795 on_set=self.rescale_plots,side='right') 796 self.pack_param('sheet_coords',parent=self.control_frame_2, 797 on_set=self.rescale_plots,side='right') 798 799 self.params_in_history.append('sheet_coords') 800 self.params_in_history.append('integer_scaling') 801 802 803 804 self._unit_menu.add_command(label='Connection Fields',indexname='connection_fields', 805 command=self._connection_fields_window) 806 807 self._unit_menu.add_command(label='Receptive Field', 808 indexname='receptive_field', 809 command=self._receptive_field_window) 810 811 self._unit_menu.add_command(label='Orientation Tuning Curves', 812 indexname='or tuning curve', 813 command=self._or_tuning_curve_window) 814 815 ###### part of disable items hack ##### 816 self._unit_menu_updaters['connection_fields'] = self.check_for_cfs 817 self._unit_menu_updaters['receptive_field'] = self.check_for_rfs
818
819 - def check_for_cfs(self,plot):
820 show_cfs = False 821 if plot.plot_src_name in topo.sim.objects(): 822 if isinstance(topo.sim[plot.plot_src_name],CFSheet): 823 show_cfs = True 824 self.__showhide("connection_fields",show_cfs)
825
826 - def check_for_rfs(self,plot):
827 show_rfs = False 828 if plot.plot_src_name in topo.sim.objects(): 829 sheet = topo.sim[plot.plot_src_name] 830 831 # RFHACK: if any one generator has RF views for this sheet, then enable the menu option 832 # At the moment, just a hack to prevent menu option for generator sheets. 833 if not isinstance(sheet,GeneratorSheet): 834 show_rfs = True 835 else: 836 show_rfs = False 837 838 self.__showhide("receptive_field",show_rfs)
839
840 - def __showhide(self,name,show):
841 if show: 842 state = 'normal' 843 else: 844 state = 'disabled' 845 self._unit_menu.entryconfig(name,state=state)
846 ####################################### 847 848
849 - def set_auto_refresh(self):
850 """ 851 Add or remove this panel from the console's 852 auto_refresh_panels list. 853 """ 854 if self.auto_refresh: 855 if not (self in topo.guimain.auto_refresh_panels): 856 topo.guimain.auto_refresh_panels.append(self) 857 else: 858 if self in topo.guimain.auto_refresh_panels: 859 topo.guimain.auto_refresh_panels.remove(self)
860 861 862
864 """ 865 Open a Connection Fields plot for the unit currently 866 identified by a right click. 867 """ 868 if 'plot' in self._right_click_info: 869 sheet = topo.sim[self._right_click_info['plot'].plot_src_name] 870 # CEBERRORALERT: should avoid requesting cf out of range. 871 center_x,center_y = self._right_click_info['coords'][1] 872 topo.guimain['Plots']["Connection Fields"](x=center_x,y=center_y,sheet=sheet)
873 874
875 - def _receptive_field_window(self):
876 """ 877 Open a Receptive Field plot for the unit currently 878 identified by a right click. 879 """ 880 if 'plot' in self._right_click_info: 881 plot = self._right_click_info['plot'] 882 sheet = topo.sim[plot.plot_src_name] 883 center_x,center_y=self._right_click_info['coords'][1] 884 r,c = self._right_click_info['coords'][0] 885 886 # RFHACK: 887 # just matrixplot for whatever generators have the views 888 for g in topo.sim.objects(GeneratorSheet).values(): 889 try: 890 view=g.sheet_views[('RFs',sheet.name,center_x,center_y)] 891 matrixplot(view.view()[0], 892 title=("Receptive Field of %s unit (%d,%d) at coord (%3.0f, %3.0f) at time %s" % 893 (sheet.name,r,c,center_x,center_y,topo.sim.timestr(view.timestamp)))) 894 895 except KeyError: 896 # maybe lose this warning 897 topo.sim.warning("No RF measurements are available yet for input_sheet %s; run the Receptive Field plot for that input_sheet to see the RF."%g.name)
898 899
900 - def _or_tuning_curve_window(self):
901 """ 902 Open a Tuning Curve plot for the unit currently 903 identified by a right click. 904 """ 905 if 'plot' in self._right_click_info: 906 plot = self._right_click_info['plot'] 907 sheet = topo.sim[plot.plot_src_name] 908 center_x,center_y=self._right_click_info['coords'][1] 909 r,c = self._right_click_info['coords'][0] 910 911 try: 912 from topo.command.pylabplot import cyclic_tuning_curve 913 cyclic_tuning_curve(x_axis="orientation",coords=[(center_x,center_y)],sheet=sheet) 914 except AttributeError: 915 topo.sim.warning("No orientation tuning curve measurements are available yet for sheet %s; run the Orientation Tuning (Fullfield) command and try again."%sheet.name)
916 917
918 - def conditional_refresh(self):
919 """ 920 Only calls refresh() if auto_refresh is enabled. 921 """ 922 if self.auto_refresh:self.refresh()
923
924 - def conditional_redraw(self):
925 """ 926 Only calls redraw_plots() if auto_refresh is enabled. 927 """ 928 if self.auto_refresh:self.redraw_plots()
929