1 |
import pygtk |
2 |
pygtk.require('2.0') |
3 |
import gtk |
4 |
import pango |
5 |
import os.path |
6 |
|
7 |
from study import * |
8 |
from unitsdialog import * |
9 |
|
10 |
OBSERVER_EDIT_COLOR = "#008800" |
11 |
OBSERVER_NOEDIT_COLOR = "#000088" |
12 |
OBSERVER_NORMAL_COLOR = "black" |
13 |
OBSERVER_TAINTED_COLOR = "#FFBBBB" |
14 |
OBSERVER_DEAD_COLOR = "#ababab" |
15 |
# This code uses the techniques described in |
16 |
# http://www.daa.com.au/pipermail/pygtk/2006-February/011777.html |
17 |
# http://piman.livejournal.com/361173.html |
18 |
|
19 |
OBSERVER_NUM=0 |
20 |
|
21 |
class ClickableTreeColumn(gtk.TreeViewColumn): |
22 |
def __init__(self, title="", *args, **kwargs): |
23 |
super(ClickableTreeColumn, self).__init__(None, *args, **kwargs) |
24 |
self.label = gtk.Label("%s" % title) |
25 |
self.label.show() |
26 |
self.set_widget(self.label) |
27 |
self.title = title |
28 |
#self.set_sort_column_id(0) |
29 |
#self.set_clickable(True) |
30 |
|
31 |
def do_connect(self): |
32 |
""" Connect the defined 'on_click' method. Note: must be called after |
33 |
this object (ClickableTreeColumn) has been added to the TreeView, |
34 |
eg mytreeview.append_column(col). """ |
35 |
button = self.label.get_ancestor(gtk.Button) |
36 |
h = button.connect("clicked",self.on_click) |
37 |
#button.clicked() |
38 |
|
39 |
def on_click(self,widget,*args): |
40 |
print "RECEIVED EVENT" |
41 |
|
42 |
class ObserverColumn: |
43 |
""" |
44 |
A class to identify the instance that relates to a specify column |
45 |
and the units of measurement and column title, etc. |
46 |
""" |
47 |
def __init__(self,instance,index,name=None,units=None,browser=None,eventdata=None): |
48 |
self.instance = instance |
49 |
self.name = name |
50 |
self.index = index |
51 |
|
52 |
if name==None: |
53 |
if browser == None: |
54 |
name = "UNNAMED" |
55 |
else: |
56 |
name = browser.sim.getInstanceName(instance) |
57 |
|
58 |
if units is None: |
59 |
units = instance.getType().getPreferredUnits() |
60 |
if units is None: |
61 |
units = instance.getType().getDimensions().getDefaultUnits() |
62 |
|
63 |
uname = str(units.getName()) |
64 |
if len(uname) or uname.find("/")!=-1: |
65 |
uname = "["+uname+"]" |
66 |
|
67 |
if uname == "": |
68 |
_title = "%s" % (name) |
69 |
else: |
70 |
_title = "%s / %s" % (name, uname) |
71 |
|
72 |
self.title = _title |
73 |
self.units = units |
74 |
self.uname = uname |
75 |
self.name = name |
76 |
self.eventdata = eventdata |
77 |
|
78 |
def __repr__(self): |
79 |
return "ObserverColumn(name="+self.name+")" |
80 |
|
81 |
def cellvalue(self, column, cell, model, row_iter, user_data=None): |
82 |
_rowobject = model.get_value(row_iter,0) |
83 |
|
84 |
cell.set_property('editable',False) |
85 |
cell.set_property('weight',400) |
86 |
try: |
87 |
if _rowobject.active or _rowobject.dead: |
88 |
_rawval = self.instance.getRealValue() |
89 |
if self.instance.getType().isRefinedSolverVar(): |
90 |
if self.instance.isFixed(): |
91 |
cell.set_property('editable',True) |
92 |
cell.set_property('weight',700) |
93 |
cell.set_property('foreground',OBSERVER_EDIT_COLOR) |
94 |
else: |
95 |
cell.set_property('foreground',OBSERVER_NOEDIT_COLOR) |
96 |
_dataval = _rawval / self.units.getConversion() |
97 |
else: |
98 |
cell.set_property('foreground',OBSERVER_NORMAL_COLOR) |
99 |
try : |
100 |
_rawval = _rowobject.values[self.index] |
101 |
_dataval = _rawval / self.units.getConversion() |
102 |
except: |
103 |
_dataval = "" |
104 |
if _rowobject.tainted is True: |
105 |
cell.set_property('background', OBSERVER_TAINTED_COLOR) |
106 |
elif _rowobject.dead or user_data == True: |
107 |
cell.set_property('background', OBSERVER_DEAD_COLOR) |
108 |
cell.set_property('editable', False) |
109 |
else: |
110 |
cell.set_property('background', None) |
111 |
except IndexError: |
112 |
_dataval = "" |
113 |
|
114 |
cell.set_property('text', _dataval) |
115 |
|
116 |
class ObserverRow: |
117 |
""" |
118 |
Just a container for a vector of values, but with columns that |
119 |
should correspond to those in the Observer object's vector of |
120 |
ObserverColumn objects. |
121 |
""" |
122 |
def __init__(self,values=None,active=True,tainted=False): |
123 |
if values==None: |
124 |
values={} |
125 |
self.tainted = tainted |
126 |
self.values = values |
127 |
self.active = active |
128 |
self.dead = False |
129 |
self.error_msg = None |
130 |
|
131 |
def make_static(self,table): |
132 |
self.active = False |
133 |
#print "TABLE COLS:",table.cols |
134 |
#print "ROW VALUES:",self.values |
135 |
_v = {} |
136 |
for col in table.cols.values(): |
137 |
_v[col.index] = col.instance.getRealValue() |
138 |
self.values = _v |
139 |
#print "Made static, values:",self.values |
140 |
|
141 |
def get_values(self,table): |
142 |
vv = {} |
143 |
if not self.active: |
144 |
for k,v in table.cols.iteritems(): |
145 |
try: |
146 |
vv[k]=(self.values[v.index]/v.units.getConversion()) |
147 |
except: |
148 |
vv[k]="" |
149 |
return vv |
150 |
else: |
151 |
for index, col in table.cols.iteritems(): |
152 |
vv[index] = float(col.instance.getRealValue())/col.units.getConversion() |
153 |
return vv |
154 |
|
155 |
class ObserverTab: |
156 |
|
157 |
def __init__(self,browser,tab,name=None,alive=True): |
158 |
global OBSERVER_NUM |
159 |
self.colindex = 0 |
160 |
if name==None: |
161 |
OBSERVER_NUM=OBSERVER_NUM+1 |
162 |
name = "Observer %d" % OBSERVER_NUM |
163 |
self.name = name |
164 |
self.browser=browser |
165 |
self.browser.builder.connect_signals(self) |
166 |
self.view = self.browser.builder.get_object('observerview') |
167 |
self.view.set_has_tooltip(True) |
168 |
self.addbutton = self.browser.builder.get_object('add') |
169 |
self.tab = tab |
170 |
self.alive=alive |
171 |
self.reloaded = False |
172 |
self.old_path = None |
173 |
self.current_instance = None |
174 |
if self.alive: |
175 |
self.browser.reporter.reportNote("New observer is 'alive'") |
176 |
|
177 |
self.keptimg = gtk.Image() |
178 |
self.activeimg = gtk.Image() |
179 |
self.errorimg = gtk.Image() |
180 |
self.activeimg.set_from_file(os.path.join(browser.options.assets_dir,"active.png")) |
181 |
self.errorimg.set_from_file(os.path.join(browser.options.assets_dir,"solveerror.png")) |
182 |
# create PixBuf objects from these? |
183 |
self.rows = [] |
184 |
_store = gtk.TreeStore(object) |
185 |
self.cols = {} |
186 |
self.tvcols = {} |
187 |
self.renderers = {} |
188 |
|
189 |
# create the 'active' pixbuf column |
190 |
_renderer = gtk.CellRendererPixbuf() |
191 |
_col = ClickableTreeColumn("") |
192 |
_col.pack_start(_renderer,False) |
193 |
_col.set_cell_data_func(_renderer, self.activepixbufvalue) |
194 |
self.view.append_column(_col) |
195 |
_col.do_connect() |
196 |
|
197 |
# get the context menu |
198 |
self.treecontext=self.browser.builder.get_object("observercontext") |
199 |
self.studycolumnmenuitem=self.browser.builder.get_object("study_column") |
200 |
self.unitsmenuitem=self.browser.builder.get_object("units2") |
201 |
self.deleterowmenuitem=self.browser.builder.get_object("delete_row") |
202 |
self.deletecolumnmenuitem=self.browser.builder.get_object("delete_column") |
203 |
self.plotmenuitem=self.browser.builder.get_object("plotmenuitem") |
204 |
self.columnplotevents=self.browser.builder.get_object("column_plot_events") |
205 |
# initially there will not be any other columns |
206 |
|
207 |
if self.alive: |
208 |
# for a 'live' Observer, create the 'active' bottom row |
209 |
self.browser.reporter.reportNote("Adding empty row to store") |
210 |
_row = ObserverRow() |
211 |
self.activeiter = _store.append(None, [_row] ) |
212 |
self.rows.append(_row) |
213 |
else: |
214 |
self.activeiter = None |
215 |
|
216 |
self.view.set_model(_store) |
217 |
self.browser.reporter.reportNote("Created observer '%s'" % self.name) |
218 |
|
219 |
_sel = self.view.get_selection() |
220 |
_sel.set_mode(gtk.SELECTION_MULTIPLE) |
221 |
|
222 |
def activepixbufvalue(self,column,cell,model,iter): |
223 |
_rowobject = model.get_value(iter,0) |
224 |
if _rowobject.active: |
225 |
cell.set_property('pixbuf',self.activeimg.get_pixbuf()) |
226 |
elif _rowobject.tainted: |
227 |
cell.set_property('pixbuf',self.errorimg.get_pixbuf()) |
228 |
else: |
229 |
cell.set_property('pixbuf',self.keptimg.get_pixbuf()) |
230 |
|
231 |
def on_add_clicked(self,*args): |
232 |
self.do_add_row() |
233 |
|
234 |
def on_clear_clicked(self,*args): |
235 |
if self.alive is False: |
236 |
self.on_close_observer_clicked() |
237 |
return |
238 |
_store = self.view.get_model() |
239 |
_store.clear(); |
240 |
self.rows = [] |
241 |
_row = ObserverRow() |
242 |
self.activeiter = _store.append(None, [_row] ) |
243 |
self.rows.append(_row) |
244 |
|
245 |
def plot(self,x=None,y=None): |
246 |
"""Create a plot from specified columns in the ObserverTable |
247 |
|
248 |
There will be points for each timestep in the 'samplelist', and also |
249 |
points for each event/boundary-crossing if any occurred. |
250 |
""" |
251 |
import platform |
252 |
import matplotlib |
253 |
matplotlib.use('GTKAgg') |
254 |
import pylab |
255 |
pylab.ioff() |
256 |
|
257 |
# nothing provided: use the first and second columns |
258 |
if x is None or y is None: |
259 |
if len(self.cols)<2: |
260 |
raise Exception("Not enough columns to plot (need 2+)") |
261 |
if x is None: |
262 |
x=self.cols[0] |
263 |
if y is None: |
264 |
y=[self.cols[1]] |
265 |
|
266 |
# if column indices are provided instead of columns, convert them |
267 |
if x.__class__ is int and x>=0 and x<len(self.cols): |
268 |
x=self.cols[x] |
269 |
if y.__class__ is int and y>=0 and y<len(self.cols): |
270 |
y=[self.cols[y]] |
271 |
|
272 |
start = None |
273 |
_p = self.browser.prefs |
274 |
_ignore = _p.getBoolPref("PlotDialog", "ignore_error_points", True) |
275 |
r = {} |
276 |
# FIXME this is not nicely written; we need to collapse the follow if/else |
277 |
# cases into a single bit of code. |
278 |
if _ignore == False: |
279 |
# get all rows, including ones that didn't solve properly |
280 |
for i in range(len(self.rows)): |
281 |
try: |
282 |
r = self.rows[i].get_values(self) |
283 |
# flag if any row are empty -- no data? FIXME why would that happen? |
284 |
flag = True |
285 |
for j in y: |
286 |
if r[j.index]=="": |
287 |
flag = False |
288 |
break |
289 |
if r[x.index]!="" and flag==True: |
290 |
if start == None: |
291 |
start = i |
292 |
break |
293 |
except: |
294 |
continue |
295 |
if start == None: |
296 |
self.browser.reporter.reportError("Can't plot, could not get enough points.") |
297 |
return |
298 |
A = pylab.zeros((len(self.rows)-1-start,len(y)+1),'f') |
299 |
i = 0 |
300 |
j = start |
301 |
while j <len(self.rows)-1: |
302 |
r = self.rows[j].get_values(self) |
303 |
A[i,0]=r[x.index] |
304 |
for k in range(len(y)): |
305 |
A[i,k+1]=r[y[k].index] |
306 |
j+=1 |
307 |
i+=1 |
308 |
else: |
309 |
# ignore error points: get just the non-error rows |
310 |
j = 0 |
311 |
k = 0 |
312 |
l = 0 |
313 |
# count the error-free rows (FIXME: why aren't we just checking the 'tainted' property??) |
314 |
for i in range(len(self.rows)-1): |
315 |
if self.rows[i].tainted is False: |
316 |
try: |
317 |
r = self.rows[i].get_values(self) |
318 |
flag = True |
319 |
for l in y: |
320 |
if r[l.index]=="": |
321 |
flag = False |
322 |
break |
323 |
if r[x.index]!="" and flag == True: |
324 |
if start == None: |
325 |
start = i |
326 |
j=0 |
327 |
j+=1 |
328 |
except: |
329 |
continue |
330 |
if start == None: |
331 |
self.browser.reporter.reportError("Can't plot, could not get enough points.") |
332 |
return |
333 |
A = pylab.zeros((j,len(y)+1),'f') |
334 |
while start<len(self.rows)-1: |
335 |
if self.rows[start].tainted is True: |
336 |
start+=1 |
337 |
continue |
338 |
r = self.rows[start].get_values(self) |
339 |
A[k,0]=r[x.index] |
340 |
for j in range(len(y)): |
341 |
A[k,j+1]=r[y[j].index] |
342 |
k+=1 |
343 |
start+=1 |
344 |
|
345 |
fig = pylab.figure() |
346 |
|
347 |
if len(y) == 2: |
348 |
# two y vectors: use two different y axes on one plot |
349 |
ax1 = pylab.subplot(111) |
350 |
# TODO: second y axis label gets cut off? |
351 |
#pylab.axis('auto') |
352 |
ax1.set_xlabel(x.title) |
353 |
ax1.set_ylabel(y[0].title,labelpad=20) |
354 |
l1 = ax1.plot(A[:,0],A[:,1],'-bo',label=y[0].title) |
355 |
ax2 = ax1.twinx() |
356 |
l2 = ax2.plot(A[:,0],A[:,2],'-ro',label=y[1].title) |
357 |
ax2.set_ylabel(y[1].title,labelpad=20) |
358 |
ax2.yaxis.tick_right() |
359 |
l = l1+l2 |
360 |
labels = [i.get_label() for i in l] |
361 |
leg = ax1.legend(l,labels,loc='upper left') |
362 |
leg.get_frame().set_alpha(0.3) |
363 |
leg.draggable() #doesn't work? |
364 |
#FIXME the events haven't been plotted in this case |
365 |
else : |
366 |
_colcyc = ['r','g','b','y','m','c'] |
367 |
_ncc = len(_colcyc) |
368 |
sharex = None |
369 |
j = 0.83/len(y) |
370 |
for i in range(len(y)): |
371 |
if i == 0: |
372 |
ax = pylab.subplot(len(y),1,i+1) |
373 |
sharex = ax |
374 |
else: |
375 |
ax = pylab.subplot(len(y),1,i+1,sharex=sharex) |
376 |
#ax[i] = fig.add_axes([0.27, 0.08+(i*(j+0.02)), 0.65, j-0.01], **axprops) |
377 |
if y[i].eventdata: |
378 |
# FIXME the 'get_values' function used above does unit conversion |
379 |
# but this 'eventdata' (hack!) doesn't do that, which breaks the plots |
380 |
# when there are non-base-SI units used in the Observer. |
381 |
arrx = [] |
382 |
arry = [] |
383 |
for _vals in y[i].eventdata: |
384 |
arry.append(_vals[1]) |
385 |
for _vals in x.eventdata: |
386 |
arrx.append(_vals[1]) |
387 |
|
388 |
pylab.plot(A[:,0],A[:,i+1],'-',color=_colcyc[i%_ncc],marker='',lw=2,label=y[i].title) |
389 |
pylab.plot(arrx,arry,'s',color='black') |
390 |
else: |
391 |
pylab.plot(A[:,0],A[:,i+1],'-',color=_colcyc[i%_ncc],market='o',label=y[i].title) |
392 |
|
393 |
# put the x-axis label only on the last plot |
394 |
if i+1 != len(y): |
395 |
pylab.setp(ax.get_xticklabels(),visible=False) |
396 |
else: |
397 |
ax.set_xlabel(x.title) |
398 |
|
399 |
# only use a y-axis label if it's a single plot, else put legend on each plot |
400 |
if len(y)==1: |
401 |
pylab.ylabel(y[i].title) |
402 |
else: |
403 |
leg = pylab.legend(loc='upper left') |
404 |
leg.get_frame().set_alpha(0.3) |
405 |
|
406 |
# FIXME why can't I drag the legend? |
407 |
|
408 |
pylab.ion() |
409 |
pylab.show() |
410 |
|
411 |
def on_plot_clicked(self,*args): |
412 |
"""This is the 'plot' button at the top of the observer tab""" |
413 |
try: |
414 |
if len(self.cols)<2: |
415 |
raise Exception("Not enough columns to plot (need 2+)") |
416 |
_plotwin = PlotDialog(self.browser, self) |
417 |
_plot = _plotwin.run() |
418 |
if _plot: |
419 |
self.plot(x=_plotwin.xcol, y=_plotwin.ycol) |
420 |
except Exception,e: |
421 |
self.browser.reporter.reportError(str(e)) |
422 |
|
423 |
def do_add_row(self,values=None): |
424 |
_store = self.view.get_model() |
425 |
if self.alive: |
426 |
_row = ObserverRow() |
427 |
self.rows.append(_row) |
428 |
if self.activeiter is not None: |
429 |
_oldrow = _store.get_value(self.activeiter,0) |
430 |
_oldrow.make_static(self) |
431 |
self.activeiter = _store.append(None,[_row]) |
432 |
_path = _store.get_path(self.activeiter) |
433 |
_oldpath,_oldcol = self.view.get_cursor() |
434 |
self.view.set_cursor(_path, _oldcol) |
435 |
else: |
436 |
_row = ObserverRow(values=values,active=False,tainted=False) |
437 |
self.rows.append(_row) |
438 |
_store.append(None,[_row]) |
439 |
#self.browser.reporter.reportNote("Added data row") |
440 |
|
441 |
def on_view_cell_edited(self, renderer, path, newtext, col): |
442 |
# we can assume it's always the self.activeiter that is edited... |
443 |
if col.instance.isFixed(): |
444 |
val = float(newtext) * col.units.getConversion() |
445 |
col.instance.setRealValue( val ) |
446 |
self.browser.reporter.reportNote("Updated value to %f" % float(newtext)) |
447 |
else: |
448 |
self.browser.reporter.reportError("Can't set a FREE variable from the Observer") |
449 |
return |
450 |
self.browser.do_solve_if_auto() |
451 |
|
452 |
def sync(self): |
453 |
self.view.queue_draw() |
454 |
#self.browser.reporter.reportNote("SYNC performed") |
455 |
|
456 |
def add_instance(self,instance,edata=None): |
457 |
_col = ObserverColumn(instance,self.colindex,browser=self.browser,eventdata=edata) |
458 |
|
459 |
# the loop is to ensure that we dont add multiple columns for the same variable |
460 |
for _cols in self.cols: |
461 |
if self.cols[_cols].title == _col.title: |
462 |
del(_col) |
463 |
return |
464 |
self.cols[self.colindex] = _col |
465 |
self.colindex = self.colindex + 1 |
466 |
|
467 |
# create a new column |
468 |
_renderer = gtk.CellRendererText() |
469 |
_renderer.connect('edited',self.on_view_cell_edited, _col) |
470 |
_tvcol = ClickableTreeColumn(_col.title) |
471 |
_tvcol.pack_start(_renderer,False) |
472 |
self.tvcols[self.colindex-1] = _tvcol |
473 |
self.renderers[self.colindex-1] = _renderer |
474 |
_tvcol.set_cell_data_func(_renderer, _col.cellvalue) |
475 |
self.view.append_column(_tvcol); |
476 |
_tvcol.do_connect() |
477 |
#self.browser.reporter.reportError("cols = "+str(self.cols)) |
478 |
|
479 |
def copy_to_clipboard(self,clip): |
480 |
_s = [] |
481 |
_s.append('\t'.join([_v.title for _k,_v in self.cols.iteritems()])) |
482 |
#_cf = [_v.units.getConversion() for _k,_v in self.cols.iteritems()] |
483 |
print "COPYING %d ROWS" % len(self.rows) |
484 |
#print "CONVERSIONS:",_cf |
485 |
for _r in self.rows: |
486 |
_s.append("\t".join(["%s" % _v for _k, _v in _r.get_values(self).iteritems()])) |
487 |
|
488 |
clip.set_text('\n'.join(_s),-1) |
489 |
|
490 |
self.browser.reporter.reportNote("Observer '%s' data copied to clipboard" % self.name) |
491 |
|
492 |
def on_observerview_query_tooltip(self, widget, _x, _y, keyboard_mode, _tooltip): |
493 |
_store = self.view.get_model() |
494 |
_pthinfo = self.view.get_path_at_pos(_x, _y) |
495 |
|
496 |
if _pthinfo is None: |
497 |
return False |
498 |
_path, _col, _cellx, _celly = _pthinfo |
499 |
_temp, = _path |
500 |
if(_temp==0): |
501 |
return False |
502 |
_path = _temp-1, |
503 |
|
504 |
# the folowing is to ensure that the tooltip |
505 |
# gets refreshed for each row |
506 |
if self.old_path == _path: |
507 |
_iter = _store.get_iter(_path) |
508 |
_rowobject = _store.get_value(_iter,0) |
509 |
if _rowobject.error_msg is None: |
510 |
return False |
511 |
_tooltip.set_text(_rowobject.error_msg) |
512 |
return True |
513 |
else: |
514 |
self.old_path = _path |
515 |
return False |
516 |
|
517 |
def set_all_menu_items_sensitive(self): |
518 |
self.unitsmenuitem.set_sensitive(True) |
519 |
self.studycolumnmenuitem.set_sensitive(True) |
520 |
self.plotmenuitem.set_sensitive(True) |
521 |
self.deletecolumnmenuitem.set_sensitive(True) |
522 |
self.columnplotevents.set_sensitive(True) |
523 |
|
524 |
def on_treeview_event(self,widget,event): |
525 |
|
526 |
_path = None |
527 |
_delete_row = False |
528 |
_contextmenu = False |
529 |
|
530 |
_sel = self.view.get_selection() |
531 |
_model, _rowlist = _sel.get_selected_rows() |
532 |
if event.type==gtk.gdk.KEY_PRESS: |
533 |
_keyval = gtk.gdk.keyval_name(event.keyval) |
534 |
_path, _col = self.view.get_cursor() |
535 |
if _path is not None: |
536 |
if _keyval=='Menu': |
537 |
self.set_all_menu_items_sensitive() |
538 |
_contextmenu = True |
539 |
_button = 3 |
540 |
elif _keyval=='Delete' or _keyval=='BackSpace': |
541 |
_delete_row = True |
542 |
|
543 |
elif event.type==gtk.gdk.BUTTON_PRESS: |
544 |
_x = int(event.x) |
545 |
_y = int(event.y) |
546 |
_button = event.button |
547 |
_pthinfo = self.view.get_path_at_pos(_x, _y) |
548 |
if _pthinfo is not None: |
549 |
_path, _col, _cellx, _celly = _pthinfo |
550 |
if event.button == 3: |
551 |
self.set_all_menu_items_sensitive() |
552 |
_contextmenu = True |
553 |
|
554 |
if not (_contextmenu or _delete_row): |
555 |
#print "NOT DOING ANYTHING ABOUT %s" % gtk.gdk.keyval_name(event.keyval) |
556 |
return |
557 |
|
558 |
if len(_rowlist)>1: |
559 |
self.unitsmenuitem.set_sensitive(False) |
560 |
self.studycolumnmenuitem.set_sensitive(False) |
561 |
self.plotmenuitem.set_sensitive(False) |
562 |
self.deletecolumnmenuitem.set_sensitive(False) |
563 |
if _delete_row: |
564 |
self.on_delete_row() |
565 |
return True |
566 |
self.treecontext.popup( None, None, None, _button, event.time) |
567 |
return |
568 |
|
569 |
self.view.grab_focus() |
570 |
self.view.set_cursor( _path, _col, 0) |
571 |
|
572 |
self.current_path = _path |
573 |
self.current_col = _col |
574 |
self.current_instance = None |
575 |
if _delete_row: |
576 |
self.on_delete_row() |
577 |
elif self.alive is False: |
578 |
self.unitsmenuitem.set_sensitive(False) |
579 |
self.studycolumnmenuitem.set_sensitive(False) |
580 |
self.treecontext.popup( None, None, None, _button, event.time) |
581 |
else: |
582 |
# Since we have the instance data in self.cols and treeview points us to the |
583 |
# ClickableTreeColumn, we need to match the two. |
584 |
for _cols in self.cols: |
585 |
if self.cols[_cols].title == self.current_col.title: |
586 |
self.current_instance = self.cols[_cols].instance |
587 |
self.current_col_key = _cols |
588 |
break |
589 |
if self.current_instance is None: |
590 |
return 0 |
591 |
if self.current_instance.isFixed() == False: |
592 |
self.studycolumnmenuitem.set_sensitive(False) |
593 |
self.treecontext.popup( None, None, None, _button, event.time) |
594 |
return 1 |
595 |
|
596 |
def on_study_column_activate(self, *args): |
597 |
if self.current_instance is not None: |
598 |
_dia = StudyWin(self.browser,self.current_instance) |
599 |
_dia.run() |
600 |
|
601 |
def on_delete_row(self, *args): |
602 |
# We need to remove the row from two places, |
603 |
# the treestore, and our own list of ObserverRows |
604 |
|
605 |
_sel = self.view.get_selection() |
606 |
_store, _rowlist = _sel.get_selected_rows() |
607 |
i = 0 |
608 |
(x,) = _rowlist[0] |
609 |
for _path in _rowlist: |
610 |
if len(self.rows) == x+1: |
611 |
self.browser.reporter.reportWarning("Can't delete the active row") |
612 |
return |
613 |
self.rows.pop(x) |
614 |
_store.remove(_store.get_iter((x,))) |
615 |
|
616 |
# now that we have deleted the row, it is |
617 |
# time to move the cursor to the next available element |
618 |
|
619 |
self.view.grab_focus() |
620 |
self.view.set_cursor((x,), self.current_col, 0) |
621 |
|
622 |
def taint_row(self, msg, _rowobject = None): |
623 |
# to set the solver error message as the row's tooltip |
624 |
_store = self.view.get_model() |
625 |
if _rowobject is None: |
626 |
_rowobject = _store.get_value(self.activeiter,0) |
627 |
_rowobject.error_msg = msg |
628 |
_rowobject.tainted = True |
629 |
|
630 |
def on_delete_column(self, *args): |
631 |
# To delete columns |
632 |
for _col in self.cols: |
633 |
if self.cols[_col].title == self.current_col.title: |
634 |
current_col_key = _col |
635 |
self.cols.pop(current_col_key) |
636 |
self.view.remove_column(self.current_col) |
637 |
|
638 |
def on_column_plot_events(self, *args): |
639 |
for _col in self.cols: |
640 |
if self.cols[_col].title == self.current_col.title: |
641 |
current_col_key = _col |
642 |
_plotwin = ColPlotEventsDialog(self.browser, self) |
643 |
_plot = _plotwin.run() |
644 |
if _plot: |
645 |
self.plot_event(x=self.cols[current_col_key], t=_plotwin.col, title = self.cols[0].title) |
646 |
|
647 |
def plot_event(self, x, t, title): |
648 |
"""Plot internal details of event handling for an individual variable""" |
649 |
import platform |
650 |
import matplotlib |
651 |
matplotlib.use('GTKAgg') |
652 |
import pylab |
653 |
pylab.ioff() |
654 |
|
655 |
vals = [] |
656 |
for x1, x2 in x.eventdata: |
657 |
if x1 == t: |
658 |
vals.append(x2) |
659 |
pylab.xlim(-1,len(vals)) |
660 |
vmin = min(vals) |
661 |
vmax = max(vals) |
662 |
pylab.ylim(vmin-1,vmax+1) |
663 |
pylab.plot(vals,'rD') |
664 |
pylab.suptitle("0 - before crossing the boundary,\n1 - after the first call of the logical solver,\n2 - after solving the boundary equations,\n3+ - added if solving the system changes the values of some boolean vars.",bbox={'facecolor':'green', 'alpha':0.5, 'pad':10}) |
665 |
pylab.xlabel(title + ' = ' + str(t)) |
666 |
pylab.ylabel(x.title) |
667 |
pylab.tight_layout(pad = 7) |
668 |
pylab.ion() |
669 |
pylab.show() |
670 |
|
671 |
def on_plotmenuitem_activate(self, *args): |
672 |
# To preselect the column as y axis |
673 |
try: |
674 |
if len(self.cols)<2: |
675 |
raise Exception("Not enough columns to plot (need 2+)") |
676 |
_plotwin = PlotDialog(self.browser, self) |
677 |
_plotwin.select_ycol(self.cols[self.current_col_key], self) |
678 |
_plot = _plotwin.run() |
679 |
if _plot: |
680 |
self.plot(x=_plotwin.xcol, y=_plotwin.ycol) |
681 |
except Exception,e: |
682 |
self.browser.reporter.reportError(str(e)) |
683 |
|
684 |
def on_close_observer_clicked(self, *args): |
685 |
# First, the dialog box is brought up, and if the user clicks on yes, |
686 |
# the observer tab is closed. Need to make sure that this instance is |
687 |
# removed from the list of observers maintained in the browser |
688 |
|
689 |
_alertwin = CloseDialog(self.browser) |
690 |
destroy = _alertwin.run() |
691 |
if destroy: |
692 |
self.view.destroy() |
693 |
if self.browser.currentobservertab == self.browser.currentpage: |
694 |
self.browser.currentobservertab = None |
695 |
self.browser.maintabs.remove_page(self.browser.currentpage) |
696 |
self.rows = [] |
697 |
self.browser.observers.remove(self) |
698 |
|
699 |
def on_units_activate(self, *args): |
700 |
if self.current_instance is not None: |
701 |
T = self.current_instance.getType() |
702 |
_un = UnitsDialog(self.browser,T) |
703 |
_un.run() |
704 |
|
705 |
def units_refresh(self, instance_type): |
706 |
for _col in self.cols.values(): |
707 |
_units = None |
708 |
_units = instance_type.getPreferredUnits() |
709 |
if _units is None: |
710 |
_units = instance_type.getDimensions().getDefaultUnits() |
711 |
_uname = str(_units.getName()) |
712 |
|
713 |
_col_type = _col.instance.getType() |
714 |
_col_units = _col_type.getPreferredUnits() |
715 |
if _col_units is None: |
716 |
_col_units = _col_type.getDimensions().getDefaultUnits() |
717 |
_col_uname = str(_col_units.getName()) |
718 |
|
719 |
if _col_uname == _uname: |
720 |
if self.browser == None: |
721 |
name = "UNNAMED" |
722 |
else: |
723 |
name = self.browser.sim.getInstanceName(_col.instance) |
724 |
|
725 |
_uname = str(_units.getName()) |
726 |
if len(_uname) or _uname.find("/")!=-1: |
727 |
_uname = "["+_uname+"]" |
728 |
|
729 |
if _uname == "": |
730 |
_title = "%s" % (name) |
731 |
else: |
732 |
_title = "%s / %s" % (name, _uname) |
733 |
for _tvcol in self.view.get_columns(): |
734 |
if _tvcol.title == _col.title: |
735 |
_tvcol.label.set_text(str(_title)) |
736 |
_tvcol.title = _title |
737 |
_tvcol.set_title(_title) |
738 |
_col.title = _title |
739 |
_col.units = _units |
740 |
_col.uname = _uname |
741 |
_col.name = name |
742 |
def set_dead(self): |
743 |
if self.alive == False and self.reloaded == True: |
744 |
for i in range(self.colindex): |
745 |
col = self.cols[i] |
746 |
renderer = self.renderers[i] |
747 |
tvcol = self.tvcols[i] |
748 |
tvcol.set_cell_data_func(renderer, None) |
749 |
tvcol.set_cell_data_func(renderer, col.cellvalue, True) |
750 |
self.alive = False |
751 |
self.addbutton.set_sensitive(False) |
752 |
_selection = self.view.get_selection() |
753 |
_selection.unselect_all() |
754 |
_store = self.view.get_model() |
755 |
if self.activeiter is not None: |
756 |
_rowobject = _store.get_value(self.activeiter,0) |
757 |
_rowobject.active = False |
758 |
_rowobject.dead = True |
759 |
self.activeiter = None |
760 |
|
761 |
class CloseDialog: |
762 |
|
763 |
# Just a dialog to confirm that the user REALLY wants to close |
764 |
# the observer |
765 |
|
766 |
def __init__(self, browser): |
767 |
browser.builder.add_objects_from_file(browser.glade_file, ["closeobserverdialog"]) |
768 |
self.alertwin = browser.builder.get_object("closeobserverdialog") |
769 |
browser.builder.connect_signals(self) |
770 |
|
771 |
def on_closeobserverdialog_close(self,*args): |
772 |
self.alertwin.response(gtk.RESPONSE_CLOSE) |
773 |
|
774 |
def run(self): |
775 |
_continue = True |
776 |
while _continue: |
777 |
_res = self.alertwin.run() |
778 |
if _res == gtk.RESPONSE_YES: |
779 |
self.alertwin.destroy() |
780 |
return True |
781 |
else: |
782 |
self.alertwin.destroy() |
783 |
return False |
784 |
|
785 |
class PlotDialog: |
786 |
|
787 |
# a dialog where the user can select which columns to plot |
788 |
|
789 |
def __init__(self, browser, tab): |
790 |
self.browser = browser |
791 |
self.browser.builder.add_objects_from_file(browser.glade_file, ["plotdialog"]) |
792 |
self.plotwin = self.browser.builder.get_object("plotdialog") |
793 |
self.plotbutton = self.browser.builder.get_object("plotbutton") |
794 |
self.xview = self.browser.builder.get_object("treeview1") |
795 |
self.yview = self.browser.builder.get_object("treeview2") |
796 |
self.yview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) |
797 |
self.ignorepoints = self.browser.builder.get_object("ignorepoints") |
798 |
|
799 |
_p = self.browser.prefs |
800 |
_ignore = _p.getBoolPref("PlotDialog", "ignore_error_points", True) |
801 |
self.ignorepoints.set_active(_ignore) |
802 |
|
803 |
_xstore = gtk.TreeStore(object) |
804 |
self.xview.set_model(_xstore) |
805 |
_xrenderer = gtk.CellRendererText() |
806 |
_xtvcol = gtk.TreeViewColumn("X axis") |
807 |
_xtvcol.pack_start(_xrenderer,False) |
808 |
_xtvcol.set_cell_data_func(_xrenderer, self.varlist) |
809 |
self.xview.append_column(_xtvcol) |
810 |
|
811 |
_ystore = gtk.TreeStore(object) |
812 |
self.yview.set_model(_ystore) |
813 |
_yrenderer = gtk.CellRendererText() |
814 |
_ytvcol = gtk.TreeViewColumn("Y axis") |
815 |
_ytvcol.pack_start(_yrenderer,False) |
816 |
_ytvcol.set_cell_data_func(_yrenderer, self.varlist) |
817 |
self.yview.append_column(_ytvcol) |
818 |
|
819 |
self.xcol = None |
820 |
self.ycol = None |
821 |
for _cols in tab.cols: |
822 |
_xtemp = _xstore.append(None, [tab.cols[_cols]]) |
823 |
_ytemp = _ystore.append(None, [tab.cols[_cols]]) |
824 |
if self.xcol is None: |
825 |
self.xcol = tab.cols[_cols] |
826 |
_xiter = _xtemp |
827 |
continue |
828 |
if self.ycol is None: |
829 |
self.ycol = tab.cols[_cols] |
830 |
_yiter = _ytemp |
831 |
self.plotbutton.set_sensitive(True) |
832 |
|
833 |
_selx = self.xview.get_selection() |
834 |
_selx.select_iter(_xiter) |
835 |
|
836 |
_sely = self.yview.get_selection() |
837 |
_sely.select_iter(_yiter) |
838 |
|
839 |
self.browser.builder.connect_signals(self) |
840 |
|
841 |
def on_plotdialog_close(self,*args): |
842 |
self.plotwin.response(gtk.RESPONSE_CANCEL) |
843 |
|
844 |
def varlist(self,column,cell,model,iter): |
845 |
_value = model.get_value(iter,0) |
846 |
cell.set_property('text', _value.title) |
847 |
|
848 |
def on_treeview_event(self,widget,event): |
849 |
|
850 |
_path = None |
851 |
_col = None |
852 |
self.plotbutton.set_sensitive(False) |
853 |
if event.type==gtk.gdk.KEY_RELEASE: |
854 |
_keyval = gtk.gdk.keyval_name(event.keyval) |
855 |
if _keyval == "Escape": |
856 |
self.plotwin.response(gtk.RESPONSE_CANCEL) |
857 |
return |
858 |
_path, _tvcol = widget.get_cursor() |
859 |
if _path is None: |
860 |
return |
861 |
|
862 |
elif event.type==gtk.gdk.BUTTON_RELEASE: |
863 |
_x = int(event.x) |
864 |
_y = int(event.y) |
865 |
_button = event.button |
866 |
_pthinfo = widget.get_path_at_pos(_x, _y) |
867 |
if _pthinfo is None: |
868 |
return |
869 |
_path, _tvcol, _cellx, _celly = _pthinfo |
870 |
if _path is None: |
871 |
return |
872 |
|
873 |
_sel = widget.get_selection() |
874 |
_view, path_list = _sel.get_selected_rows() |
875 |
if path_list is None: |
876 |
return |
877 |
#_path = _view.get_path(_iter) |
878 |
#_path = _iter[0] |
879 |
#if _path is None: |
880 |
# return |
881 |
widget.grab_focus() |
882 |
_store = widget.get_model() |
883 |
#_selx = self.xview.get_selection() |
884 |
#_sely = self.yview.get_selection() |
885 |
|
886 |
#widget.set_cursor( _path, None, 0) |
887 |
flag=True |
888 |
for _path in path_list: |
889 |
if _path is None: |
890 |
return |
891 |
_iter = _store.get_iter(_path) |
892 |
_col = _store.get_value(_iter,0) |
893 |
if widget is self.xview: |
894 |
self.xcol = _col |
895 |
#_selx.select_iter(_iter) |
896 |
break |
897 |
else: |
898 |
if flag==True: |
899 |
self.ycol = [] |
900 |
flag=False |
901 |
self.ycol.append(_col) |
902 |
#_sely.select_iter(_iter) |
903 |
|
904 |
if self.ycol is not None and type(self.ycol)!=type([]) : |
905 |
self.ycol = [self.ycol] |
906 |
|
907 |
flag=True |
908 |
if self.xcol is not None and self.ycol is not None: |
909 |
for yitem in self.ycol: |
910 |
if self.xcol.title == yitem.title: |
911 |
flag=False |
912 |
if widget is self.xview: |
913 |
self.yview.get_selection().unselect_all() |
914 |
self.ycol = None |
915 |
else: |
916 |
self.xview.get_selection().unselect_all() |
917 |
self.xcol = None |
918 |
break |
919 |
if flag==True: |
920 |
self.plotbutton.set_sensitive(True) |
921 |
|
922 |
def select_ycol(self, _col, _tab): |
923 |
_ystore = self.yview.get_model() |
924 |
_iter = _ystore.get_iter_first() |
925 |
for i in _tab.cols: |
926 |
_item = _ystore.get_value(_iter, 0) |
927 |
if _item.title == _col.title: |
928 |
self.ycol = _col |
929 |
_sel = self.yview.get_selection() |
930 |
_sel.unselect_all() |
931 |
_sel.select_iter(_iter) |
932 |
self.xcol = None |
933 |
self.xview.get_selection().unselect_all() |
934 |
self.plotbutton.set_sensitive(False) |
935 |
_iter = _ystore.iter_next(_iter) |
936 |
|
937 |
def run(self): |
938 |
_continue = True |
939 |
while _continue: |
940 |
_res = self.plotwin.run() |
941 |
if _res == gtk.RESPONSE_YES: |
942 |
_p = self.browser.prefs |
943 |
_p.setBoolPref("PlotDialog", "ignore_error_points", self.ignorepoints.get_active()) |
944 |
self.plotwin.destroy() |
945 |
if self.ycol is not None and type(self.ycol)!=type([]) : |
946 |
self.ycol = [self.ycol] |
947 |
return True |
948 |
else: |
949 |
self.plotwin.destroy() |
950 |
return False |
951 |
|
952 |
class ColPlotEventsDialog: |
953 |
|
954 |
# a dialog where the user can select which events to plot |
955 |
# FIXME: this window should be scrolled |
956 |
|
957 |
def __init__(self, browser, tab): |
958 |
self.browser = browser |
959 |
self.browser.builder.add_objects_from_file(browser.glade_file, ["colploteventsdialog"]) |
960 |
self.plotwin = self.browser.builder.get_object("colploteventsdialog") |
961 |
self.plotbutton = self.browser.builder.get_object("plotbutton") |
962 |
self.view = self.browser.builder.get_object("treeview1") |
963 |
|
964 |
_store = gtk.TreeStore(object) |
965 |
self.view.set_model(_store) |
966 |
_renderer = gtk.CellRendererText() |
967 |
_tvcol = gtk.TreeViewColumn("t") |
968 |
_tvcol.pack_start(_renderer,False) |
969 |
_tvcol.set_cell_data_func(_renderer, self.vallist) |
970 |
self.view.append_column(_tvcol) |
971 |
|
972 |
self.col = None |
973 |
tprev = tab.cols[0].eventdata[0] |
974 |
for _vals in tab.cols[0].eventdata: |
975 |
if _vals[0] != tprev: |
976 |
_temp = _store.append(None,[_vals[0]]) |
977 |
tprev = _vals[0] |
978 |
if self.col is None: |
979 |
self.col = _vals[0] |
980 |
_iter = _temp |
981 |
|
982 |
self.browser.builder.connect_signals(self) |
983 |
|
984 |
def on_colploteventsdialog_close(self,*args): |
985 |
self.plotwin.response(gtk.RESPONSE_CANCEL) |
986 |
|
987 |
def vallist(self,column,cell,model,iter): |
988 |
_value = model.get_value(iter,0) |
989 |
cell.set_property('text', _value) |
990 |
|
991 |
def on_treeview_event(self,widget,event): |
992 |
_path = None |
993 |
_col = None |
994 |
self.plotbutton.set_sensitive(False) |
995 |
if event.type==gtk.gdk.KEY_RELEASE: |
996 |
_keyval = gtk.gdk.keyval_name(event.keyval) |
997 |
if _keyval == "Escape": |
998 |
self.plotwin.response(gtk.RESPONSE_CANCEL) |
999 |
return |
1000 |
_path, _tvcol = widget.get_cursor() |
1001 |
if _path is None: |
1002 |
return |
1003 |
elif event.type==gtk.gdk.BUTTON_RELEASE: |
1004 |
_x = int(event.x) |
1005 |
_y = int(event.y) |
1006 |
_button = event.button |
1007 |
_pthinfo = widget.get_path_at_pos(_x, _y) |
1008 |
if _pthinfo is None: |
1009 |
return |
1010 |
_path, _tvcol, _cellx, _celly = _pthinfo |
1011 |
if _path is None: |
1012 |
return |
1013 |
|
1014 |
_sel = widget.get_selection() |
1015 |
_view, path_list = _sel.get_selected_rows() |
1016 |
if path_list is None: |
1017 |
return |
1018 |
widget.grab_focus() |
1019 |
_store = widget.get_model() |
1020 |
flag=True |
1021 |
for _path in path_list: |
1022 |
if _path is None: |
1023 |
return |
1024 |
_iter = _store.get_iter(_path) |
1025 |
_col = _store.get_value(_iter,0) |
1026 |
if widget is self.view: |
1027 |
self.col = _col |
1028 |
break |
1029 |
|
1030 |
flag=True |
1031 |
if self.col is not None: |
1032 |
self.plotbutton.set_sensitive(True) |
1033 |
|
1034 |
def run(self): |
1035 |
_continue = True |
1036 |
while _continue: |
1037 |
_res = self.plotwin.run() |
1038 |
if _res == gtk.RESPONSE_YES: |
1039 |
self.plotwin.destroy() |
1040 |
return True |
1041 |
else: |
1042 |
self.plotwin.destroy() |
1043 |
return False |
1044 |
|
1045 |
|