source: trunk/python/customgui_base.py@ 3037

Last change on this file since 3037 was 2799, checked in by Kana Sugimoto, 12 years ago

New Development: No (a bug fix)

JIRA Issue: Yes (CAS-4859)

Ready for Test: Yes

Interface Changes: No

What Interface Changed:

Test Programs:

Put in Release Notes: No

Module(s): asap.asapplotter and sdplot

Description:

Fixed a bug in the usage of weakref.


File size: 49.1 KB
RevLine 
[2155]1import os
[2796]2import weakref
[2155]3import matplotlib, numpy
4from asap.logging import asaplog, asaplog_post_dec
5from matplotlib.patches import Rectangle
6from asap.parameters import rcParams
[2170]7from asap import scantable
[2155]8from asap._asap import stmath
[2172]9from asap.utils import _n_bools, mask_not, mask_or
[2155]10
11######################################
12## Add CASA custom toolbar ##
13######################################
14class CustomToolbarCommon:
15 def __init__(self,parent):
[2796]16 self.plotter = weakref.ref(parent)
[2155]17 #self.figmgr=self.plotter._plotter.figmgr
18
[2799]19 def _get_plotter(self):
20 # check for the validity of the plotter and
21 # return the plotter class instance if its valid.
22 if self.plotter() is None:
23 raise RuntimeError, "Internal Error. The plotter has been destroyed."
24 else:
25 return self.plotter()
26
[2155]27 ### select the nearest spectrum in pick radius
28 ### and display spectral value on the toolbar.
29 def _select_spectrum(self,event):
30 # Do not fire event when in zooming/panning mode
31 mode = self.figmgr.toolbar.mode
32 if not mode == '':
33 return
34 # When selected point is out of panels
35 if event.inaxes == None:
36 return
37 # If not left button
38 if event.button != 1:
39 return
40
[2799]41 # check for the validity of plotter and get the plotter
42 theplotter = self._get_plotter()
43
[2155]44 xclick = event.xdata
45 yclick = event.ydata
46 dist2 = 1000.
47 pickline = None
48 # If the pannel has picable objects
49 pflag = False
50 for lin in event.inaxes.lines:
51 if not lin.pickable():
52 continue
53 pflag = True
54 flag,pind = lin.contains(event)
55 if not flag:
56 continue
57 # Get nearest point
58 inds = pind['ind']
59 xlin = lin.get_xdata()
60 ylin = lin.get_ydata()
61 for i in inds:
62 d2=(xlin[i]-xclick)**2+(ylin[i]-yclick)**2
63 if dist2 >= d2:
64 dist2 = d2
65 pickline = lin
66 # No pickcable line in the pannel
67 if not pflag:
68 return
69 # Pickable but too far from mouse position
70 elif pickline is None:
71 picked = 'No line selected.'
72 self.figmgr.toolbar.set_message(picked)
73 return
74 del pind, inds, xlin, ylin
75 # Spectra are Picked
[2799]76 theplot = theplotter._plotter
[2155]77 thetoolbar = self.figmgr.toolbar
78 thecanvas = self.figmgr.canvas
79 # Disconnect the default motion notify event
80 # Notice! the other buttons are also diabled!!!
81 thecanvas.mpl_disconnect(thetoolbar._idDrag)
82 # Get picked spectrum
83 xdata = pickline.get_xdata()
84 ydata = pickline.get_ydata()
85 titl = pickline.get_label()
86 titp = event.inaxes.title.get_text()
87 panel0 = event.inaxes
88 picked = "Selected: '"+titl+"' in panel '"+titp+"'."
89 thetoolbar.set_message(picked)
90 # Generate a navigation window
91 #naviwin=Navigationwindow(titp,titl)
92 #------------------------------------------------------#
93 # Show spectrum data at mouse position
94 def spec_data(event):
95 # Getting spectrum data of neiboring point
96 xclick = event.xdata
97 if event.inaxes != panel0:
98 return
99 ipoint = len(xdata)-1
100 for i in range(len(xdata)-1):
101 xl = xclick - xdata[i]
102 xr = xclick - xdata[i+1]
103 if xl*xr <= 0.:
104 ipoint = i
105 break
106 # Output spectral value on the navigation window
107 posi = '[ %s, %s ]: x = %.2f value = %.2f'\
108 %(titl,titp,xdata[ipoint],ydata[ipoint])
109 #naviwin.posi.set(posi)
110 thetoolbar.set_message(posi)
111 #------------------------------------------------------#
112 # Disconnect from mouse events
113 def discon(event):
114 #naviwin.window.destroy()
115 theplot.register('motion_notify',None)
116 # Re-activate the default motion_notify_event
117 thetoolbar._idDrag = thecanvas.mpl_connect('motion_notify_event',
118 thetoolbar.mouse_move)
119 theplot.register('button_release',None)
120 return
121 #------------------------------------------------------#
122 # Show data value along with mouse movement
123 theplot.register('motion_notify',spec_data)
124 # Finish events when mouse button is released
125 theplot.register('button_release',discon)
126
127
[2172]128 ### Notation
[2155]129 def _mod_note(self,event):
130 # Do not fire event when in zooming/panning mode
131 if not self.figmgr.toolbar.mode == '':
132 return
133 if event.button == 1:
134 self.notewin.load_textwindow(event)
135 elif event.button == 3 and self._note_picked(event):
136 self.notewin.load_modmenu(event)
137 return
138
139 def _note_picked(self,event):
140 # just briefly check if any texts are picked
141 for textobj in self.canvas.figure.texts:
142 if textobj.contains(event)[0]:
143 return True
144 for ax in self.canvas.figure.axes:
145 for textobj in ax.texts:
146 if textobj.contains(event)[0]:
147 return True
148 #print "No text picked"
149 return False
150
[2172]151
152 ### Purely plotter based statistics calculation of a selected area.
153 ### No access to scantable
154 def _single_mask(self,event):
155 # Do not fire event when in zooming/panning mode
156 if not self.figmgr.toolbar.mode == '':
157 return
158 # When selected point is out of panels
159 if event.inaxes == None:
160 return
161 if event.button == 1:
162 exclude=False
163 elif event.button == 3:
164 exclude=True
165 else:
166 return
167
[2799]168 # check for the validity of plotter and get the plotter
169 theplotter = self._get_plotter()
170
[2172]171 self._thisregion = {'axes': event.inaxes,'xs': event.x,
172 'worldx': [event.xdata,event.xdata],
173 'invert': exclude}
174 self.xold = event.x
175 self.xdataold = event.xdata
176
[2799]177 theplotter._plotter.register('button_press',None)
178 theplotter._plotter.register('motion_notify', self._xspan_draw)
179 theplotter._plotter.register('button_press', self._xspan_end)
[2172]180
181 def _xspan_draw(self,event):
182 if event.inaxes == self._thisregion['axes']:
183 xnow = event.x
184 self.xold = xnow
185 self.xdataold = event.xdata
186 else:
187 xnow = self.xold
188 try: self.lastspan
189 except AttributeError: pass
190 else:
191 if self.lastspan: self._remove_span(self.lastspan)
192
[2324]193 self.lastspan = self._draw_span(self._thisregion['axes'],
194 self._thisregion['xs'], xnow, fill="")
[2172]195 del xnow
196
197 def _draw_span(self,axes,x0,x1,**kwargs):
198 pass
199
200 def _remove_span(self,span):
201 pass
202
203 @asaplog_post_dec
204 def _xspan_end(self,event):
205 if not self.figmgr.toolbar.mode == '':
206 return
207 #if event.button != 1:
208 # return
209
210 try: self.lastspan
211 except AttributeError: pass
212 else:
213 self._remove_span(self.lastspan)
214 del self.lastspan
215 if event.inaxes == self._thisregion['axes']:
216 xdataend = event.xdata
217 else:
218 xdataend = self.xdataold
219
[2799]220 # check for the validity of plotter and get the plotter
221 theplotter = self._get_plotter()
222
[2172]223 self._thisregion['worldx'][1] = xdataend
224 # print statistics of spectra in subplot
225 self._subplot_stats(self._thisregion)
226
227 # release event
[2799]228 theplotter._plotter.register('button_press',None)
229 theplotter._plotter.register('motion_notify',None)
[2172]230 # Clear up region selection
231 self._thisregion = None
232 self.xdataold = None
233 self.xold = None
234 # finally recover region selection event
[2799]235 theplotter._plotter.register('button_press',self._single_mask)
[2172]236
237 def _subplot_stats(self,selection):
238 statstr = ['max', 'min', 'median', 'mean', 'sum', 'std'] #'rms']
239 panelstr = selection['axes'].title.get_text()
240 ssep = "-"*70
241 asaplog.push(ssep)
242 asaplog.post()
243 for line in selection['axes'].lines:
[2324]244 # Don't include annotations
245 if line.get_label().startswith("_"):
246 continue
[2172]247 label = panelstr + ", "+line.get_label()
248 x = line.get_xdata()
[2324]249 newmsk = None
250 selmsk = self._create_flag_from_array(x,
251 selection['worldx'],
252 selection['invert'])
253 ydat = None
[2172]254 y = line.get_ydata()
[2324]255 if numpy.ma.isMaskedArray(y):
[2172]256 ydat = y.data
257 basemsk = y.mask
258 else:
259 ydat = y
260 basemsk = False
261 if not isinstance(basemsk, bool):
262 # should be ndarray
[2324]263 newmsk = mask_or(selmsk, basemsk)
[2172]264 elif basemsk:
265 # the whole original spectrum is flagged
266 newmsk = basemsk
267 else:
268 # no channel was flagged originally
269 newmsk = selmsk
[2324]270 mdata = numpy.ma.masked_array(ydat, mask=newmsk)
[2172]271 statval = {}
272 for stat in statstr:
[2324]273 # need to get the stat functions from the ma module!!!
274 statval[stat] = getattr(numpy.ma,stat)(mdata)
275 self._print_stats(statval, statstr=statstr, label=label,\
[2172]276 mask=selection['worldx'],\
277 unmask=selection['invert'])
278 asaplog.push(ssep)
279 asaplog.post()
280
281 def _create_flag_from_array(self,x,masklist,invert):
282 # Return True for channels which should be EXCLUDED (flag)
283 if len(masklist) <= 1:
284 asaplog.push()
285 asaplog.post("masklist should be a list of 2 elements")
286 asaplog.push("ERROR")
287 n = len(x)
288 # Base mask: flag out all channels
289 mask = _n_bools(n, True)
290 minval = min(masklist[0:2])
291 maxval = max(masklist[0:2])
292 for i in range(n):
293 if minval <= x[i] <= maxval:
294 mask[i] = False
295 if invert:
296 mask = mask_not(mask)
297 return mask
298
299 @asaplog_post_dec
300 def _print_stats(self,stats,statstr=None,label="",mask=None,unmask=False):
301 if not isinstance(stats,dict) or len(stats) == 0:
302 asaplog.post()
303 asaplog.push("Invalid statistic value")
304 asaplog.post("ERROR")
305 maskstr = "Not available"
306 if mask:
307 masktype = "mask"
308 maskstr = str(mask)
309 if unmask: masktype = "unmask"
310
311 sout = label + ", " + masktype + " = " + maskstr + "\n"
312 statvals = []
313 if not len(statstr):
314 statstr = stats.keys()
315 for key in statstr:
316 sout += key.ljust(10)
317 statvals.append(stats.pop(key))
318 sout += "\n"
319 sout += ("%f "*len(statstr) % tuple(statvals))
320 asaplog.push(sout)
[2324]321 #del sout, maskstr, masktype, statvals, key, stats, statstr, mask, label
[2172]322
323
[2155]324 ### Page chages
325 ### go to the previous page
326 def prev_page(self):
327 self.figmgr.toolbar.set_message('plotting the previous page')
328 #self._pause_buttons(operation="start",msg='plotting the previous page')
329 self._new_page(goback=True)
330 #self._pause_buttons(operation="end")
331
332 ### go to the next page
333 def next_page(self):
334 self.figmgr.toolbar.set_message('plotting the next page')
335 #self._pause_buttons(operation="start",msg='plotting the next page')
336 self._new_page(goback=False)
337 #self._pause_buttons(operation="end")
338
339 ### actual plotting of the new page
340 def _new_page(self,goback=False):
[2799]341 # check for the validity of plotter and get the plotter
342 theplotter = self._get_plotter()
343
[2155]344 top = None
[2799]345 header = theplotter._headtext
[2155]346 reset = False
347 doheader = (isinstance(header['textobj'],list) and \
348 len(header['textobj']) > 0)
[2697]349 if doheader:
[2799]350 top = theplotter._plotter.figure.subplotpars.top
[2697]351 fontsize = header['textobj'][0].get_fontproperties().get_size()
[2799]352 if theplotter._startrow <= 0:
[2155]353 msg = "The page counter is reset due to chages of plot settings. "
354 msg += "Plotting from the first page."
355 asaplog.push(msg)
356 asaplog.post('WARN')
357 reset = True
358 goback = False
359 if doheader:
360 extrastr = selstr = ''
361 if header.has_key('extrastr'):
362 extrastr = header['extrastr']
363 if header.has_key('selstr'):
364 selstr = header['selstr']
[2799]365 theplotter._reset_header()
[2155]366
[2799]367 theplotter._plotter.hold()
[2155]368 if goback:
369 self._set_prevpage_counter()
[2799]370 #theplotter._plotter.clear()
371 theplotter._plot(theplotter._data)
[2697]372 pagenum = self._get_pagenum()
373 self.set_pagecounter(pagenum)
[2155]374 # Plot header information
[2697]375 #if header['textobj']:
376 if doheader and pagenum == 1:
[2799]377 if top and top != theplotter._margins[3]:
[2155]378 # work around for sdplot in CASA. complete checking in future?
[2799]379 theplotter._plotter.figure.subplots_adjust(top=top)
[2155]380 if reset:
[2799]381 theplotter.print_header(plot=True,fontsize=fontsize,selstr=selstr, extrastr=extrastr)
[2155]382 else:
[2799]383 theplotter._header_plot(header['string'],fontsize=fontsize)
384 theplotter._plotter.release()
385 theplotter._plotter.tidy()
386 theplotter._plotter.show(hardrefresh=False)
[2155]387 del top
388
389 ### calculate the panel ID and start row to plot the previous page
390 def _set_prevpage_counter(self):
[2799]391 # check for the validity of plotter and get the plotter
392 theplotter = self._get_plotter()
393
[2155]394 # set row and panel counters to those of the 1st panel of previous page
395 maxpanel = 16
396 # the ID of the last panel in current plot
[2799]397 lastpanel = theplotter._ipanel
[2155]398 # the number of current subplots
[2799]399 currpnum = len(theplotter._plotter.subplots)
[2155]400 # the nuber of previous subplots
401 prevpnum = None
[2799]402 if theplotter._rows and theplotter._cols:
[2155]403 # when user set layout
[2799]404 prevpnum = theplotter._rows*theplotter._cols
[2155]405 else:
406 # no user specification
407 prevpnum = maxpanel
408
409 start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
410 # set the pannel ID of the last panel of prev-prev page
[2799]411 theplotter._ipanel = start_ipanel-1
412 if theplotter._panelling == 'r':
413 theplotter._startrow = start_ipanel
[2155]414 else:
415 # the start row number of the next panel
[2799]416 theplotter._startrow = theplotter._panelrows[start_ipanel]
[2155]417 del lastpanel,currpnum,prevpnum,start_ipanel
418
419 ### refresh the page counter
420 def set_pagecounter(self,page):
421 nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
422 nwidth = max(nwidth,4)
423 formatstr = '%'+str(nwidth)+'d'
424 self.show_pagenum(page,formatstr)
425
426 def show_pagenum(self,pagenum,formatstr):
427 # passed to backend dependent class
428 pass
429
430 def _get_pagenum(self):
[2799]431 # check for the validity of plotter and get the plotter
432 theplotter = self._get_plotter()
433
[2155]434 # get the ID of last panel in the current page
[2799]435 idlastpanel = theplotter._ipanel
[2697]436 # max panels in a page
[2799]437 ppp = theplotter._plotter.rows*theplotter._plotter.cols
[2155]438 return int(idlastpanel/ppp)+1
439
440 # pause buttons for slow operations. implemented at a backend dependent class
441 def _pause_buttons(self,operation="end",msg=""):
442 pass
443
444
445
446
447######################################
448## Notation box window ##
449######################################
450class NotationWindowCommon:
451 """
452 A base class to define the functions that the backend-based
453 GUI notation class must implement to print/modify/delete notes on a canvas.
454
455 The following methods *must* be implemented in the backend-based
456 parent class:
457 _get_note : get text in text box
458 _get_anchval : get anchor value selected
459 """
460 def __init__(self,master=None):
461 #self.parent = master
462 self.canvas = master
463 self.event = None
464 self.note = None
465 self.anchors = ["figure","axes","data"]
466 self.seltext = {}
467 self.numnote = 0
468
469 @asaplog_post_dec
470 def print_text(self):
471 """
472 Print a note on a canvas specified with the Notation window.
473 Called when 'print' button selected on the window.
474 """
475 anchor = self.anchors[self._get_anchval()]
476 notestr = self._get_note().rstrip("\n")
477 if len(notestr.strip()) == 0:
478 #self._clear_textbox()
479 #print "Empty string!"
480 return
481
482 myaxes = None
483 calcpos = True
484 xpos = None
485 ypos = None
486 if self.seltext:
487 # You are modifying a text
488 mycanvas = self.canvas
489 oldanch = self.seltext['anchor']
490 if oldanch != 'figure':
491 myaxes = self.seltext['parent']
492 calcpos = (anchor != oldanch)
493 if not calcpos:
494 # printing text in the same coord.
495 # you don't have to recalc position
496 parent = self.seltext['parent']
497 transform = self.seltext['textobj'].get_transform()
498 (xpos, ypos) = self.seltext['textobj'].get_position()
499 elif anchor == "figure":
500 # converting from "axes"/"data" -> "figure"
501 (x, y) = self.seltext['textobj']._get_xy_display()
502 elif oldanch == "data":
503 # converting from "data" -> "axes".
504 # need xdata & ydata in the axes
505 (x, y) = self.seltext['textobj'].get_position()
506 else:
507 # converting "figure"/"axes" -> "data"
508 # need to calculate xdata & ydata in the axes
509 pixpos = self.seltext['textobj']._get_xy_display()
510 (w,h) = mycanvas.get_width_height()
511 relpos = (pixpos[0]/float(w), pixpos[1]/float(h))
512 if not myaxes:
513 myaxes = self._get_axes_from_pos(relpos,mycanvas)
514 if not myaxes:
515 raise RuntimeError, "Axes resolution failed!"
516 (x, y) = self._convert_pix2dat(relpos,myaxes)
517 self._remove_seltext()
518 elif self.event:
519 mycanvas = self.event.canvas
520 myaxes = self.event.inaxes
521 if myaxes and (anchor != "figure"):
522 x = self.event.xdata
523 y = self.event.ydata
524 else:
525 x = self.event.x
526 y = self.event.y
527 else:
528 raise RuntimeError, "No valid position to print data"
529 return
530
531 # now you know
532 picker = True
533 # alignment of the text: ha (horizontal), va (vertical)
534 ha = 'left'
535 #va = 'center'
536 va = 'top'
537 if not calcpos:
538 # you aready know parent, tansform, xpos and ypos
539 pass
540 elif anchor == "figure":
541 # text instance will be appended to mycanvas.figure.texts
542 parent = mycanvas.figure
543 transform = parent.transFigure
544 (w,h) = mycanvas.get_width_height()
545 xpos = x/float(w)
546 ypos = y/float(h)
547 elif myaxes:
548 ## text instance will be appended to myaxes.texts
549 parent = myaxes
550 if anchor == "axes":
551 transform = myaxes.transAxes
552 lims = myaxes.get_xlim()
553 xpos = (x-lims[0])/(lims[1]-lims[0])
554 lims = myaxes.get_ylim()
555 ypos = (y-lims[0])/(lims[1]-lims[0])
556 else:
557 # anchored on "data"
558 transform = myaxes.transData
559 xpos = x
560 ypos = y
561 parent.text(xpos,ypos,notestr,transform=transform,
562 ha=ha,va=va,picker=picker)
563 mycanvas.draw()
564
565 self.numnote += 1
566
567 #self._clear_textbox()
568 msg = "Added note: '"+notestr+"'"
569 msg += " @["+str(xpos)+", "+str(ypos)+"] ("+anchor+"-coord)"
570 msg += "\ntotal number of notes are "+str(self.numnote)
571 asaplog.push( msg )
572
573 def _get_axes_from_pos(self,pos,canvas):
574 """helper function to get axes of a position in a plot (fig-coord)"""
575 if len(pos) != 2:
576 raise ValueError, "pixel position should have 2 elements"
577 for axes in canvas.figure.axes:
578 ##check if pos is in the axes
579 #if axes.contains_point(pos): ### seems not working
580 # return axes
581 try:
582 axbox = axes.get_position().get_points()
583 except AttributeError: ### WORKAROUND for old matplotlib
584 axbox = self._oldpos2new(axes.get_position())
585 if (axbox[0][0] <= pos[0] <= axbox[1][0]) and \
586 (axbox[0][1] <= pos[1] <= axbox[1][1]):
587 return axes
588 return None
589
590 ### WORKAROUND for old matplotlib
591 def _oldpos2new(self,oldpos=None):
592 return [[oldpos[0],oldpos[1]],[oldpos[0]+oldpos[2],oldpos[1]+oldpos[3]]]
593
594 def _convert_pix2dat(self,pos,axes):
595 """
596 helper function to convert a position in figure-coord (0-1) to
597 data-coordinate of the axes
598 """
599 # convert a relative position from lower-left of the canvas
600 # to a data in axes
601 if len(pos) != 2:
602 raise ValueError, "pixel position should have 2 elements"
603 # left-/bottom-pixel, and pixel width & height of the axes
604 bbox = axes.get_position()
605 try:
606 axpos = bbox.get_points()
607 except AttributeError: ### WORKAROUND for old matplotlib
608 axpos = self._oldpos2new(bbox)
609 # check pos value
610 if (pos[0] < axpos[0][0]) or (pos[1] < axpos[0][1]) \
611 or (pos[0] > axpos[1][0]) or (pos[1] > axpos[1][1]):
612 raise ValueError, "The position is out of the axes"
613 xlims = axes.get_xlim()
614 ylims = axes.get_ylim()
615 wdat = xlims[1] - xlims[0]
616 hdat = ylims[1] - ylims[0]
617 xdat = xlims[0] + wdat*(pos[0] - axpos[0][0])/(axpos[1][0] - axpos[0][0])
618 ydat = ylims[0] + hdat*(pos[1] - axpos[0][1])/(axpos[1][1] - axpos[0][1])
619 return (xdat, ydat)
620
621 @asaplog_post_dec
622 def _get_selected_text(self,event):
623 """helper function to return a dictionary of the nearest note to the event."""
624 (w,h) = event.canvas.get_width_height()
625 dist2 = w*w+h*h
626 selected = {}
627 for textobj in self.canvas.figure.texts:
628 if textobj.contains(event)[0]:
629 d2 = self._get_text_dist2(event,textobj)
630 if dist2 >= d2:
631 dist2 = d2
632 selected = {'anchor': 'figure', \
633 'parent': event.canvas.figure, 'textobj': textobj}
634 msg = "Fig loop: a text, '"+textobj.get_text()+"', at "
635 msg += str(textobj.get_position())+" detected"
636 #print msg
637 for ax in self.canvas.figure.axes:
638 for textobj in ax.texts:
639 if textobj.contains(event)[0]:
640 d2 = self._get_text_dist2(event,textobj)
641 if dist2 >= d2:
642 anchor='axes'
643 if ax.transData == textobj.get_transform():
644 anchor = 'data'
645 selected = {'anchor': anchor, \
646 'parent': ax, 'textobj': textobj}
647 msg = "Ax loop: a text, '"+textobj.get_text()+"', at "
648 msg += str(textobj.get_position())+" detected"
649 #print msg
650
651 if selected:
652 msg = "Selected (modify/delete): '"+selected['textobj'].get_text()
653 msg += "' @"+str(selected['textobj'].get_position())
654 msg += " ("+selected['anchor']+"-coord)"
655 asaplog.push(msg)
656
657 return selected
658
659 def _get_text_dist2(self,event,textobj):
660 """
661 helper function to calculate square of distance between
662 a event position and a text object.
663 """
664 (x,y) = textobj._get_xy_display()
665 return (x-event.x)**2+(y-event.y)**2
666
667 def delete_note(self):
668 """
669 Remove selected note.
670 """
671 #print "You selected 'OK'"
672 self._remove_seltext()
673 self.canvas.draw()
674
675 @asaplog_post_dec
676 def _remove_seltext(self):
677 """helper function to remove the selected note"""
678 if len(self.seltext) < 3:
679 raise ValueError, "Don't under stand selected text obj."
680 return
681 try:
682 self.seltext['textobj'].remove()
683 except NotImplementedError:
684 self.seltext['parent'].texts.pop(self.seltext['parent'].texts.index(self.seltext['textobj']))
685 self.numnote -= 1
686
687 textobj = self.seltext['textobj']
688 msg = "Deleted note: '"+textobj.get_text()+"'"
689 msg += "@"+str(textobj.get_position())\
690 +" ("+self.seltext['anchor']+"-coord)"
691 msg += "\ntotal number of notes are "+str(self.numnote)
692 asaplog.push( msg )
693
694 self.seltext = {}
695
696 @asaplog_post_dec
697 def cancel_delete(self):
698 """
699 Cancel deleting the selected note.
700 Called when 'cancel' button selected on confirmation dialog.
701 """
702 asaplog.push( "Cancel deleting: '"+self.seltext['textobj'].get_text()+"'" )
703 self.seltext = {}
704
705
706
707###########################################
708## Add CASA custom Flag toolbar ##
709###########################################
710class CustomFlagToolbarCommon:
711 def __init__(self,parent):
[2796]712 self.plotter=weakref.ref(parent)
[2155]713 #self.figmgr=self.plotter._plotter.figmgr
714 self._selregions = {}
715 self._selpanels = []
716 self._polygons = []
717 self._thisregion = None
718 self.xdataold=None
719
[2799]720 def _get_plotter(self):
721 # check for the validity of the plotter and
722 # return the plotter class instance if its valid.
723 if self.plotter() is None:
724 raise RuntimeError, "Internal Error. The plotter has been destroyed."
725 else:
726 return self.plotter()
727
[2155]728 ### select the nearest spectrum in pick radius
729 ### and display spectral value on the toolbar.
730 def _select_spectrum(self,event):
731 # Do not fire event when in zooming/panning mode
732 mode = self.figmgr.toolbar.mode
733 if not mode == '':
734 return
735 # When selected point is out of panels
736 if event.inaxes == None:
737 return
738 # If not left button
739 if event.button != 1:
740 return
741
[2799]742 # check for the validity of plotter and get the plotter
743 theplotter = self._get_plotter()
744
[2155]745 xclick = event.xdata
746 yclick = event.ydata
747 dist2 = 1000.
748 pickline = None
749 # If the pannel has picable objects
750 pflag = False
751 for lin in event.inaxes.lines:
752 if not lin.pickable():
753 continue
754 pflag = True
755 flag,pind = lin.contains(event)
756 if not flag:
757 continue
758 # Get nearest point
759 inds = pind['ind']
760 xlin = lin.get_xdata()
761 ylin = lin.get_ydata()
762 for i in inds:
763 d2=(xlin[i]-xclick)**2+(ylin[i]-yclick)**2
764 if dist2 >= d2:
765 dist2 = d2
766 pickline = lin
767 # No pickcable line in the pannel
768 if not pflag:
769 return
770 # Pickable but too far from mouse position
771 elif pickline is None:
772 picked = 'No line selected.'
773 self.figmgr.toolbar.set_message(picked)
774 return
775 del pind, inds, xlin, ylin
776 # Spectra are Picked
[2799]777 theplot = theplotter._plotter
[2155]778 thetoolbar = self.figmgr.toolbar
779 thecanvas = self.figmgr.canvas
780 # Disconnect the default motion notify event
781 # Notice! the other buttons are also diabled!!!
782 thecanvas.mpl_disconnect(thetoolbar._idDrag)
783 # Get picked spectrum
784 xdata = pickline.get_xdata()
785 ydata = pickline.get_ydata()
786 titl = pickline.get_label()
787 titp = event.inaxes.title.get_text()
788 panel0 = event.inaxes
789 picked = "Selected: '"+titl+"' in panel '"+titp+"'."
790 thetoolbar.set_message(picked)
791 # Generate a navigation window
792 #naviwin=Navigationwindow(titp,titl)
793 #------------------------------------------------------#
794 # Show spectrum data at mouse position
795 def spec_data(event):
796 # Getting spectrum data of neiboring point
797 xclick = event.xdata
798 if event.inaxes != panel0:
799 return
800 ipoint = len(xdata)-1
801 for i in range(len(xdata)-1):
802 xl = xclick-xdata[i]
803 xr = xclick-xdata[i+1]
804 if xl*xr <= 0.:
805 ipoint = i
806 break
807 # Output spectral value on the navigation window
808 posi = '[ %s, %s ]: x = %.2f value = %.2f'\
809 %(titl,titp,xdata[ipoint],ydata[ipoint])
810 #naviwin.posi.set(posi)
811 thetoolbar.set_message(posi)
812 #------------------------------------------------------#
813 # Disconnect from mouse events
814 def discon(event):
815 #naviwin.window.destroy()
816 theplot.register('motion_notify',None)
817 # Re-activate the default motion_notify_event
818 thetoolbar._idDrag=thecanvas.mpl_connect('motion_notify_event',
819 thetoolbar.mouse_move)
820 theplot.register('button_release',None)
821 return
822 #------------------------------------------------------#
823 # Show data value along with mouse movement
824 theplot.register('motion_notify',spec_data)
825 # Finish events when mouse button is released
826 theplot.register('button_release',discon)
827
828
829 ### Notation
830 def _mod_note(self,event):
831 # Do not fire event when in zooming/panning mode
832 if not self.figmgr.toolbar.mode == '':
833 return
834 if event.button == 1:
835 self.notewin.load_textwindow(event)
836 elif event.button == 3 and self._note_picked(event):
837 self.notewin.load_modmenu(event)
838 return
839
840 def _note_picked(self,event):
841 # just briefly check if any texts are picked
842 for textobj in self.canvas.figure.texts:
843 if textobj.contains(event)[0]:
844 return True
845 for ax in self.canvas.figure.axes:
846 for textobj in ax.texts:
847 if textobj.contains(event)[0]:
848 return True
849 return False
850
851 ### Region/Panel selection & oparations
852 ### add regions to selections
853 @asaplog_post_dec
854 def _add_region(self,event):
855 if not self.figmgr.toolbar.mode == '':
856 return
857 if event.button != 1 or event.inaxes == None:
858 return
[2799]859 # check for the validity of plotter and get the plotter
860 theplotter = self._get_plotter()
[2155]861 # this row resolution assumes row panelling
862 irow = int(self._getrownum(event.inaxes))
863 if irow in self._selpanels:
864 msg = "The whole spectrum is already selected"
865 asaplog.post()
866 asaplog.push(msg)
867 asaplog.post('WARN')
868 return
869 self._thisregion = {'axes': event.inaxes,'xs': event.x,
870 'worldx': [event.xdata,event.xdata]}
[2799]871 theplotter._plotter.register('button_press',None)
[2171]872 self.xold = event.x
873 self.xdataold = event.xdata
[2799]874 theplotter._plotter.register('motion_notify', self._xspan_draw)
875 theplotter._plotter.register('button_press', self._xspan_end)
[2155]876
877 def _xspan_draw(self,event):
878 if event.inaxes == self._thisregion['axes']:
879 xnow = event.x
[2171]880 self.xold = xnow
881 self.xdataold = event.xdata
[2155]882 else:
[2171]883 xnow = self.xold
[2155]884 try: self.lastspan
885 except AttributeError: pass
[2172]886 else:
887 if self.lastspan: self._remove_span(self.lastspan)
[2155]888
889 #self.lastspan = self._draw_span(self._thisregion['axes'],self._thisregion['xs'],xnow,fill="#555555",stipple="gray50")
890 self.lastspan = self._draw_span(self._thisregion['axes'],self._thisregion['xs'],xnow,fill="")
891 del xnow
892
893 def _draw_span(self,axes,x0,x1,**kwargs):
894 pass
895
896 def _remove_span(self,span):
897 pass
898
899 @asaplog_post_dec
900 def _xspan_end(self,event):
901 if not self.figmgr.toolbar.mode == '':
902 return
903 if event.button != 1:
904 return
905
906 try: self.lastspan
907 except AttributeError: pass
908 else:
909 self._remove_span(self.lastspan)
910 del self.lastspan
911 if event.inaxes == self._thisregion['axes']:
912 xdataend = event.xdata
913 else:
[2172]914 xdataend = self.xdataold
[2155]915
916 self._thisregion['worldx'][1] = xdataend
917 lregion = self._thisregion['worldx']
[2606]918 # WORKAROUND for the issue axvspan started to reset xlim.
919 axlimx = self._thisregion['axes'].get_xlim()
[2155]920 pregion = self._thisregion['axes'].axvspan(lregion[0],lregion[1],
921 facecolor='0.7')
[2606]922 self._thisregion['axes'].set_xlim(axlimx)
923
[2799]924 # check for the validity of plotter and get the plotter
925 theplotter = self._get_plotter()
926
927 theplotter._plotter.canvas.draw()
[2155]928 self._polygons.append(pregion)
929 srow = self._getrownum(self._thisregion['axes'])
930 irow = int(srow)
931 if not self._selregions.has_key(srow):
932 self._selregions[srow] = []
933 self._selregions[srow].append(lregion)
[2172]934 del lregion, pregion, xdataend
[2155]935 sout = "selected region: "+str(self._thisregion['worldx'])+\
936 "(@row "+str(self._getrownum(self._thisregion['axes']))+")"
937 asaplog.push(sout)
938
939 # release event
[2799]940 theplotter._plotter.register('button_press',None)
941 theplotter._plotter.register('motion_notify',None)
[2155]942 # Clear up region selection
943 self._thisregion = None
944 self.xdataold = None
[2172]945 self.xold = None
[2155]946 # finally recover region selection event
[2799]947 theplotter._plotter.register('button_press',self._add_region)
[2155]948
949 ### add panels to selections
950 @asaplog_post_dec
951 def _add_panel(self,event):
952 if not self.figmgr.toolbar.mode == '':
953 return
954 if event.button != 1 or event.inaxes == None:
955 return
[2799]956 # check for the validity of plotter and get the plotter
957 theplotter = self._get_plotter()
958
[2155]959 selax = event.inaxes
960 # this row resolution assumes row panelling
961 srow = self._getrownum(selax)
962 irow = int(srow)
963 if srow:
964 self._selpanels.append(irow)
965 shadow = Rectangle((0,0),1,1,facecolor='0.7',transform=selax.transAxes,visible=True)
966 self._polygons.append(selax.add_patch(shadow))
[2799]967 #theplotter._plotter.show(False)
968 theplotter._plotter.canvas.draw()
[2155]969 asaplog.push("row "+str(irow)+" is selected")
970 ## check for region selection of the spectra and overwrite it.
971 ##!!!! currently disabled for consistency with flag tools !!!!
972 #if self._selregions.has_key(srow):
973 # self._selregions.pop(srow)
974 # msg = "The whole spectrum is selected for row="+srow+". Region selection will be overwritten."
975 # asaplog.push(msg)
976
977 def _getrownum(self,axis):
978 ### returns the row number of selected spectrum as a string ###
979 plabel = axis.get_title()
980 if plabel.startswith("row "):
981 return plabel.strip("row ")
982 return None
983
984 def _any_selection(self):
985 ### returns if users have selected any spectrum or region ###
986 if len(self._selpanels) or len(self._selregions):
987 return True
988 return False
989
990 def _plot_selections(self,regions=None,panels=None):
991 ### mark panels/spectra selections in the page
992 if not self._any_selection() and not (regions or panels):
993 return
994 regions = regions or self._selregions.copy() or {}
995 panels = panels or self._selpanels or []
996 if not isinstance(regions,dict):
997 asaplog.post()
998 asaplog.push("Invalid region specification")
999 asaplog.post('ERROR')
1000 if not isinstance(panels,list):
1001 asaplog.post()
1002 asaplog.push("Invalid panel specification")
1003 asaplog.post('ERROR')
[2799]1004
1005 # check for the validity of plotter and get the plotter
1006 theplotter = self._get_plotter()
1007
1008 strow = self._getrownum(theplotter._plotter.subplots[0]['axes'])
1009 enrow = self._getrownum(theplotter._plotter.subplots[-1]['axes'])
[2155]1010 for irow in range(int(strow),int(enrow)+1):
1011 if regions.has_key(str(irow)):
[2799]1012 ax = theplotter._plotter.subplots[irow - int(strow)]['axes']
[2155]1013 mlist = regions.pop(str(irow))
[2606]1014 # WORKAROUND for the issue axvspan started to reset xlim.
1015 axlimx = ax.get_xlim()
[2155]1016 for i in range(len(mlist)):
1017 self._polygons.append(ax.axvspan(mlist[i][0],mlist[i][1],
1018 facecolor='0.7'))
[2606]1019 ax.set_xlim(axlimx)
1020 del ax,mlist,axlimx
[2155]1021 if irow in panels:
[2799]1022 ax = theplotter._plotter.subplots[irow - int(strow)]['axes']
[2155]1023 shadow = Rectangle((0,0),1,1,facecolor='0.7',
1024 transform=ax.transAxes,visible=True)
1025 self._polygons.append(ax.add_patch(shadow))
1026 del ax,shadow
[2799]1027 theplotter._plotter.canvas.draw()
[2155]1028 del regions,panels,strow,enrow
1029
1030 def _clear_selection_plot(self, refresh=True):
1031 ### clear up polygons which mark selected spectra and regions ###
1032 if len(self._polygons) > 0:
1033 for shadow in self._polygons:
1034 shadow.remove()
[2799]1035 if refresh:
1036 # check for the validity of plotter and get the plotter
1037 theplotter = self._get_plotter()
1038 theplotter._plotter.canvas.draw()
[2155]1039 self._polygons = []
1040
1041 def _clearup_selections(self, refresh=True):
1042 # clear-up selection and polygons
1043 self._selpanels = []
1044 self._selregions = {}
1045 self._clear_selection_plot(refresh=refresh)
1046
1047 ### clear up selections
1048 def cancel_select(self):
1049 self.figmgr.toolbar.set_message('selections canceled')
1050 # clear-up selection and polygons
1051 self._clearup_selections(refresh=True)
1052
1053 ### flag selected spectra/regions
1054 @asaplog_post_dec
1055 def flag(self):
1056 if not self._any_selection():
1057 msg = "No selection to be Flagged"
1058 asaplog.post()
1059 asaplog.push(msg)
1060 asaplog.post('WARN')
1061 return
[2799]1062
[2155]1063 self._pause_buttons(operation="start",msg="Flagging data...")
1064 self._flag_operation(rows=self._selpanels,
1065 regions=self._selregions,unflag=False)
1066 sout = "Flagged:\n"
1067 sout += " rows = "+str(self._selpanels)+"\n"
1068 sout += " regions: "+str(self._selregions)
1069 asaplog.push(sout)
1070 del sout
[2799]1071 # check for the validity of plotter and get the plotter
1072 theplotter = self._get_plotter()
1073
1074 theplotter._ismodified = True
[2155]1075 self._clearup_selections(refresh=False)
1076 self._plot_page(pagemode="current")
1077 self._pause_buttons(operation="end")
1078
1079 ### unflag selected spectra/regions
1080 @asaplog_post_dec
1081 def unflag(self):
1082 if not self._any_selection():
1083 msg = "No selection to be Flagged"
1084 asaplog.push(msg)
1085 asaplog.post('WARN')
1086 return
1087 self._pause_buttons(operation="start",msg="Unflagging data...")
1088 self._flag_operation(rows=self._selpanels,
1089 regions=self._selregions,unflag=True)
1090 sout = "Unflagged:\n"
1091 sout += " rows = "+str(self._selpanels)+"\n"
1092 sout += " regions: "+str(self._selregions)
1093 asaplog.push(sout)
1094 del sout
[2799]1095
1096 # check for the validity of plotter and get the plotter
1097 theplotter = self._get_plotter()
1098 theplotter._ismodified = True
[2155]1099 self._clearup_selections(refresh=False)
1100 self._plot_page(pagemode="current")
1101 self._pause_buttons(operation="end")
1102
1103 ### actual flag operation
1104 @asaplog_post_dec
1105 def _flag_operation(self,rows=None,regions=None,unflag=False):
[2799]1106 # check for the validity of plotter and get the plotter
1107 theplotter = self._get_plotter()
1108
1109 scan = theplotter._data
[2155]1110 if not scan:
1111 asaplog.post()
1112 asaplog.push("Invalid scantable")
1113 asaplog.post("ERROR")
1114 if isinstance(rows,list) and len(rows) > 0:
1115 scan.flag_row(rows=rows,unflag=unflag)
1116 if isinstance(regions,dict) and len(regions) > 0:
1117 for srow, masklist in regions.iteritems():
1118 if not isinstance(masklist,list) or len(masklist) ==0:
1119 msg = "Ignoring invalid region selection for row = "+srow
1120 asaplog.post()
1121 asaplog.push(msg)
1122 asaplog.post("WARN")
1123 continue
1124 irow = int(srow)
1125 mask = scan.create_mask(masklist,invert=False,row=irow)
1126 scan.flag(row=irow,mask=mask,unflag=unflag)
1127 del irow, mask
1128 del srow, masklist
1129 del scan
1130
1131 ### show statistics of selected spectra/regions
1132 @asaplog_post_dec
1133 def stat_cal(self):
1134 if not self._any_selection():
1135 msg = "No selection to be calculated"
1136 asaplog.push(msg)
1137 asaplog.post('WARN')
1138 return
1139 self._selected_stats(rows=self._selpanels,regions=self._selregions)
1140 self._clearup_selections(refresh=True)
1141
1142 @asaplog_post_dec
1143 def _selected_stats(self,rows=None,regions=None):
[2799]1144 # check for the validity of plotter and get the plotter
1145 theplotter = self._get_plotter()
1146
1147 scan = theplotter._data
[2155]1148 if not scan:
1149 asaplog.post()
1150 asaplog.push("Invalid scantable")
1151 asaplog.post("ERROR")
1152 mathobj = stmath( rcParams['insitu'] )
1153 statval = {}
1154 statstr = ['max', 'min', 'mean', 'median', 'sum', 'stddev', 'rms']
[2324]1155 if isinstance(rows, list) and len(rows) > 0:
[2155]1156 for irow in rows:
1157 for stat in statstr:
1158 statval[stat] = mathobj._statsrow(scan,[],stat,irow)[0]
1159 self._print_stats(scan,irow,statval,statstr=statstr)
1160 del irow
1161 if isinstance(regions,dict) and len(regions) > 0:
1162 for srow, masklist in regions.iteritems():
1163 if not isinstance(masklist,list) or len(masklist) ==0:
1164 msg = "Ignoring invalid region selection for row = "+srow
1165 asaplog.post()
1166 asaplog.push(msg)
1167 asaplog.post("WARN")
1168 continue
1169 irow = int(srow)
1170 mask = scan.create_mask(masklist,invert=False,row=irow)
1171 for stat in statstr:
1172 statval[stat] = mathobj._statsrow(scan,mask,stat,irow)[0]
[2324]1173 self._print_stats(scan,irow,statval,statstr=statstr,
1174 mask=masklist)
[2155]1175 del irow, mask
1176 del srow, masklist
1177 del scan, statval, mathobj
1178
1179 @asaplog_post_dec
1180 def _print_stats(self,scan,row,stats,statstr=None,mask=None):
1181 if not isinstance(scan, scantable):
1182 asaplog.post()
1183 asaplog.push("Invalid scantable")
1184 asaplog.post("ERROR")
1185 if row < 0 or row > scan.nrow():
1186 asaplog.post()
1187 asaplog.push("Invalid row number")
1188 asaplog.post("ERROR")
1189 if not isinstance(stats,dict) or len(stats) == 0:
1190 asaplog.post()
1191 asaplog.push("Invalid statistic value")
1192 asaplog.post("ERROR")
1193 maskstr = "All"
1194 if mask:
1195 maskstr = str(mask)
1196 ssep = "-"*70+"\n"
1197 sout = ssep
1198 sout += ("Row=%d Scan=%d IF=%d Pol=%d Time=%s mask=%s" % \
1199 (row, scan.getscan(row), scan.getif(row), scan.getpol(row), scan.get_time(row),maskstr))
1200 sout += "\n"
1201 statvals = []
1202 if not len(statstr):
1203 statstr = stats.keys()
1204 for key in statstr:
1205 sout += key.ljust(10)
1206 statvals.append(stats.pop(key))
1207 sout += "\n"
1208 sout += ("%f "*len(statstr) % tuple(statvals))
1209 sout += "\n"+ssep
1210 asaplog.push(sout)
1211 del sout, ssep, maskstr, statvals, key, scan, row, stats, statstr, mask
1212
1213 ### Page chages
1214 ### go to the previous page
1215 def prev_page(self):
1216 self._pause_buttons(operation="start",msg='plotting the previous page')
1217 self._clear_selection_plot(refresh=False)
1218 self._plot_page(pagemode="prev")
1219 self._plot_selections()
1220 self._pause_buttons(operation="end")
1221
1222 ### go to the next page
1223 def next_page(self):
1224 self._pause_buttons(operation="start",msg='plotting the next page')
1225 self._clear_selection_plot(refresh=False)
1226 self._plot_page(pagemode="next")
1227 self._plot_selections()
1228 self._pause_buttons(operation="end")
1229
1230 ### actual plotting of the new page
1231 def _plot_page(self,pagemode="next"):
[2799]1232 # check for the validity of plotter and get the plotter
1233 theplotter = self._get_plotter()
1234 if theplotter._startrow <= 0:
[2155]1235 msg = "The page counter is reset due to chages of plot settings. "
1236 msg += "Plotting from the first page."
1237 asaplog.post()
1238 asaplog.push(msg)
1239 asaplog.post('WARN')
1240 goback = False
1241
[2799]1242 theplotter._plotter.hold()
1243 #theplotter._plotter.legend(1)
[2155]1244 self._set_plot_counter(pagemode)
[2799]1245 theplotter._plot(theplotter._data)
[2155]1246 self.set_pagecounter(self._get_pagenum())
[2799]1247 theplotter._plotter.release()
1248 theplotter._plotter.tidy()
1249 theplotter._plotter.show(hardrefresh=False)
[2155]1250
1251 ### calculate the panel ID and start row to plot a page
1252 #def _set_prevpage_counter(self):
1253 def _set_plot_counter(self, pagemode):
1254 ## page operation should be either "previous", "current", or "next"
1255 availpage = ["p","c","n"]
1256 pageop = pagemode[0].lower()
1257 if not (pageop in availpage):
1258 asaplog.post()
1259 asaplog.push("Invalid page operation")
1260 asaplog.post("ERROR")
1261 if pageop == "n":
1262 # nothing necessary to plot the next page
1263 return
[2799]1264
1265 # check for the validity of plotter and get the plotter
1266 theplotter = self._get_plotter()
1267
[2155]1268 # set row and panel counters to those of the 1st panel of previous page
[2606]1269 maxpanel = 25
[2155]1270 # the ID of the last panel in current plot
[2799]1271 lastpanel = theplotter._ipanel
[2155]1272 # the number of current subplots
[2799]1273 currpnum = len(theplotter._plotter.subplots)
[2155]1274
1275 # the nuber of previous subplots
1276 start_ipanel = None
1277 if pageop == "c":
1278 start_ipanel = max(lastpanel-currpnum+1, 0)
1279 else:
1280 ## previous page
1281 prevpnum = None
[2799]1282 if theplotter._rows and theplotter._cols:
[2155]1283 # when user set layout
[2799]1284 prevpnum = theplotter._rows*theplotter._cols
[2155]1285 else:
1286 # no user specification
1287 prevpnum = maxpanel
1288 start_ipanel = max(lastpanel-currpnum-prevpnum+1, 0)
1289 del prevpnum
1290
1291 # set the pannel ID of the last panel of the prev(-prev) page
[2799]1292 theplotter._ipanel = start_ipanel-1
1293 if theplotter._panelling == 'r':
1294 theplotter._startrow = start_ipanel
[2155]1295 else:
1296 # the start row number of the next panel
[2799]1297 theplotter._startrow = theplotter._panelrows[start_ipanel]
[2155]1298 del lastpanel,currpnum,start_ipanel
1299
1300 ### refresh the page counter
1301 def set_pagecounter(self,page):
1302 nwidth = int(numpy.ceil(numpy.log10(max(page,1))))+1
1303 nwidth = max(nwidth,4)
1304 formatstr = '%'+str(nwidth)+'d'
1305 self.show_pagenum(page,formatstr)
1306
1307 def show_pagenum(self,pagenum,formatstr):
1308 # passed to backend dependent class
1309 pass
1310
1311 def _get_pagenum(self):
[2799]1312 # check for the validity of plotter and get the plotter
1313 theplotter = self._get_plotter()
[2155]1314 # get the ID of last panel in the current page
[2799]1315 idlastpanel = theplotter._ipanel
[2697]1316 # max panels in a page
[2799]1317 ppp = theplotter._plotter.rows*theplotter._plotter.cols
[2155]1318 return int(idlastpanel/ppp)+1
1319
1320 # pause buttons for slow operations. implemented at a backend dependent class
1321 def _pause_buttons(self,operation="end",msg=""):
1322 pass
Note: See TracBrowser for help on using the repository browser.