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