Package qubx :: Module data_panel
[hide private]
[frames] | no frames]

Source Code for Module qubx.data_panel

  1  """Components for the top level of the QUB Express or Fitness. 
  2   
  3  Copyright 2008-2014 Research Foundation State University of New York  
  4  This file is part of QUB Express.                                           
  5   
  6  QUB Express is free software; you can redistribute it and/or modify           
  7  it under the terms of the GNU General Public License as published by  
  8  the Free Software Foundation, either version 3 of the License, or     
  9  (at your option) any later version.                                   
 10   
 11  QUB Express is distributed in the hope that it will be useful,                
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of        
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
 14  GNU General Public License for more details.                          
 15   
 16  You should have received a copy of the GNU General Public License,    
 17  named LICENSE.txt, in the QUB Express program directory.  If not, see         
 18  <http://www.gnu.org/licenses/>.                                       
 19   
 20   
 21  """ 
 22   
 23  import os 
 24  import traceback 
 25   
 26  import gtk 
 27  import gobject 
 28  import numpy 
 29   
 30  import qubx.data_types 
 31  import qubx.data_fit 
 32  import qubx.dataGTK 
 33  import qubx.faces 
 34  import qubx.GTK 
 35  import qubx.pyenv 
 36  import qubx.settings 
 37  import qubx.tree 
 38  import qubx.table 
 39  import qubx.tableGTK 
 40  import qubx.toolspace 
 41  import qubx.notebook 
 42  import qubx.notebookGTK 
 43   
 44  from itertools import izip, count 
 45  from math import * 
 46  from gtk import gdk 
 47  from qubx.accept import * 
 48  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem 
 49  from qubx.settings import Property, Propertied 
 50  from qubx.table import COPY_ROWS_ALL, COPY_ROWS_GROUP, COPY_ROWS_CRITERIA, COPY_ROWS_CHECKED 
 51  from qubx.toolspace import ColorInfo 
 52  from qubx.util_types import * 
 53   
 54   
 55  COLOR_DATA_FILE_MENU = ('QubX.data.file.menu', (.6, .6, 1, 1)) 
 56  ColorInfo[COLOR_DATA_FILE_MENU[0]].label = 'Data file menu triangle' 
 57   
 58  LORES_FRAC = (1.0 - 1.0 / (1.618)) / 2.0 
 59  LORES_MIN_EM = 8.0 
 60   
 61  @Propertied(Property('prompt_store_fit_curves', True, 'False to trust the user on "save fit curves in session"'), 
 62              Property('splitter_frac', 0.0, 'position between 0.0 and 1.0 of last hi/lo splitter moved')) 
63 -class DataFace(qubx.faces.FilesFace):
64 """ 65 Panel displaying one of possibly several open data files. If has_modeling, file index 0 is L{qubx.data_types.QubData_Scratch}. 66 67 @ivar views: list of L{qubx.dataGTK.QubDataView} 68 """ 69 70 __explore_featured = ['has_modeling', 'about', 'controls_fit', 'sequence', 'lstSequence', 'display_prefs', 71 'menubar', 'init_fitting', 'init_scratch', 'open_sequence', 'open_seq_next', 'go_seq', 72 'close_seq_curr', 'done_sequence', 'set_auto_save_session', 73 'copy_segments_to_list', 'list_add_screen', 'new_list', 'import_list', 'export_list', 74 'copy_list', 'measure_list', 'split_list', 'get_samples', 'get_samples_indexed', 75 'add_computed_signal'] 76
77 - def __init__(self, tables, has_modeling, global_name=""):
78 qubx.faces.FilesFace.__init__(self, ' Data', qubx.data_types.QubDatas(), 79 lambda table:qubx.dataGTK.QubDataView_Lo(table,qubx.faces.FILEMENU_WIDTH), qubx.faces.FILEMENU_WIDTH, 80 popup_color=COLOR_DATA_FILE_MENU, global_name=global_name) 81 self.__ref = Reffer() 82 self.propertied_connect_settings('QubX.Data') 83 self.has_modeling = has_modeling 84 if not has_modeling: 85 for layer in (self.layFiles, self.layMenu): 86 self.page0.controls.remove_layer(layer) 87 self.page0.add_layer(layer) 88 self.page0.file = qubx.data_types.QubData_Blank('<no file>') 89 self.tables = tables 90 self.tables.add_table(self.table) 91 hdr = qubx.toolspace.Button_Popup_PNG(self.mnuFile, os.path.join(qubx.global_namespace.app_path, 'icons', 'folder.png'), "File menu") 92 hdr.show() 93 tables.find_view(' Data').set_index_widget(hdr, hdr.do_popup) 94 self.about = qubx.faces.TextFace("Data", global_name='QubX.About.Data') 95 self.about_notes = qubx.faces.TextFace("DataNotes", global_name='QubX.About.DataNotes') 96 self.about.show() 97 self.QubX = None 98 self.controls_fit = None 99 self.__showing_fit = False 100 self.__timeout_session = None 101 self.__timeout_session_minutes = 5 102 103 self.itemNew.hide() 104 self.itemSave.hide() 105 self.itemRevert.hide() 106 item = build_menuitem('Open Sequence...', self.__ref(lambda item: self.open_sequence())) 107 self.mnuFile.insert(item, 3) 108 item = self.itemCancelSequence = build_menuitem('Cancel Sequence', self.__ref(lambda item: self.open_sequence([])), show=False) 109 self.mnuFile.insert(item, 4) 110 self.mnuFile.append(build_menuitem('Save session', self.__ref(self.__onClickSaveSession))) 111 self.itemAutoSaveSession = build_menuitem('Auto-save session...', self.__ref(self.__onClickAutoSaveSession), menu=self.mnuFile, item_class=gtk.CheckMenuItem) 112 self.itemStoreFits = build_menuitem('Save fit curves in session', self.__ref(self.__onClickStoreFits), menu=self.mnuFile, item_class=gtk.CheckMenuItem) 113 self.__in_auto_session = False 114 self.mnuFile.append(build_menuitem('New Table...', self.__ref(lambda item: qubx.pyenv.env.globals['QubX'].new_table()))) 115 self.__list_table = None 116 self.__ignore_store_fits = False 117 self.__open_seq = [] 118 self.__ix_in_seq = -1 119 self.__curr_in_seq = None 120 self.__next_in_seq = None 121 self.sequence = qubx.faces.Face('Sequence') 122 self.lstSequence = qubx.GTK.CheckList() 123 self.lstSequence.OnClick += self.__ref(self.__onClickSequence) 124 pack_scrolled(self.lstSequence, self.sequence, expand=True) 125 126 self.display_prefs = qubx.settings.SettingsMgr['Data.Display'] 127 self.display_prefs.OnSet = self.__ref(self.__onUpdateDisplay) 128 129 self.layFiles.cBG = qubx.scope.COLOR_SIGNAL_BG 130 self.mnuFiles.color = qubx.dataGTK.COLOR_DATA_LAYER_FG 131 132 self.menubar = gtk.MenuBar() 133 self.itemFile = gtk.MenuItem('File') 134 self.itemFile.connect('activate', bind(self.on_popup_file)) 135 self.itemFile.show() 136 self.menubar.append(self.itemFile) 137 self.itemLists = gtk.MenuItem('Lists') 138 self.itemLists.connect('activate', self.__onPopupLists) 139 self.itemLists.show() 140 self.menubar.append(self.itemLists) 141 self.itemDisplay = gtk.MenuItem('Display') 142 self.itemDisplay.connect('activate', self.__onPopupDisplay) 143 self.itemDisplay.show() 144 self.menubar.append(self.itemDisplay) 145 self.itemTools = gtk.MenuItem('Tools') 146 self.itemTools.connect('activate', self.__onPopupTools) 147 self.itemTools.show() 148 self.menubar.append(self.itemTools) 149 150 self.init_scratch(has_modeling) 151 self.OnSwitch += self.__ref(self.__onSwitch) 152 self.OnSave += self.__ref(self.__onSave) 153 self.OnClosing += self.__ref(self.__onClosing) 154 qubx.notebook.Notebook.register_auto('Data_Open.Picture', 'Data picture, on open/choose') 155 qubx.notebook.Notebook.register_auto('Data_Open.PictureNO', 'Data picture, no overlays, on open/choose') 156 qubx.notebook.Notebook.register_auto('Data_Open.Table', 'Data table, on open/choose') 157 qubx.notebook.Notebook.register_auto('Data_Save.Picture', 'Data picture, on save') 158 qubx.notebook.Notebook.register_auto('Data_Save.PictureNO', 'Data picture, no overlays, on save') 159 qubx.notebook.Notebook.register_auto('Data_Save.Table', 'Data table, on save')
160 - def init_fitting(self):
161 # after QubX.Data and QubX.DataSource have been assigned 162 self.controls_fit = qubx.data_fit.DataFitControls(global_name="QubX.Data.controls_fit")
163 - def make_page(self, view):
164 page = gtk.VPaned() 165 page.pack1(view, True, True) 166 view.show() 167 page.pack2(view.hires, True, True) 168 view.hires.show() 169 page.show() 170 view.hires.mnuFile = view.mnuFile 171 view.hires.mnuFiles = view.mnuFiles 172 page.evt_on_size_allocate = page.connect('size_allocate', self.__onAllocPage) 173 page.evt_on_position = page.connect('notify::position', self.__onSplitterPosition) 174 return page
175 - def __onAllocPage(self, page, allocation):
176 if (self.splitter_frac == 0.0) or (page.get_position() < qubx.toolspace.Appearance.emsize): 177 gobject.idle_add(page.set_position, int(round(min(.6*allocation.height, max(LORES_MIN_EM*qubx.toolspace.Appearance.emsize, LORES_FRAC * allocation.height))))) 178 elif (page.get_position() > (allocation.height - qubx.toolspace.Appearance.emsize)): 179 gobject.idle_add(page.set_position, int(round(min(.6*allocation.height, max(LORES_MIN_EM*qubx.toolspace.Appearance.emsize, self.splitter_frac*allocation.height)))))
180 - def __onSplitterPosition(self, paned, param_spec):
181 x,y,w,h = paned.get_allocation() 182 if h <= 1: return 183 p = paned.get_position() 184 self.splitter_frac = max(0.0, min(1.0, p*1.0/h)) 185 if h and (p < qubx.toolspace.Appearance.emsize): 186 paned.set_position(max(1, h/3))
187 - def __onUpdateDisplay(self, prefs, updates):
188 if self.view: 189 self.view.update_display_prefs(prefs, updates)
190 - def __onSwitch(self, selfy, file):
191 if (not self.view) or (not self.view.signals): return 192 qubx.pyenv.env.OnScriptable('QubX.Data.show_file(%s)' % repr(file.path)) 193 qubx.notebook.Notebook.send(self.view.nbPicture, auto_id='Data_Open.Picture') 194 qubx.notebook.Notebook.send(self.view.nbPictureNO, auto_id='Data_Open.PictureNO') 195 qubx.notebook.Notebook.send(self.view.nbChart, auto_id='Data_Open.Table')
196 - def __onSave(self, selfy, file, path):
197 qubx.notebook.Notebook.send(self.view.nbPicture, auto_id='Data_Save.Picture') 198 qubx.notebook.Notebook.send(self.view.nbPictureNO, auto_id='Data_Save.PictureNO') 199 qubx.notebook.Notebook.send(self.view.nbChart, auto_id='Data_Save.Table') 200 if not os.path.splitext(path)[1]: 201 path = path + '.txt' 202 self.open(path)
203 - def __onRqFit(self, show_fit):
204 if show_fit and not self.__showing_fit: 205 if self.view: 206 self.view.hires.layerset = self.controls_fit 207 self.__showing_fit = True 208 elif (not show_fit) and self.__showing_fit: 209 if self.view: 210 self.view.hires.layerset = self.view.hires.controls 211 self.__showing_fit = False
212 - def onShow(self, showing):
213 qubx.faces.FilesFace.onShow(self, showing) 214 if showing: 215 if not self.QubX: 216 self.QubX = qubx.pyenv.env.globals['QubX'] 217 self.QubX.About.append_face(self.about_notes) 218 self.QubX.About.append_face(self.about) 219 self.QubX.About.show_face('Data') 220 self.QubX.DataSourceFace.show() 221 else: 222 self.QubX.About.remove_face(self.QubX.About.index('Data')) 223 self.QubX.About.remove_face(self.QubX.About.index('DataNotes')) 224 self.QubX.DataSourceFace.hide()
225 - def init_scratch(self, has_modeling):
226 if has_modeling: 227 self.table.insert(0, qubx.data_types.QubData_Scratch('<simulation>')) 228 self.index = 0 229 else: 230 self._file = self.page0.file 231 self.init_file(self.page0.file, self.page0)
232 - def open(self, path=None):
233 if path is None: 234 self._onItemOpen(None) 235 return 236 if os.path.splitext(path)[1].lower() == '.txt': 237 response = qubx.pyenvGTK.prompt_choices("Open the %s as sampled data, or as a table?" % os.path.split(path)[1], 238 ["Skip", "As sampled data", "As table"]) 239 else: 240 response = 1 241 if response == 0: 242 return 243 elif response == 2: 244 return qubx.global_namespace.QubX.open_table(path) 245 246 if not self.showing: 247 self.request_show() 248 i = self.index + 1 249 def task(progress): 250 data = qubx.data_types.Open(path, progress) 251 if data: 252 self.table.insert(i, data)
253 def finish(): 254 self.index = i
255 256 qubx.pyenv.env.OnScriptable('QubX.Data.open(%s)' % repr(path)) 257 dlg = qubx.GTK.BusyDialog('Opening %s...'%os.path.split(path)[1], task, finish) 258 dlg.set_size_request(300, 75) 259 try: 260 dlg.run() 261 finally: 262 dlg.destroy()
263 - def open_sequence(self, paths=None):
264 if paths is None: 265 return qubx.GTK.OpenMulti('Open Sequence...', self.path, self.open_sequence, self.get_open_filters(), allow_all_files=True) 266 qubx.pyenv.env.OnScriptable('QubX.Data.open_sequence(%s)' % repr(paths)) 267 self.done_sequence() 268 if paths: 269 self.__open_seq = [path for path in paths] 270 for path in paths: 271 self.lstSequence.append(True, os.path.split(path)[1]) 272 qubx.pyenv.env.globals['QubX'].About.append_face(self.sequence) 273 qubx.pyenv.env.globals['QubX'].About.show_face('Sequence') 274 self.__ix_in_seq = -1 275 self.open_seq_next(True)
276 - def open_seq_next(self, already_closed=False):
277 if not already_closed: 278 return self.close_seq_curr() 279 if not self.__open_seq: return 280 if self.__next_in_seq is None: 281 ix = self.__ix_in_seq + 1 282 while (ix < len(self.__open_seq)) and not self.lstSequence.get_active(ix): 283 ix += 1 284 if not (0 <= ix < len(self.__open_seq)): 285 self.done_sequence() 286 return 287 else: 288 ix = self.__next_in_seq 289 self.__next_in_seq = None 290 self.lstSequence.set_cursor((ix,), None, False) 291 self.open(self.__open_seq[ix]) 292 self.__ix_in_seq = ix 293 self.__curr_in_seq = self.file 294 self.itemCancelSequence.show()
295 - def go_seq(self, ix):
296 if not self.__open_seq: return 297 self.__next_in_seq = ix 298 self.close_seq_curr()
299 - def close_seq_curr(self):
300 if self.__open_seq and self.__curr_in_seq: 301 self.show_file(self.__curr_in_seq.path) 302 self.close()
303 - def done_sequence(self):
304 if self.__open_seq: 305 self.__ix_in_seq = len(self.__open_seq) - 1 # so it doesn't just open the next one 306 self.close_seq_curr() 307 self.itemCancelSequence.hide() 308 self.__open_seq = [] 309 self.lstSequence.clear() 310 if self.sequence.parent: 311 qubx.pyenv.env.globals['QubX'].About.remove_face(qubx.pyenv.env.globals['QubX'].About.index('Sequence'))
312 - def __onClickSequence(self, ix):
313 if self.file.path == self.__open_seq[ix]: 314 #print 'already there' 315 return 316 qubx.pyenv.env.OnScriptable('QubX.Data.go_seq(%i)' % ix) 317 self.go_seq(ix)
318 - def __onClosing(self, ix):
319 qubx.pyenv.env.OnScriptable('QubX.Data.close()')
320 - def save(self, path=None):
321 if not self.file.signals: 322 return 323 def task(progressf): 324 fname = path or self.file.path 325 for other_view in self.views: 326 if other_view.file != self.file: 327 print other_view.file.path, fname 328 if other_view.file.path == fname: 329 raise Exception('The file is in use. Please close it first.') 330 qubx.pyenv.env.OnScriptable('QubX.Data.save(%s)' % repr(fname)) 331 self.file.saveAs(fname, progressf) 332 self.path = os.path.split(fname)[0] or self.path 333 self.OnSave(self, self.file, fname)
334 dlg = qubx.GTK.BusyDialog('Saving %s...'%os.path.split(path)[1], task) 335 dlg.set_size_request(300, 75) 336 try: 337 dlg.run() 338 finally: 339 dlg.destroy()
340 - def on_pre_close(self):
341 self.file.notes = self.about_notes.get_text() 342 ix = self.index 343 if self.__open_seq and (self.views[ix].file == self.__curr_in_seq): 344 self.__curr_in_seq = None 345 gobject.idle_add(self.open_seq_next, True) 346 if self.prompt_store_fit_curves and not self.file.store_fit_curves: 347 Ndata = self.file.segmentation.segments[-1][1] 348 for fitsignal in self.file.fits: 349 if fitsignal.idl.count_dwells(0, Ndata-1, True): 350 nm = os.path.split(self.file.path)[1] 351 dlg = qubx.GTK.AskOnceDialog('%s - Save fit curves with data?' % qubx.global_namespace.QubX.appname, None, 352 """The data file %s 353 has unsaved fit curves. Save them in the session file? 354 (%s.qsf) 355 356 By default, they are not saved unless you select 357 "Save fit curves in session" from the File menu.""" % (nm, os.path.splitext(nm)[0]), 358 (("Don't Save", 0), ('Save', 1)), dont_show_again=False) 359 response, dont_show = dlg.run() 360 dlg.destroy() 361 self.file.store_fit_curves = response 362 self.prompt_store_fit_curves = not dont_show 363 break
364 - def on_pre_save(self, fname):
365 self.file.notes = self.about_notes.get_text()
366 - def is_changed(self):
367 return False # self._file and self._file.changed
368 - def can_save(self):
369 return False # (self._file and self._file.path and self._file.changed) or False
370 - def can_revert(self):
371 return False # (self._file and self._file.path and self._file.changed) or False
372 - def init_file(self, file, view):
373 if file: 374 if not (file.signals is None): self.tables.add_table(file.signals, None, 375 qubx.tableGTK.TableViewCtrls(file.signals, None, 376 ('Add computed signal...', self.add_computed_signal), 377 qubx.tableGTK.TableViewCtrls.REMOVE)) 378 if not (view.signals is None): self.tables.add_table(view.signals) 379 if not (file.constants is None): self.tables.add_table(file.constants, None, 380 qubx.tableGTK.TableViewCtrls(file.constants, None, 381 qubx.tableGTK.TableViewCtrls.ADD, 382 qubx.tableGTK.TableViewCtrls.REMOVE)) 383 if not (file.stimuli is None): self.tables.add_table(file.stimuli) 384 if not (file.segments is None): self.tables.add_table(file.segments, None, 385 qubx.tableGTK.TableViewCtrls(file.segments, None, 386 ('Copy row(s) to list...', self.copy_segments_to_list), 387 qubx.tableGTK.TableViewCtrls.CALCULATE, 388 qubx.tableGTK.TableViewCtrls.PLOT)) 389 if not (file.lists is None): 390 file.lists.OnSwitchList += self.__ref(self.__onSwitchList) 391 self.__show_list_table(file.list) 392 self._file.OnChangePath += self.__ref(self.__onChangePath) 393 view.OnHint += self.__ref(self.__onHint) 394 view.hires.OnHint += self.__ref(self.__onHint) 395 self.about_notes.set_text(file.notes) 396 try: 397 self.itemFile.set_submenu(view.mnuFile) 398 self.itemLists.set_submenu(view.layLists.mnu) 399 self.itemDisplay.set_submenu(view.subDisplay.popup) 400 self.itemTools.set_submenu(view.hires.mnuOtherTools) 401 except: 402 self.itemFile.set_submenu(None) 403 self.itemLists.set_submenu(None) 404 self.itemDisplay.set_submenu(None) 405 self.itemTools.set_submenu(None) 406 view.hires.request_fit_controls = self.__onRqFit 407 if self.__showing_fit: 408 view.hires.layerset = self.controls_fit 409 if self.index >= (self.has_modeling and 1 or 0): 410 self.itemClose.show() 411 else: # scratch file -- can't close 412 self.itemClose.hide()
413 - def done_file(self, file, view):
414 if file: 415 file.notes = self.about_notes.get_text() 416 if not (file.signals is None): self.tables.remove_table(file.signals) 417 if not (view.signals is None): self.tables.remove_table(view.signals) 418 if not (file.constants is None): self.tables.remove_table(file.constants) 419 if not (file.stimuli is None): self.tables.remove_table(file.stimuli) 420 if not (file.segments is None): self.tables.remove_table(file.segments) 421 if not (file.lists is None): 422 file.lists.OnSwitchList -= self.__ref(self.__onSwitchList) 423 self._file.OnChangePath -= self.__ref(self.__onChangePath) 424 view.OnHint -= self.__ref(self.__onHint) 425 if self.__showing_fit: 426 view.hires.layerset = view.hires.controls 427 self.__show_list_table(None)
428 - def __onPopupLists(self, item):
429 self.view.layLists.on_popup_lists()
430 - def __onPopupDisplay(self, item):
431 self.view.subDisplay.on_popup()
432 - def __onPopupTools(self, item):
433 self.view.hires.onPopupOtherTools()
434 - def get_open_filters(self):
435 filters = [] 436 for ext in sorted(qubx.data_types.Readers): 437 reader = qubx.data_types.GetReader(ext) 438 if ext == '.qdf': 439 filters.insert(0, (reader.description, reader.ext)) 440 else: 441 filters.append((reader.description, reader.ext)) 442 return filters
443 - def get_save_filters(self):
444 filters = [] 445 for ext in sorted(qubx.data_types.Writers): 446 writer = qubx.data_types.Writers[ext] 447 if ext == '.txt': 448 filters.insert(0, (writer.description, writer.ext)) # default entry first 449 else: 450 filters.append((writer.description, writer.ext)) 451 return filters
452 - def close_down(self):
453 """Override: don't close the simulation data.""" 454 self._no_to_all = False 455 self.done_sequence() 456 for i in reversed(xrange(1, self.table.size)): 457 self.index = i 458 if not self.close_one(): 459 return False 460 return True
461 - def __onClickSaveSession(self, item):
462 qubx.pyenv.env.OnScriptable('QubX.Data.file.save_session()') 463 self.file.save_session()
464 - def __onClickAutoSaveSession(self, item):
465 if self.__in_auto_session: return 466 # prompt: [ x ] Save session every [ 5 ] minutes [Cancel] [OK] 467 QubX = qubx.pyenv.env.globals['QubX'] 468 dlg = gtk.Dialog('%s - Auto-save session...' % QubX.appname, None, gtk.DIALOG_MODAL) 469 line = pack_item(gtk.HBox(), dlg.vbox) 470 chk = pack_check('Auto-save session (.qsf) every ', line, active=bool(self.__timeout_session)) 471 txt = pack_item(qubx.GTK.NumEntry(self.__timeout_session_minutes, acceptIntGreaterThan(0), width_chars=4), line) 472 pack_label(' minutes', line) 473 dlg.add_button('Cancel', gtk.RESPONSE_REJECT) 474 dlg.add_button('OK', gtk.RESPONSE_ACCEPT) 475 response = dlg.run() 476 mins = chk.get_active() and txt.value or None 477 dlg.destroy() 478 if response == gtk.RESPONSE_ACCEPT: 479 qubx.pyenv.env.OnScriptable('QubX.Data.set_auto_save_session(minutes=%s)' % repr(mins)) 480 self.set_auto_save_session(mins) 481 else: 482 self.__in_auto_session = True 483 self.itemAutoSaveSession.set_active(bool(self.__timeout_session)) 484 self.__in_auto_session = False
485
486 - def set_auto_save_session(self, minutes):
487 if minutes: 488 self.__timeout_session_minutes = minutes 489 if self.__timeout_session: 490 gobject.source_remove(self.__timeout_session) 491 self.__in_auto_session = True 492 self.itemAutoSaveSession.set_active(bool(minutes)) 493 self.__in_auto_session = False 494 if minutes: 495 self.__timeout_session = gobject.timeout_add(int(round(1000*60*minutes)), self.__onTimeoutSession) 496 else: 497 self.__timeout_session = None
498 - def __onTimeoutSession(self):
499 print 'Auto-saving session (.qsf) file(s)' 500 for view in self.views: 501 view.file.save_session() 502 return True
503 - def __onClickStoreFits(self, item):
504 if self.__ignore_store_fits: return 505 store = not self.file.store_fit_curves 506 qubx.pyenv.env.OnScriptable('QubX.Data.file.store_fit_curves = %s' % repr(store)) 507 self.file.store_fit_curves = store
508 - def on_popup_file(self):
509 qubx.faces.FilesFace.on_popup_file(self) 510 self.__ignore_store_fits = True 511 self.itemStoreFits.set_active(int(self.file.store_fit_curves)) 512 self.__ignore_store_fits = False 513 return True
514 - def copy_segments_to_list(self, list_name=None, copy_rows_type=None, group=None, inverse=False, criteria=None):
515 tbn, crt, grp, inv, cta = qubx.tableGTK.prompt_copy_rows(self.file.segments, self.file.segments.label, 516 [l.list_name for l in self.file.lists.lists], 517 list_name, copy_rows_type, group, inverse, criteria) 518 if crt is None: # dialog cancel 519 return 520 if crt == COPY_ROWS_ALL: 521 qubx.pyenv.env.OnScriptable('QubX.Data.copy_segments_to_list(list_name=%s, copy_rows_type=COPY_ROWS_ALL)' % repr(tbn)) 522 elif crt == COPY_ROWS_GROUP: 523 qubx.pyenv.env.OnScriptable('QubX.Data.copy_segments_to_list(list_name=%s, copy_rows_type=COPY_ROWS_GROUP, group=%s, inverse=%s)' 524 % (repr(tbn), repr(grp), repr(inv))) 525 elif crt == COPY_ROWS_CRITERIA: 526 qubx.pyenv.env.OnScriptable('QubX.Data.copy_segments_to_list(list_name=%s, copy_rows_type=COPY_ROWS_CRITERIA, criteria=%s)' 527 % (repr(tbn), 528 repr(cta))) 529 elif crt == COPY_ROWS_CHECKED: 530 qubx.pyenv.env.OnScriptable('QubX.Data.copy_segments_to_list(list_name=%s, copy_rows_type=COPY_ROWS_CHECKED)' % repr(tbn)) 531 self.file.lists.show_list(tbn) 532 to_list = self.file.list 533 self.tables.show_table(self.file.list) 534 if not self.file.segments: 535 rows = [] 536 elif crt == COPY_ROWS_ALL: 537 rows = list(xrange(self.file.segments.size)) 538 elif crt == COPY_ROWS_GROUP: 539 inv_op = (lambda x: not x) if inv else (lambda x: x) 540 tbl = self.file.segments 541 rows = [i for i in xrange(tbl.size) if inv_op(grp == tbl.get(i, 'Group'))] 542 elif crt == COPY_ROWS_CRITERIA: 543 rows = self.file.segments.rows_meeting_criteria(cta) 544 elif crt == COPY_ROWS_CHECKED: 545 rows = [i for i in xrange(tbl.size) if tbl.checked[i]] 546 for i in rows: 547 f, l, n = self.file.segmentation.segments[i] 548 row = self.file.segments.get_row(i).__dict__ 549 entry = dict((f, row[f]) for f in self.file.segments.fields) 550 entry['From'] = f 551 entry['To'] = l 552 to_list.insert_selection(**entry) 553 return to_list
554 - def __onChangePath(self, data, path):
555 self.names[self.index] = os.path.split(path)[1] 556 self.lbls[self.index].set_text(self.names[self.index]) 557 lbl, action = self.mnuFiles.menu[self.index] 558 self.mnuFiles.menu[self.index] = self.names[self.index], action 559 self.mnuFiles.label = self.names[self.index]
560 - def __onPopulateLists(self, add):
561 for lst in self.file.lists.lists: 562 add(lst.list_name)
563 - def __onChooseList(self, mnu, list_name):
564 self.file.lists.show_list(list_name) 565 if not (self.file.list is None): 566 self.tables.show_table(self.file.list)
567 - def __onClickCloseList(self, ctrls, lst):
568 qubx.pyenv.env.OnScriptable('QubX.Data.file.lists.del_list(QubX.Data.file.lists.index)') 569 gobject.idle_add(self.file.lists.del_list, self.file.lists.index)
570 - def __onSwitchList(self, lists, index, item):
571 self.__show_list_table(item)
572 - def __show_list_table(self, item):
573 if not (self.__list_table is None): 574 self.tables.remove_table(self.__list_table) 575 self.__list_table = None 576 if not (item is None): 577 ref = Reffer() 578 mnuList = qubx.GTK.DynamicComboBox() 579 mnuList.OnPopulate += ref(self.__onPopulateLists) 580 mnuList.OnChanged += ref(self.__onChooseList) 581 mnuList.active_text = item.list_name 582 mnuListTools = gtk.Menu() 583 btnListTools = qubx.toolspace.Button_MenuLines(mnuListTools, 'List tools...', ref(bind(self.__onPopupListTools, mnuListTools))) 584 list_controls = qubx.tableGTK.TableViewCtrls(item, None, 585 'List:', mnuList, 586 ('New list...', self.new_list), 587 qubx.tableGTK.TableViewCtrls.REMOVE, 588 qubx.tableGTK.TableViewCtrls.ADD_FIELD, 589 qubx.tableGTK.TableViewCtrls.CALCULATE, 590 qubx.tableGTK.TableViewCtrls.PLOT, 591 btnListTools, 592 qubx.tableGTK.TableViewCtrls.CLOSE) 593 list_controls.ref = ref 594 list_controls.OnClickClose += ref(self.__onClickCloseList) 595 self.tables.add_table(item, None, list_controls) 596 self.tables.show_table(self.file.list) 597 self.tables.find_view('List').OnSortColumnChanged += ref(self.view.OnListSorted) 598 self.__list_table = item 599 else: 600 item = self.__list_table = qubx.data_types.SelectionList() 601 list_controls = qubx.tableGTK.TableViewCtrls(item, None, 'List: <none>', ('New list...', self.new_list)) 602 self.tables.add_table(item, None, list_controls) 603 self.__list_table = item
604 - def list_add_screen(self):
605 self.view.add_screen_to_list()
606 - def new_list(self, list_name=None):
607 if list_name is None: 608 dlg = qubx.GTK.NumEntryDialog('New list...', self.QubX, 'List name:', '', str) 609 if gtk.RESPONSE_ACCEPT == dlg.run(): 610 if dlg.value: 611 self.new_list(dlg.value) 612 dlg.destroy() 613 return 614 qubx.pyenv.env.OnScriptable('QubX.Data.new_list(%s)' % repr(list_name)) 615 self.file.lists.show_list(list_name) 616 self.tables.show_table(self.file.list)
617 - def import_list(self, fname=None):
618 if fname is None: 619 qubx.GTK.Open('%s - Import List...'%self.QubX.appname, qubx.pyenv.env.globals['documents_path'], self.import_list) 620 return 621 qubx.pyenv.env.OnScriptable('QubX.Data.import_list(%s)' % repr(fname)) 622 name = os.path.splitext(os.path.split(fname)[1])[0] 623 txt = open(fname, 'r').read() 624 self.file.lists.show_list(name) 625 self.file.list.clear() 626 self.file.list.from_text(txt) 627 self.tables.show_table(self.file.list)
628 - def export_list(self, fname=None):
629 lst = self.file.list 630 if fname is None: 631 qubx.GTK.SaveAs('%s - Export List...'%self.QubX.appname, qubx.pyenv.env.globals['documents_path'], lst.list_name, self.export_list) 632 return 633 qubx.pyenv.env.OnScriptable('QubX.Data.export_list(%s)' % repr(fname)) 634 open(fname, 'w').write(lst.to_text())
635 - def copy_list(self, list_name=None, copy_rows_type=None, group=None, inverse=False, criteria=None):
636 tbn, crt, grp, inv, cta = qubx.tableGTK.prompt_copy_rows(self.file.list, self.file.list.list_name, 637 [l.list_name for l in self.file.lists.lists], 638 list_name, copy_rows_type, group, inverse, criteria) 639 if crt is None: # dialog cancel 640 return 641 if crt == COPY_ROWS_ALL: 642 qubx.pyenv.env.OnScriptable('QubX.Data.copy_list(list_name=%s, copy_rows_type=COPY_ROWS_ALL)' 643 % repr(tbn)) 644 elif crt == COPY_ROWS_GROUP: 645 qubx.pyenv.env.OnScriptable('QubX.Data.copy_list(list_name=%s, copy_rows_type=COPY_ROWS_GROUP, group=%s, inverse=%s)' 646 % (repr(tbn), 647 repr(grp), repr(inv))) 648 elif crt == COPY_ROWS_CRITERIA: 649 qubx.pyenv.env.OnScriptable('QubX.Data.copy_list(list_name=%s, copy_rows_type=COPY_ROWS_CRITERIA, criteria=%s)' 650 % (repr(tbn), 651 repr(cta))) 652 elif crt == COPY_ROWS_CHECKED: 653 qubx.pyenv.env.OnScriptable('QubX.Data.copy_list(list_name=%s, copy_rows_type=COPY_ROWS_CHECKED)' 654 % repr(tbn)) 655 from_list = self.file.list 656 self.file.lists.show_list(tbn) 657 to_list = self.file.list 658 gobject.idle_add(self.tables.show_table, self.file.list) 659 to_list.copy_rows_from(from_list, copy_rows_type=crt, group=grp, inverse=inv, criteria=cta) 660 return to_list
661 - def measure_list(self):
662 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure_list(QubX.Data.file.list)') 663 self.view.measure_list(self.file.list, wait=False)
664 - def split_list(self, group_names=None): ### TODO: prompt for output list names, whether to clear first
665 lst = self.file.list 666 names = {} if (group_names is None) else group_names 667 groups = lst.groups_occupied(True) 668 for g in groups: 669 if not (g in names): 670 names[g] = g and ('%s-%i' % (lst.list_name, g)) or "" 671 values = qubx.GTK.PromptEntries([('This will build a new list from each non-zero Group in the old list.', None, None, None)] + 672 [('Group %i' % g, names[g], str, str) for g in groups], 673 "Split list by Group") 674 if values is None: 675 return 676 for i, g in enumerate(groups): 677 names[g] = values[i+1] 678 qubx.pyenv.env.OnScriptable('QubX.Data.split_list()') 679 for g in groups: 680 to_name = names[g] 681 if to_name: 682 to_list = self.file.lists.show_list(to_name) 683 to_list.copy_rows_from(lst, copy_rows_type=COPY_ROWS_GROUP, group=g, inverse=False)
684 - def __onPopupListTools(self, mnu):
685 mnu.foreach(mnu.remove) 686 mnu.ref = Reffer() 687 list_tools = qubx.dataGTK.Tools.by_cat['list'] 688 for tool_label in sorted(list_tools.keys()): 689 action, tool_class = list_tools[tool_label] 690 build_menuitem(tool_label, action, menu=mnu) 691 subMeasure = gtk.Menu() 692 build_menuitem('Measure (pick script):', menu=mnu, submenu=subMeasure) 693 names = [] 694 for folder in self.view.hires.tool_ruler.system_path, self.view.hires.tool_ruler.user_path: 695 for base, dirs, files in os.walk(folder): 696 dirs[:] = [] 697 names.extend(fn for fn in files if fn[-3:].lower() == '.py') 698 for name in sorted(list(set(names))): 699 build_menuitem(name, mnu.ref(bind_with_args_before(self.__onItemScript, name)), menu=subMeasure) 700 return True
701 - def __onItemScript(self, item, name):
702 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure_list(QubX.Data.file.list, script_name=%s)' % repr(name)) 703 self.view.measure_list(self.file.list, script_name=name, wait=False)
704 - def __onChangeCopyGroup(self, txt, val):
705 self.palCopyGroup.color = val
706 - def __onClickCopyColor(self, palette, color):
707 self.txtCopyGroup.value = color
708 - def __onHint(self, data, hint):
709 self.about.set_text(hint)
710 - def get_samples(self, left=None, right=None, signal_name=None, source=qubx.dataGTK.DATASOURCE_SCREEN):
711 """Returns array of data values, onscreen or between times "left" and "right" in seconds, onscreen segments or all.""" 712 signal_ix = self.QubX.DataSource.signal 713 if signal_name: 714 signal_ix = [self.view.scope.get(i, 'Name') for i in xrange(1, self.view.scope.size)].index(signal_name) 715 segs = self.view.get_segmentation(left=left, right=right, source=source, signal=signal_ix) 716 if segs: 717 return numpy.hstack([seg.get_samples().samples for seg in segs]) 718 return numpy.array([])
719 - def get_samples_indexed(self, first, last, signal_name=None):
720 """Returns array of data values with index between first and last, inclusive.""" 721 signal_ix = self.QubX.DataSource.signal 722 if signal_name: 723 signal_ix = [self.view.scope.get(i, 'Name') for i in xrange(1, self.view.scope.size)].index(signal_name) 724 segs = self.view.get_segmentation_indexed(first=first, last=last, signal=signal_ix) 725 if segs: 726 return numpy.hstack([seg.get_samples().samples for seg in segs]) 727 return numpy.array([])
728 - def add_computed_signal(self):
729 new_signal_name = qubx.pyenvGTK.prompt_entry('Name of the new signal:', 'Untitled', str, str) 730 if not new_signal_name: return 731 name_hint = "Signal names: %s\n" % ', '.join(SafeName(name) for name in self.file.analog_sources.keys()) 732 dlg = qubx.pyenvGTK.HelpTestDialog(self.file.accept_computed, "Enter an expression in terms of t (seconds) and the other signal names", "%s =" % new_signal_name, 733 COMPUTED_SIGNAL_HELP+name_hint+qubx.pyenv.PYTHON_HELP, self.help_bind, self.help_write_test) 734 response = dlg.run("") 735 expr = dlg.expr 736 dlg.destroy() 737 if response == gtk.RESPONSE_ACCEPT: 738 qubx.pyenv.env.OnScriptable('QubX.Data.file.signals.append({"Name" : %s, "Computed as" : %s})' % (repr(new_signal_name), repr(expr))) 739 self.file.signals.append({'Name' : new_signal_name, 'Computed as' : expr})
740 - def help_bind(self, func):
741 def test_computed_signal(**signal_vals): 742 return eval(func.expr, qubx.pyenv.env.globals, signal_vals)
743 qubx.global_namespace.test_computed_signal = test_computed_signal
744 - def help_write_test(self, func):
745 args = [(name, i+1) for i, name in enumerate(func.args)] 746 return "test_computed_signal(%s)" % (", ".join("%s=%s" % (arg, repr(val)) for arg, val in args))
747 748 COMPUTED_SIGNAL_HELP = """For example: 749 750 Current / Gain 751 Current / (Voltage - (-60.0)) 752 753 """ 754 755 756 qubx.dataGTK.Tools.register('list', 'Import...', lambda item: qubx.global_namespace.QubX.Data.import_list()) 757 qubx.dataGTK.Tools.register('list', 'Export...', lambda item: qubx.global_namespace.QubX.Data.export_list()) 758 qubx.dataGTK.Tools.register('list', 'Add onscreen selection...', lambda item: qubx.global_namespace.QubX.Data.list_add_screen()) 759 qubx.dataGTK.Tools.register('list', 'Copy sel(s) to list...', lambda item: qubx.global_namespace.QubX.Data.copy_list()) 760 qubx.dataGTK.Tools.register('list', 'Measure selections', lambda item: qubx.global_namespace.QubX.Data.measure_list()) 761 qubx.dataGTK.Tools.register('list', 'Split list by Group...', lambda item: qubx.global_namespace.QubX.Data.split_list())
762 763 764 -class DataSourceFace(qubx.faces.Face):
765 """Panel with source signal and file/screen; monitors QubX.DataSource and handles presets.""" 766 767 __explore_featured = ['DataSource', 'chkFile', 'chkScreen', 'chkList', 'chkFilter', 'slideFreq', 'txtFreq', 'mnuSignal'] 768
769 - def __init__(self, DataSource, global_name=""):
770 qubx.faces.Face.__init__(self, 'Data Source', global_name=global_name) 771 self.DataSource = DataSource 772 self.__ref = Reffer() 773 DataSource.OnChangeSource += self.__ref(self.__onChangeSource) 774 DataSource.OnChangeSignal += self.__ref(self.__onChangeSignal) 775 DataSource.OnChangeSignals += self.__ref(self.__onChangeSignals) 776 DataSource.OnChangeSamples += self.__ref(self.__onChangeSamples) 777 line = pack_item(gtk.HBox(), self) 778 pack_label('Data source:', line) 779 self.mnuSignal = pack_item(gtk.combo_box_new_text(), line, expand=True) 780 line = pack_item(gtk.HBox(), self) 781 self.chkFile = pack_radio('Whole File', line, expand=True, 782 active=(DataSource.source == qubx.dataGTK.DATASOURCE_FILE), 783 on_toggle=bind_with_args_before(self.__onToggleSource, qubx.dataGTK.DATASOURCE_FILE)) 784 self.chkScreen = pack_radio('Screen', line, expand=True, group=self.chkFile, 785 active=(DataSource.source == qubx.dataGTK.DATASOURCE_SCREEN), 786 on_toggle=bind_with_args_before(self.__onToggleSource, qubx.dataGTK.DATASOURCE_SCREEN)) 787 self.chkList = pack_radio('List', line, expand=True, group=self.chkFile, 788 active=(DataSource.source == qubx.dataGTK.DATASOURCE_LIST), 789 on_toggle=bind_with_args_before(self.__onToggleSource, qubx.dataGTK.DATASOURCE_LIST)) 790 line = pack_item(gtk.HBox(), self) 791 self.chkFilter = pack_check('Filter', line, on_toggle=self.__onToggleFilter) 792 self.slideFreq = pack_item(qubx.GTK.Pow10Slider(powers=3), line, expand=True) 793 self.slideFreq.OnChange += self.__ref(self.__onSlideFreq) 794 self.txtFreq = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=4), line) 795 self.txtFreq.OnChange += self.__ref(self.__onChangeFreq) 796 pack_label('kHz', line) 797 self.__onChangeSignals(self.DataSource.signals) 798 self.__profile = qubx.settings.SettingsMgr['DataSource'].active 799 qubx.settings.SettingsMgr['DataSource'].OnSet = self.__ref(self.__onPreSet) 800 if self.__profile.find('Source').data: 801 DataSource.source = self.__profile['Source'].data[0] 802 if self.__profile.find('Signal').data: 803 DataSource.signal = self.__profile['Signal'].data[0] 804 self.mnuSignal.connect('changed', self.__onSignalChanged)
805 - def __onPreSet(self, settings, updates):
806 if updates.find('Source').data: 807 self.DataSource.source = updates['Source'].data[0] 808 if updates.find('Signal').data: 809 self.DataSource.signal = updates['Signal'].data[0]
810 - def __onChangeSource(self, source):
811 if source == qubx.dataGTK.DATASOURCE_FILE: 812 self.chkFile.set_active(True) 813 elif source == qubx.dataGTK.DATASOURCE_SCREEN: 814 self.chkScreen.set_active(True) 815 else: 816 self.chkList.set_active(True) 817 self.__profile['Source'].data = source
818 - def __onChangeSignal(self, signal):
819 self.mnuSignal.set_active(signal) 820 self.__profile['Signal'].data = signal
821 - def __onChangeSignals(self, signals):
822 for i in reversed(xrange(len(self.mnuSignal.get_model()))): 823 self.mnuSignal.remove_text(i) 824 for name in signals: 825 self.mnuSignal.append_text(name) 826 self.mnuSignal.set_active(self.DataSource.signal)
827 - def __onChangeSamples(self):
828 if self.DataSource.data and self.DataSource.data.signals: 829 self.chkFilter.set_active(self.DataSource.data.file.signals.get(self.DataSource.signal, 'Filter')) 830 self.txtFreq.value = self.DataSource.data.file.signals.get(self.DataSource.signal, 'Filter Freq') 831 self.slideFreq.top = .5 * 1e-3 / self.DataSource.data.file.sampling 832 self.slideFreq.value = self.txtFreq.value
833 - def __onSignalChanged(self, mnu):
834 sig = mnu.get_active() 835 if (sig >= 0) and (sig != self.DataSource.signal): 836 self.DataSource.signal = sig 837 qubx.pyenv.env.OnScriptable('QubX.DataSource.signal = %s' % sig)
838 - def __onToggleSource(self, chk, source):
839 if chk.get_active(): 840 self.DataSource.source = source 841 qubx.pyenv.env.OnScriptable('QubX.DataSource.source = %s' % ['DATASOURCE_FILE', "DATASOURCE_SCREEN", "DATASOURCE_LIST"][source])
842 - def __onToggleFilter(self, chk):
843 if self.DataSource.data: 844 self.DataSource.data.file.signals.set(self.DataSource.signal, 'Filter', chk.get_active()) 845 qubx.pyenv.env.OnScriptable('QubX.DataSource.data.file.signals[QubX.DataSource.signal, "Filter"] = %s' % chk.get_active())
846 - def __onSlideFreq(self, slide, val):
847 self.txtFreq.value = val 848 self.__onChangeFreq(self.txtFreq, val)
849 - def __onChangeFreq(self, txt, val):
850 if self.DataSource.data: 851 qubx.pyenv.env.OnScriptable('QubX.DataSource.data.file.signals[QubX.DataSource.signal, "Filter Freq"] = %s' % val) 852 self.DataSource.data.file.signals.set(self.DataSource.signal, 'Filter Freq', val)
853