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

Source Code for Module qubx.tableGTK

  1  """GTK components for L{qubx.table}. 
  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  from cStringIO import StringIO 
 23  import qubx.notebook 
 24  import qubx.notebookGTK 
 25  import qubx.pyenv 
 26  import qubx.pyenvGTK 
 27  from qubx.util_types import * 
 28  from qubx.GTK import * 
 29  from qubx.table import * 
 30  import qubx.accept 
 31  import qubx.table 
 32  import gobject 
 33  import gtk 
 34  from gtk import gdk 
 35  from qubx.pyenvGTK import prompt_entry 
 36  from qubx.toolspace import ColorInfo 
 37   
 38  COLOR_TABLE_FILE_MENU = ('tableGTK.file_menu', (0,1,0,1)) 
 39  ColorInfo[COLOR_TABLE_FILE_MENU[0]].label = 'Tables file menu' 
 40  COLOR_MNU_CHECKED = ('tableGTK.checked_menu', (1, .7, 0, 1)) 
 41  ColorInfo[COLOR_MNU_CHECKED[0]].label = 'Tables check-column menu' 
 42   
43 -def run_later_dbg(*args, **kw):
44 traceback.print_stack() 45 print 46 gobject.idle_add(*args, **kw)
47 qubx.table.run_later = gobject.idle_add # run_later_dbg 48
49 -class TableView(gtk.TreeView):
50 """View/controller for a L{qubx.table.Table}. 51 52 @ivar showing: (def True) if False, OnSelect edits are via dialog 53 @ivar table: L{qubx.table.Table} 54 @ivar OnEdit: L{WeakEvent}(label) in case you want to seal an L{qubx.undo.UndoStack} 55 @ivar OnSortColumnChanged: L{WeakEvent}(TableView) 56 """ 57 58 __explore_featured = ['global_name', 'OnEdit', 'OnSortColumnChanged', 'showing', 'table', 'model', '_ix_of_col', 59 'dispose', 'sorted_ix', 'raw_ix', 'get_column_ix', 'set_index_widget'] 60
61 - def __init__(self, table, global_name=''):
62 self.ref = Reffer() 63 self.global_name = global_name 64 self.OnEdit = WeakEvent() # (lbl) 65 self.OnSortColumnChanged = WeakEvent() 66 self.showing = True 67 self.table = table 68 self.model = self._create_model() 69 self._ext_sel = False # flag to avoid propagating external selection events 70 self.__col_events = [] 71 self.__sort_cols = [] 72 gtk.TreeView.__init__(self, self.model) 73 self.set_headers_visible(True) 74 self.get_selection().set_mode(gtk.SELECTION_SINGLE) 75 self.set_rules_hint(True) 76 self._setup_spares() 77 self._add_events() 78 self._ix_of_col = {} 79 self.__select_changed_cursor = False 80 self.__waiting_first_cursor_change = True 81 self._row = self._col = None # click-selection 82 self.__ix_checked = self.get_column_ix('__ix_checked') 83 for field_name in table.fields: 84 self.get_column_ix(field_name) 85 for i in xrange(table.size): 86 self._onInsert(i, False) 87 self.connect('button_press_event', self._onButtonPress) 88 #self.connect('focus_in_event', self._onFocus, True) 89 #self.connect('focus_out_event', self._onFocus, False) 90 self.connect('cursor_changed', self._onCursorChange) 91 self.connect('row_activated', self._onRowActivated) 92 self.set_headers_clickable(True)
93 - def dispose(self):
94 self._remove_events() 95 for c in self.__sort_cols: 96 self.model.set_sort_func(c, no_sort) 97 self.ref.clear() 98 self.table = None
99 - def sorted_ix(self, raw):
100 if self.table.sortable: 101 return self.model.convert_child_path_to_path((raw,))[0] 102 else: 103 return raw
104 - def raw_ix(self, sorted):
105 if self.table.sortable: 106 return self.model.convert_path_to_child_path((sorted,))[0] 107 else: 108 return sorted
109 - def get_column_ix(self, nm):
110 """Returns the index of the named column; adds the column if none.""" 111 try: 112 return self._ix_of_name[nm] 113 except: 114 col_options = {} 115 if (nm == '__ix_checked') or (self.table.accept[nm] in (bool, acceptBool)): 116 ix = self._spare_bool.pop() 117 self._bool_names.append(nm) 118 rend = gtk.CellRendererToggle() 119 rend.set_property('activatable', True) 120 self.__col_events.append( (rend, rend.connect('toggled', self.__onToggle, ix)) ) 121 else: 122 ix = self._spare.pop() 123 rend = gtk.CellRendererText() 124 if self.table.choices[nm] is None: 125 rend.set_property('editable', True) 126 self.__col_events.append( (rend, rend.connect('editing_started', self._onEditingStarted, ix)) ) 127 if self.table.accept[nm] != qubx.accept.acceptNothing: 128 self.__col_events.append( (rend, rend.connect('edited', self._onEdit, ix)) ) 129 col_options['text'] = ix 130 try: 131 units = self.table.units[nm] 132 except: 133 units = "" 134 nmu = nm if (not units) else '%s [%s]' % (nm, units) 135 col = gtk.TreeViewColumn(gtk_literally(nmu), rend, **col_options) 136 if isinstance(rend, gtk.CellRendererToggle): 137 col.add_attribute( rend, "active", ix) 138 if nm == '__ix_checked': 139 self.table.add_notebook_item('Pick columns...', qubx.notebookGTK.NbSubTablePicker(self.table, self.table.global_name and ("%s.notebook['Pick columns...']"%self.table.global_name) or "", self.table.nb_get_caption)) 140 hdr = gtk.HBox() 141 hdr.show() 142 self.mnuChecked = gtk.Menu() 143 btn = qubx.toolspace.Button_Popup(self.mnuChecked, COLOR_MNU_CHECKED, 'Act on all checked rows...', self.ref(self.__onPopupChecked))#, qubx.toolspace.COLOR_WHITE) 144 btn.show() 145 hdr.pack_start(btn, False, True) 146 self.btnChecked = btn 147 btn = qubx.notebookGTK.Button_Notebook(scale=0.75) 148 btn.items = self.table.notebook_items 149 btn.show() 150 hdr.pack_start(btn, False, True) 151 self.btnNotebook = btn 152 col.set_widget(hdr) 153 self.tvcol_checked, self.tvcol_event = col, col.connect('clicked', self.__onClickMenus) # lambda col: hdr.do_popup()) 154 155 self.nbTable = self.table.notebook['Default'] 156 elif self.table.sortable: 157 self.model.set_sort_func(ix, self.__sort_func, ix) 158 col.set_sort_column_id(ix) 159 self.__sort_cols.append(ix) 160 self.append_column(col) 161 self._col_of_name[nm] = col 162 self._ix_of_name[nm] = ix 163 self._name_of_col[ix] = nm 164 self._used_col.append(ix) 165 self._ix_of_col[col] = ix 166 if self.table.sortable and (ix == 0): 167 self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) 168 return ix
169 - def __sort_func(self, model, iter1, iter2, col_ix):
170 field = self._name_of_col[col_ix] 171 val1, val2 = (self.table[model.get_path(it)[0], field] for it in (iter1, iter2)) 172 if val1 < val2: 173 return -1 174 elif val1 == val2: 175 return 0 176 return 1
177 - def __onClickMenus(self, col):
178 x, y, mods = self.btnNotebook.window.get_pointer() 179 if x < 0: 180 self.btnChecked.do_popup() 181 else: 182 self.btnNotebook.do_popup()
183 - def set_index_widget(self, widget, onclick):
184 self.tvcol_checked.disconnect(self.tvcol_event) 185 self.tvcol_checked.set_widget(widget) 186 onclick = self.ref(onclick) 187 self.tvcol_event = self.tvcol_checked.connect('clicked', lambda col: onclick())
188 - def _create_model(self):
189 self.model_raw = gtk.ListStore(*((self.table.max_fields*[str]) + (self.table.max_bools*[bool]))) 190 if self.table.sortable: 191 sorted = gtk.TreeModelSort(self.model_raw) 192 sorted.connect('sort_column_changed', self.__onSortColumnChanged) 193 return sorted 194 else: 195 return self.model_raw
196 - def __onSortColumnChanged(self, smodel):
197 self.OnSortColumnChanged(self)
198 - def _setup_spares(self):
199 self._col_of_name = {} 200 self._ix_of_name = {} 201 self._name_of_col = {} 202 self._used_col = [] 203 self._spare = [x for x in xrange(self.table.max_fields)] 204 self._spare.reverse() 205 self._spare_bool = [x for x in xrange(self.table.max_fields, self.table.max_fields+self.table.max_bools)] 206 self._spare_bool.reverse() 207 self._bool_names = []
208 - def _add_events(self):
209 self.table.OnAddField += self.ref(self._onAddField), 'TableView<%s>.onAddField' % self.table.label 210 self.table.OnRemovingField += self.ref(self.__onRemovingField) 211 self.table.OnInsert += self.ref(self._onInsert), 'TableView<%s>.onInsert' % self.table.label 212 self.table.OnRemoved += self.ref(self._onRemoved), 'TableView<%s>.onRemoved' % self.table.label 213 self.table.OnSet += self.ref(self._onSet), 'TableView<%s>.onAddSet' % self.table.label 214 self.table.OnSelect += self.ref(self._onSelect), 'TableView<%s>.onSelect' % self.table.label 215 self.table.OnChecked += self.ref(self._onChecked)
216 - def _remove_events(self):
217 self.table.OnAddField -= self.ref(self._onAddField) 218 self.table.OnRemovingField -= self.ref(self.__onRemovingField) 219 self.table.OnInsert -= self.ref(self._onInsert) 220 self.table.OnRemoved -= self.ref(self._onRemoved) 221 self.table.OnSet -= self.ref(self._onSet) 222 self.table.OnSelect -= self.ref(self._onSelect) 223 self.table.OnChecked -= self.ref(self._onChecked) 224 for col, evt in self.__col_events: 225 col.disconnect(evt) 226 self.tvcol_checked.disconnect(self.tvcol_event)
227 - def _onEditingStarted(self, cell, editable, path_string, col):
228 self._row = self.raw_ix(int(path_string)) 229 self._col = col
230 #?# self.table.select(self._row, self._name_of_col[self._col], sender=self)
231 - def _onEdit(self, cell, path_string, new_text, col):
232 i = self.raw_ix(int(path_string)) 233 field = self._name_of_col[col] 234 try: 235 val = self.table.accept[field](new_text) 236 if self.table.global_name: 237 qubx.pyenv.env.scriptable_if_matching('%s[%i, %s] = %s' % 238 (self.table.global_name, i, repr(field), repr(val)), # self.table.format[field](val)), 239 [(self.table.global_name, self.table)]) 240 self.table.set(i, field, val) 241 self.OnEdit('table edit') 242 except: 243 pass
244 - def __onToggle(self, cell, path_string, col):
245 i = self.raw_ix(int(path_string)) 246 field = self._name_of_col[col] 247 if field == '__ix_checked': 248 val = not self.table.checked[i] 249 if self.table.global_name: 250 qubx.pyenv.env.scriptable_if_matching('%s.set_checked(%i, %s)' % (self.table.global_name, i, repr(val)), 251 [(self.table.global_name, self.table)]) 252 self.table.set_checked(i, val) 253 else: 254 val = not self.table[i, field] 255 if self.table.global_name: 256 qubx.pyenv.env.scriptable_if_matching('%s[%i, %s] = %s' % (self.table.global_name, i, repr(field), repr(val)), 257 [(self.table.global_name, self.table)]) 258 self.table[i, field] = val 259 self.OnEdit('table edit')
260 - def _onFocus(self, selfy, event, focused):
261 pass # self._onCursorChange(self, focused)
262 - def _onCursorChange(self, selfy, focused=True):
263 if self.__waiting_first_cursor_change: # bogus cursor-change on show 264 self.__waiting_first_cursor_change = False 265 return 266 if self._ext_sel: 267 return 268 if not focused: 269 #self.table.select(-1, sender=self) 270 return 271 path, col = self.get_cursor() 272 if path is None: 273 self._row = None 274 else: 275 self._row = self.raw_ix(path[0]) 276 if col is None: 277 pass # leave it as before 278 else: 279 self._col = self._ix_of_col[col] 280 field = (not (self._col is None)) and self._name_of_col[self._col] or None 281 if self.__select_changed_cursor: 282 self.__select_changed_cursor = False 283 else: 284 self.table.select(self._row, field, sender=self)
285 - def _onRowActivated(self, tree, path, col):
286 if (path is None) or (col is None): 287 return 288 field = (not (self._col is None)) and self._name_of_col[self._col] or None 289 self.table.OnDoubleClick(self.raw_ix(path[0]), field)
290 - def _onAddField(self, name):
291 col = self.get_column_ix(name) 292 if name in self._bool_names: 293 for i in xrange(self.table.size): 294 self.model_raw.set_value(self.model_raw.get_iter((i,)), col, self.table[i, name]) 295 else: 296 format = self.table.format[name] 297 for i in xrange(self.table.size): 298 self.model_raw.set_value(self.model_raw.get_iter((i,)), col, format(self.table.get(i, name)))
299 - def __onRemovingField(self, name):
300 col = self._col_of_name[name] 301 ix = self._ix_of_name[name] 302 self.remove_column(col) 303 del self._col_of_name[name] 304 del self._ix_of_name[name] 305 del self._name_of_col[ix] 306 self._used_col.remove(ix) 307 if name in self._bool_names: 308 self._bool_names.remove(name) 309 self._spare_bool.append(ix) 310 else: 311 self._spare.append(ix)
312 - def _onInsert(self, i, undoing=False): #####
313 t = self.table 314 def getitem(c): 315 if not (c in self._name_of_col): 316 if c < t.max_fields: 317 return "" 318 else: 319 return False 320 field = self._name_of_col[c] 321 if field == '__ix_checked': 322 return t.checked[i] 323 elif field in self._bool_names: 324 return t.get(i, field) 325 else: 326 return t.format[field](t.get(i, field))
327 row = [getitem(c) for c in xrange(t.max_fields + t.max_bools)] 328 self.model_raw.insert(i, row)
329 - def _onRemoved(self, i, undoing=False):
330 self.model_raw.remove(self.model_raw.get_iter((i,))) 331 if self._row == i: 332 self.table.select(None, sender=self)
333 - def _onSet(self, i, field, val, prev, undoing=False):
334 iter = self.model_raw.get_iter((i,)) 335 col = self.get_column_ix(field) 336 if field in self._bool_names: 337 self.model_raw.set_value(iter, col, val) 338 else: 339 self.model_raw.set_value(iter, col, self.table.format[field](val))
340 - def _onSelect(self, i, field, sender):
341 if (sender != self) and (0 <= i < len(self.model)): 342 if self.showing: 343 self._ext_sel = True 344 self.grab_focus() 345 col = field and self._col_of_name[field] 346 #self.set_cursor((i,), col, True) 347 self._ext_sel = False 348 self.__select_changed_cursor = True 349 gobject.idle_add(self.set_cursor, (self.sorted_ix(i),), col, True) # one click to select! 350 elif field: 351 new_val = prompt_entry("%s[%i] = " % (field, i), self.table[i, field], self.table.accept[field], self.table.format[field], 352 "%s - Editing %s" % (qubx.global_namespace.QubX.appname, self.table.label)) 353 if not (new_val is None): 354 qubx.pyenv.env.scriptable_if_matching('%s[%i, %s] = %s' % 355 (self.table.global_name, i, repr(field), repr(new_val)), 356 [(self.table.global_name, self.table)]) 357 self.table[i, field] = new_val 358 self.OnEdit('table edit')
359 # selection -> table?
360 - def _onChecked(self, i, checked):
361 self.model_raw.set_value(self.model_raw.get_iter((i,)), self.__ix_checked, checked)
362 - def _onButtonPress(self, selfy, event):
363 tup = self.get_path_at_pos(int(event.x), int(event.y)) 364 if tup is None: 365 return 366 path, col, cellx, celly = tup 367 field = self._name_of_col[ self._ix_of_col[col] ] 368 choices = self.table.choices[field] if (field != '__ix_checked') else None 369 if not (choices is None): 370 popup = gtk.Menu() 371 for c in choices: 372 build_menuitem(self.table.format[field](c), self.ref(bind_with_args_before(self._onChoice, path, field, c)), menu=popup) 373 popup.popup(None, None, None, 0, event.time)
374 - def _onChoice(self, item, path, field, choice):
375 self.table.set(self.raw_ix(path[0]), field, choice) 376 self.OnEdit('table edit')
377 - def __onPopupChecked(self):
378 self.mnuChecked.foreach(lambda item: self.mnuChecked.remove(item)) 379 build_menuitem('Un-check all', self.ref(self.__onClickUncheck), menu=self.mnuChecked) 380 build_menuitem('Check rows if...', self.ref(self.__onClickCheckIf), menu=self.mnuChecked) 381 build_menuitem('Invert selection', self.ref(self.__onClickInvert), menu=self.mnuChecked) 382 build_menuitem('Delete rows', self.ref(self.__onClickDel), menu=self.mnuChecked) 383 for field in self.table.fields: 384 if (field == 'Index') or (self.table.accept[field] == acceptNothing): 385 continue 386 build_menuitem('Edit %s...' % field, self.ref(bind(self.__onClickEdit, field)), menu=self.mnuChecked) 387 return True
388 - def __onClickUncheck(self, item):
389 t = self.table 390 if t.global_name: 391 qubx.pyenv.env.scriptable_if_matching('for i in xrange(%s.size): %s.set_checked(i, False)' % (t.global_name, t.global_name), 392 [(t.global_name, t)]) 393 for i in xrange(t.size): 394 if t.checked[i]: 395 t.set_checked(i, False)
396 - def __onClickCheckIf(self, item):
397 self.check_rows_if()
398 - def __onClickInvert(self, item):
399 t = self.table 400 if t.global_name: 401 qubx.pyenv.env.scriptable_if_matching('for i in xrange(%s.size): %s.set_checked(i, not %s.checked[i])' % (t.global_name, t.global_name, t.global_name), 402 [(t.global_name, t)]) 403 for i in xrange(t.size): 404 t.set_checked(i, not t.checked[i])
405 - def __onClickDel(self, item):
406 t = self.table 407 if qubx.GTK.PromptChoices("Really delete the checked rows? This can't be undone.", title="Confirm delete rows"): 408 qubx.pyenv.env.scriptable_if_matching("""for i in reversed(xrange(%s.size)): 409 if %s.checked[i]: 410 %s.remove(i)""" % (t.global_name, t.global_name, t.global_name), 411 [(t.global_name, t)]) 412 first = 0 413 while first < t.size: 414 if t.checked[first]: 415 break 416 first += 1 417 for i in reversed(xrange(t.size)): 418 if t.checked[i] and t.user_can_remove[i]: 419 t.remove(i, False, i==first)
420 - def __onClickEdit(self, field):
421 t = self.table 422 chk_ixs = [i for i in xrange(t.size) if t.checked[i]] 423 if not chk_ixs: 424 qubx.GTK.ShowMessage("Please mark some rows first.", title='Editing %s - no rows chosen' % field) 425 return 426 old_val = t[chk_ixs[0], field] 427 new_val = prompt_entry('Set "%s" of all checked rows to' % field, old_val, t.accept[field], t.format[field], 428 "%s - Editing %s" % (qubx.global_namespace.QubX.appname, self.table.label)) 429 if not (new_val is None): 430 qubx.pyenv.env.scriptable_if_matching("""for i in xrange(%s.size): 431 if %s.checked[i]: 432 %s[i, %s] = %s""" % (t.global_name, t.global_name, t.global_name, repr(field), repr(new_val)), 433 [(t.global_name, t)]) 434 for ix in chk_ixs: 435 t[ix, field] = new_val
436 - def check_rows_if(self, copy_rows_type=None, group=None, inverse=False, criteria=None):
437 crt, grp, inv, cta = prompt_check_rows(self.table, copy_rows_type, group, inverse, criteria) 438 if crt is None: # dialog cancel 439 return 440 QubX = qubx.global_namespace.QubX 441 if self.table.global_name: 442 if crt == COPY_ROWS_ALL: 443 qubx.pyenv.env.scriptable_if_matching('%s.check_rows_if(copy_rows_type=COPY_ROWS_ALL)' 444 % self.table.global_name, 445 [(self.table.global_name, self.table)]) 446 elif crt == COPY_ROWS_GROUP: 447 qubx.pyenv.env.scriptable_if_matching('%s.check_rows_if(copy_rows_type=COPY_ROWS_GROUP, group=%s, inverse=%s)' 448 % (self.table.global_name, 449 repr(grp), repr(inv)), 450 [(self.table.global_name, self.table)]) 451 elif crt == COPY_ROWS_CRITERIA: 452 qubx.pyenv.env.scriptable_if_matching('%s.check_rows_if(copy_rows_type=COPY_ROWS_CRITERIA, criteria=%s)' 453 % (self.table.global_name, 454 repr(cta)), 455 [(self.table.global_name, self.table)]) 456 elif crt == COPY_ROWS_CHECKED: 457 qubx.pyenv.env.scriptable_if_matching('%s.check_rows_if(copy_rows_type=COPY_ROWS_CHECKED)' 458 % self.table.global_name, 459 [(self.table.global_name, self.table)]) 460 self.table.check_rows_if(copy_rows_type=crt, group=grp, inverse=inv, criteria=cta)
461 462
463 -class TableViewCtrls(gtk.HBox):
464 """A row of controls for the top of some L{TableView}s.""" 465 466 __explore_featured = ['ADD', 'REMOVE', 'ADD_FIELD', 'CLOSE', 'FILE_MENU', 'COPY_ROWS', 'CALCULATE', 'PLOT', 467 'table', 'undoStack', 'OnClickClose', 'actions', 'need_sel', 'add_row', 'remove_row', 468 'add_field', 'copy_rows', 'calculate', 'plot', 'acceptExpr', 'save'] 469 470 ADD = 1 471 REMOVE = 2 472 ADD_FIELD = 3 473 CLOSE = 4 474 FILE_MENU = 5 475 COPY_ROWS = 6 476 CALCULATE = 7 477 PLOT = 8
478 - def __init__(self, table, undoStack=None, *args):
479 """ 480 @param table: L{qubx.table.Table} 481 @param undoStack: assuming there is already a L{qubx.table.TableUndo}(table, undoStack), any edits from here will be sealed 482 @param ...: constants ADD or REMOVE (buttons), any string (label), any gtk.Widget, or pair label, action (button that calls action()) 483 """ 484 gtk.HBox.__init__(self) 485 self.__ref = Reffer() 486 self.table = table 487 self.undoStack = undoStack 488 self.OnClickClose = WeakEvent() # (TableViewCtrls, Table) 489 self.actions = {} 490 self.need_sel = [] 491 for x in args: 492 if isinstance(x, str): 493 pack_label(x, self) 494 elif isinstance(x, gtk.Widget): 495 pack_item(x, self) 496 elif x == self.ADD: 497 x = ('Add row', self.add_row) 498 elif x == self.REMOVE: 499 x = ('Del row', self.remove_row, True) 500 elif x == self.ADD_FIELD: 501 x = ('Add a column...', self.add_field) 502 elif x == self.COPY_ROWS: 503 x = ('Copy row(s) to Table...', self.copy_rows) 504 elif x == self.CALCULATE: 505 x = ('Calculate...', self.calculate) 506 elif x == self.PLOT: 507 x = ('Plot...', self.plot) 508 if isinstance(x, tuple): 509 txt = x[0] 510 proc = x[1] 511 need_sel = False 512 if len(x) >= 3: 513 need_sel = x[2] 514 btn = pack_button(txt, self, on_click=lambda b: self.actions[b]()) 515 self.actions[btn] = proc 516 if need_sel: 517 btn.set_sensitive(False) 518 self.need_sel.append(btn) 519 elif x == self.FILE_MENU: 520 self.mnuFile = gtk.Menu() 521 build_menuitem('New Table...', self.__ref(lambda item: qubx.pyenv.env.globals['QubX'].new_table()), menu=self.mnuFile) 522 build_menuitem('Open...', self.__ref(lambda item: qubx.pyenv.env.globals['QubX'].Data.open()), menu=self.mnuFile) 523 build_menuitem('Save as...', self.__ref(self.__onClickSaveAs), menu=self.mnuFile) 524 item = build_menuitem('Close', self.__ref(lambda b: self.OnClickClose(self, self.table)), menu=self.mnuFile) 525 self.btnFile = pack_item(qubx.toolspace.Button_Popup_PNG(self.mnuFile, os.path.join(qubx.global_namespace.app_path, 'icons', 'folder.png'), "File menu"), self) 526 elif x == self.CLOSE: 527 pack_button('X', self, at_end=True, on_click=lambda b: self.OnClickClose(self, self.table)) 528 self.table.OnSelect += self.__ref(self._onSelect)
529 - def _onSelect(self, i, field, sender):
530 self.row = i 531 have_sel = not (self.row is None) 532 for b in self.need_sel: 533 b.set_sensitive(have_sel)
534 - def add_row(self):
535 self.table.append({}) 536 if self.table.global_name: 537 qubx.pyenv.env.scriptable_if_matching('%s.append({})' % self.table.global_name, 538 [(self.table.global_name, self.table)]) 539 if self.undoStack: 540 self.undoStack.seal_undo('Add row')
541 - def remove_row(self):
542 r = self.row 543 if self.table.user_can_remove[r]: 544 self.table.remove(r) 545 if self.table.global_name: 546 qubx.pyenv.env.scriptable_if_matching('%s.remove(%i)' % (self.table.global_name, r), 547 [(self.table.global_name, self.table)]) 548 if self.undoStack: 549 self.undoStack.seal_undo('Del row')
550 - def add_field(self):
551 dlg = qubx.GTK.NumEntryDialog('Add a column...', qubx.GTK.get_active_window(), 552 'Column name:', 'Untitled') 553 if dlg.run() == gtk.RESPONSE_ACCEPT: 554 name = dlg.value 555 if not (name in self.table.fields): 556 self.table.add_field(name, 0.0, acceptFloat, '%.6g', '') 557 dlg.destroy()
558 - def copy_rows(self, table_name=None, copy_rows_type=None, group=None, inverse=False, criteria=None):
559 tbn, crt, grp, inv, cta = prompt_copy_rows(self.table, self.table.label, [], 560 table_name, copy_rows_type, group, inverse, criteria) 561 if crt is None: # dialog cancel 562 return 563 from_table = self.table 564 QubX = qubx.global_namespace.QubX 565 result = QubX.new_table(tbn, initial_row=False) # emits scriptable QubX.new_table(...) 566 if self.table.global_name: 567 if crt == COPY_ROWS_ALL: 568 qubx.pyenv.env.scriptable_if_matching('QubX.Tables.find_table(%s).copy_rows_from(%s, copy_rows_type=COPY_ROWS_ALL)' 569 % (repr(tbn), 570 self.table.global_name), 571 [(self.table.global_name, self.table)]) 572 elif crt == COPY_ROWS_GROUP: 573 qubx.pyenv.env.scriptable_if_matching('QubX.Tables.find_table(%s).copy_rows_from(%s, copy_rows_type=COPY_ROWS_GROUP, group=%s, inverse=%s)' 574 % (repr(tbn), 575 self.table.global_name, 576 repr(grp), repr(inv)), 577 [(self.table.global_name, self.table)]) 578 elif crt == COPY_ROWS_CRITERIA: 579 qubx.pyenv.env.scriptable_if_matching('QubX.Tables.find_table(%s).copy_rows_from(%s, copy_rows_type=COPY_ROWS_CRITERIA, criteria=%s)' 580 % (repr(tbn), 581 self.table.global_name, 582 repr(cta)), 583 [(self.table.global_name, self.table)]) 584 elif crt == COPY_ROWS_CHECKED: 585 qubx.pyenv.env.scriptable_if_matching('QubX.Tables.find_table(%s).copy_rows_from(%s, copy_rows_type=COPY_ROWS_CHECKED)' 586 % (repr(tbn), 587 self.table.global_name), 588 [(self.table.global_name, self.table)]) 589 result.copy_rows_from(from_table, copy_rows_type=crt, group=grp, inverse=inv, criteria=cta) 590 return result
591 - def calculate(self, field_name=None, expr=None):
592 fn = field_name 593 ex = expr 594 if (fn is None) or (ex is None): 595 if fn is None: 596 fn = 'Output' 597 if ex is None: 598 ex = '' 599 QubX = qubx.pyenv.env.globals['QubX'] 600 dlg = gtk.Dialog('%s - Calculate...'%QubX.appname, qubx.GTK.get_active_window(), gtk.DIALOG_MODAL) 601 line = pack_item(gtk.HBox(), dlg.vbox) 602 pack_label('Replace data in column:', line) 603 txtField = pack_item(qubx.GTK.NumEntry(fn, str), line, expand=True) 604 line = pack_item(gtk.HBox(), dlg.vbox) 605 pack_label('with:', line) 606 txtExpr = pack_item(qubx.GTK.NumEntry(ex, self.acceptExpr), line, expand=True) 607 btnHelp = pack_item(qubx.pyenvGTK.HelpTestButton(txtExpr), line) 608 btnHelp.caption = 'Enter a numeric expression in terms of field_name' 609 btnHelp.help_msg = CALCULATE_HELP 610 btnHelp.bind = lambda expr: qubx.pyenv.env.globals.__setitem__('calc_value_of_row', lambda r: calc_expr_values(self.table, expr, [r])[0]) 611 btnHelp.write_test = lambda expr: 'print calc_value_of_row(0)' 612 dlg.add_button('Cancel', gtk.RESPONSE_REJECT) 613 dlg.add_button('OK', gtk.RESPONSE_ACCEPT) 614 response = dlg.run() 615 dlg.destroy() 616 if response != gtk.RESPONSE_ACCEPT: 617 return 618 fn = txtField.value 619 ex = txtExpr.value 620 if (not fn) or (not ex): 621 return 622 if self.table.global_name: 623 qubx.pyenv.env.scriptable_if_matching('for i, val in enumerate(%s.calc_expr_values(%s)): %s[i, %s] = val' 624 % (self.table.global_name, repr(ex), self.table.global_name, repr(fn)), 625 [(self.table.global_name, self.table)]) 626 for i, val in enumerate(self.table.calc_expr_values(ex)): 627 self.table[i, fn] = val
628 - def plot(self):
629 if qubx.pyenv.env.globals['QubX'].Figures.Charts.add_plot(self.table.label): 630 qubx.pyenv.env.globals['QubX'].show_charts()
631 - def acceptExpr(self, expr):
632 sample = calc_expr_values(self.table, expr, [0])[0] 633 return expr
634 - def __onClickSaveAs(self, item):
635 ### TODO: remember table saveas path 636 fname = SaveAs("Save table as...", qubx.pyenv.env.globals['documents_path'], self.table.label, self.save, allow_all_files=True)
637 - def save(self, fname):
638 qubx.pyenv.env.scriptable_if_matching('open(%s, "w").write("%%s\\n" %% %s.to_text())' % 639 (repr(fname), self.table.global_name), 640 [(self.table.global_name, self.table)]) 641 open(fname, 'w').write("%s\n" % self.table.to_text())
642 643
644 -def calc_expr_values(table, expr, indices=None):
645 return table.calc_expr_values(expr, indices=indices)
646 647 CALCULATE_HELP = """Enter a numeric expression such as 648 Index + 1 649 650 These names are available: 651 field_name -- val. of field in this row; replace spaces with underscores; match capitalization 652 row["field name"] -- same, with spaces 653 654 Expressions use Python syntax. Full documentation is available at www.python.org 655 """ + qubx.pyenv.PYTHON_HELP 656 657
658 -class RowPicker(gtk.VBox):
659 __explore_featured = ['update_recent', 'copy_rows_type', 'group', 'inv', 'criteria', 'table', 'get_rows']
660 - def __init__(self, recent_criteria, checked=True):
661 gtk.VBox.__init__(self) 662 self.__ref = Reffer() 663 self.__table = None 664 self.__copy_rows_type = COPY_ROWS_ALL 665 self.__inv = False 666 667 line = pack_item(gtk.HBox(), self) 668 pack_label('Pick rows:', line) 669 line = pack_item(gtk.HBox(), self) 670 self.chkAll = pack_radio('All', line, active=True, on_toggle=bind_with_args_before(self.__onToggle, COPY_ROWS_ALL)) 671 if checked: 672 line = pack_item(gtk.HBox(), self) 673 self.chkChecked = pack_radio('Checked', line, group=self.chkAll, on_toggle=bind_with_args_before(self.__onToggle, COPY_ROWS_CHECKED)) 674 else: 675 self.chkChecked = None 676 line = pack_item(gtk.HBox(), self) 677 self.chkGroup = pack_radio('Group (color)', line, group=self.chkAll, on_toggle=bind_with_args_before(self.__onToggle, COPY_ROWS_GROUP)) 678 self.panGroup = pack_item(gtk.HBox(), line, expand=True) 679 self.chkIn = pack_radio('is', self.panGroup, active=True, on_toggle=bind_with_args_before(self.__onToggleInv, False)) 680 pack_label('/', self.panGroup) 681 self.chkNotIn = pack_radio('is not', self.panGroup, group=self.chkIn, on_toggle=bind_with_args_before(self.__onToggleInv, True)) 682 txtGroup = self.txtGroup = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(-1), width_chars=3), self.panGroup) 683 txtGroup.OnChange += self.__ref(self.__onChangeGroup) 684 palette = self.palGroup = pack_item(qubx.toolspace.Palette(vertical=False), line, expand=True) 685 palette.OnClickColor += self.__ref(self.__onClickColor) 686 line = pack_item(gtk.HBox(), self) 687 self.chkCriteria = pack_radio('Criteria:', line, group=self.chkAll, on_toggle=bind_with_args_before(self.__onToggle, COPY_ROWS_CRITERIA)) 688 self.criteria_entry = pack_item(qubx.select_charts.CriteriaEntry(recent_criteria), line, expand=True) 689 690 self.__copy_rows_type = None 691 self.copy_rows_type = COPY_ROWS_ALL
692
693 - def update_recent(self):
694 self.criteria_entry.update_recent()
695
696 - def set_copy_rows_type(self, x):
697 if x == self.__copy_rows_type: 698 return 699 self.__copy_rows_type = x 700 self.chkAll.set_active(x == COPY_ROWS_ALL) 701 if self.chkChecked: 702 self.chkChecked.set_active(x == COPY_ROWS_CHECKED) 703 self.chkGroup.set_active(x == COPY_ROWS_GROUP) 704 self.chkCriteria.set_active(x == COPY_ROWS_CRITERIA) 705 self.panGroup.set_sensitive(x == COPY_ROWS_GROUP) 706 self.criteria_entry.set_sensitive(x == COPY_ROWS_CRITERIA)
707 copy_rows_type = property(lambda self: self.__copy_rows_type, lambda self, x: self.set_copy_rows_type(x))
708 - def set_group(self, x):
709 self.palGroup.color = x 710 self.txtGroup.value = x
711 group = property(lambda self: self.palGroup.color, lambda self, x: self.set_group(x))
712 - def set_inv(self, x):
713 self.chkIn.set_active(not x) 714 self.chkNotIn.set_active(x)
715 inv = property(lambda self: self.chkNotIn.get_active(), lambda self, x: self.set_inv(x)) 716 criteria = property(lambda self: self.criteria_entry.txtCriteria.value, lambda self, x: self.criteria_entry.txtCriteria.setValue)
717 - def __onToggle(self, chk, copy_rows_type):
718 if chk.get_active(): 719 self.copy_rows_type = copy_rows_type
720 - def __onToggleInv(self, chk, inv):
721 if chk.get_active(): 722 self.__inv = inv
723 - def set_table(self, x):
724 self.__table = x 725 self.criteria_entry.table = x
726 table = property(lambda self: self.__table, lambda self, x: self.set_table(x))
727 - def get_rows(self):
728 tbl = self.__table 729 if not tbl: 730 return [] 731 if self.chkAll.get_active(): 732 return list(xrange(tbl.size)) 733 elif self.chkGroup.get_active(): 734 grp = self.palGroup.color 735 if self.chkNotIn.get_active(): 736 inv = lambda x: not x 737 else: 738 inv = lambda x: x 739 return [i for i in xrange(tbl.size) if inv(grp == tbl.get(i, 'Group'))] 740 elif self.chkCriteria.get_active(): 741 return self.criteria_entry.get_rows()
742 - def __onChangeGroup(self, txt, val):
743 self.group = val
744 - def __onClickColor(self, palette, color):
745 self.group = color 746 self.copy_rows_type = COPY_ROWS_GROUP
747 748
749 -def no_sort(model, iter1, iter2):
750 a, b = [model.get_path(it)[0] for it in (iter1, iter2)] 751 if a < b: return -1 752 if a == b: return 0 753 return 1
754 755
756 -def prompt_copy_rows(table, base_name, name_choices=[], table_name=None, copy_rows_type=None, group=None, inverse=False, criteria=None):
757 tbn = table_name 758 crt = copy_rows_type 759 grp = group 760 inv = inverse 761 cta = criteria 762 QubX = qubx.pyenv.env.globals['QubX'] 763 if (tbn is None) or ((crt == COPY_ROWS_GROUP) and (grp is None)) or ((crt == COPY_ROWS_CRITERIA) and (cta is None)): 764 if tbn is None: 765 tbn = base_name+' x' 766 if crt is None: 767 crt = COPY_ROWS_ALL 768 if grp is None: 769 grp = 1 770 if cta is None: 771 cta = "" 772 dlg = gtk.Dialog('%s - Copy rows...'%QubX.appname, qubx.GTK.get_active_window(), gtk.DIALOG_MODAL) 773 line = pack_item(gtk.HBox(), dlg.vbox) 774 pack_label('Output name:', line) 775 txtTable = pack_item(qubx.GTK.SuggestiveComboBox(tbn), line) 776 def on_popup_names(combo, suggest): 777 for name in name_choices: 778 suggest(name)
779 txtTable.OnPopup += on_popup_names 780 picker = pack_item(RowPicker(qubx.settings.SettingsMgr['Select'].active['RecentCriteria']), dlg.vbox) 781 picker.table = table 782 picker.copy_rows_type = crt 783 picker.group = grp 784 picker.inv = inv 785 picker.criteria = cta 786 dlg.add_button('Cancel', gtk.RESPONSE_REJECT) 787 dlg.add_button('OK', gtk.RESPONSE_ACCEPT) 788 response = dlg.run() 789 dlg.destroy() 790 if response != gtk.RESPONSE_ACCEPT: 791 return None, None, None, None, None 792 picker.update_recent() 793 tbn = txtTable.value 794 crt = picker.copy_rows_type 795 grp = picker.group 796 inv = picker.inv 797 cta = picker.criteria 798 return tbn, crt, grp, inv, cta 799
800 -def prompt_check_rows(table, copy_rows_type=None, group=None, inverse=False, criteria=None):
801 crt = copy_rows_type 802 grp = group 803 inv = inverse 804 cta = criteria 805 QubX = qubx.pyenv.env.globals['QubX'] 806 if (crt is None) or ((crt == COPY_ROWS_GROUP) and (grp is None)) or ((crt == COPY_ROWS_CRITERIA) and (cta is None)): 807 if crt is None: 808 crt = COPY_ROWS_ALL 809 if grp is None: 810 grp = 1 811 if cta is None: 812 cta = "" 813 dlg = gtk.Dialog('%s - Check rows if...'%QubX.appname, qubx.GTK.get_active_window(), gtk.DIALOG_MODAL) 814 picker = pack_item(RowPicker(qubx.settings.SettingsMgr['Select'].active['RecentCriteria'], checked=False), dlg.vbox) 815 picker.table = table 816 picker.copy_rows_type = crt 817 picker.group = grp 818 picker.inv = inv 819 picker.criteria = cta 820 dlg.add_button('Cancel', gtk.RESPONSE_REJECT) 821 dlg.add_button('OK', gtk.RESPONSE_ACCEPT) 822 response = dlg.run() 823 dlg.destroy() 824 if response != gtk.RESPONSE_ACCEPT: 825 return None, None, None, None 826 picker.update_recent() 827 crt = picker.copy_rows_type 828 grp = picker.group 829 inv = picker.inv 830 cta = picker.criteria 831 return crt, grp, inv, cta
832