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

Source Code for Module qubx.scope

  1  """ 
  2  2d data display with oscilloscope-inspired controls. 
  3   
  4  Copyright 2008-2013 Research Foundation State University of New York  
  5  This file is part of QUB Express.                                           
  6   
  7  QUB Express is free software; you can redistribute it and/or modify           
  8  it under the terms of the GNU General Public License as published by  
  9  the Free Software Foundation, either version 3 of the License, or     
 10  (at your option) any later version.                                   
 11   
 12  QUB Express is distributed in the hope that it will be useful,                
 13  but WITHOUT ANY WARRANTY; without even the implied warranty of        
 14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
 15  GNU General Public License for more details.                          
 16   
 17  You should have received a copy of the GNU General Public License,    
 18  named LICENSE.txt, in the QUB Express program directory.  If not, see         
 19  <http://www.gnu.org/licenses/>.                                       
 20   
 21  """ 
 22   
 23  import cairo 
 24  import gc 
 25  import gtk 
 26  import gobject 
 27  import random 
 28  import traceback 
 29   
 30  import qubx.toolspace 
 31  from qubx.toolspace import ColorInfo 
 32  if qubx.toolspace.HAVE_OPENGL: 
 33      try: 
 34          import gtk.gtkgl 
 35          import gtk.gdkgl 
 36          import OpenGL.GL 
 37          import OpenGL.GLU 
 38      except: 
 39          pass 
 40   
 41  import qubx.data_types 
 42  import qubx.table 
 43   
 44  from gtk import gdk 
 45  from gtk import keysyms 
 46  from itertools import izip, count 
 47  from math import * 
 48  from qubx.util_types import * 
 49  from qubx.accept import * 
 50  from qubx.GTK import build_menuitem 
 51   
 52  SIGNAL_CENTER_HIDDEN = -1e9 # copied from qubx.dataGTK 
 53   
 54  # pix / divs \approx 50; divs in [4, 6, 10, 14, 16, 20, ...] 
 55  TARGET_PIX_PER_DIV = 50.0 
56 -def allowed_divs():
57 """Yields an increasing sequence of allowed pixels per div: 2, 4, 6, 10, 12, 14, 16, 20, 22, ...""" 58 base = 0 59 while True: 60 yield base + 2 61 yield base + 4 62 yield base + 6 63 base += 10 64 yield base
65 66 COLOR_BG = ('scope.bg', (0,0,0,1)) 67 ColorInfo[COLOR_BG[0]].label = 'Data background'
68 -def MEAN_COLOR(alpha): return (1,.4,.8,alpha)
69 GRID_ALPHA = 0.3 70 COLOR_MEAN_LINE = ('scope.mean.line', MEAN_COLOR(GRID_ALPHA)) 71 ColorInfo[COLOR_MEAN_LINE[0]].label = 'Data center line' 72 COLOR_MEAN_VALUE = ('scope.mean.value', MEAN_COLOR(.8)) 73 ColorInfo[COLOR_MEAN_VALUE[0]].label = 'Data center number' 74 COLOR_MEAN_HOVER = ('scope.mean.hover', MEAN_COLOR(1)) 75 ColorInfo[COLOR_MEAN_HOVER[0]].label = 'Data center number mouseover' 76 COLOR_GRID = ('scope.grid', (0,1,0,GRID_ALPHA)) 77 ColorInfo[COLOR_GRID[0]].label = 'Data grid lines' 78 COLOR_UPERD_VALUE = ('scope.uperd.value', (0,1,0,.8)) 79 ColorInfo[COLOR_UPERD_VALUE[0]].label = 'Data units-per-division number' 80 COLOR_UPERD_HOVER = ('scope.uperd.hover', (0,1,0,1)) 81 ColorInfo[COLOR_UPERD_HOVER[0]].label = 'Data units-per-division number mouseover' 82 COLOR_UNITS_VALUE = ('scope.units.value', (1, 1, 1, .8)) 83 ColorInfo[COLOR_UNITS_VALUE[0]].label = 'Data units' 84 COLOR_UNITS_HOVER = ('scope.units.hover', (1, 1, 1, 1)) 85 ColorInfo[COLOR_UNITS_HOVER[0]].label = 'Data units mouseover' 86 COLOR_GRAB = ('scope.grab', (.5,.9,1,.8)) 87 ColorInfo[COLOR_GRAB[0]].label = 'Data auto-scale "?"' 88 COLOR_GRAB_HOVER = ('scope.grab.hover', (1, .7, .7, 1)) 89 ColorInfo[COLOR_GRAB_HOVER[0]].label = 'Data auto-scale "?" mouseover' 90 COLOR_IDEALIZATION = ('scope.idealization', (.8, 0, 0, .8)) 91 ColorInfo[COLOR_IDEALIZATION[0]].label = 'Data idealization' 92 COLOR_EXCLUDED = ('scope.excluded', (.5, .5, .5, .4)) 93 ColorInfo[COLOR_EXCLUDED[0]].label = 'Data excluded' 94 COLOR_SIGNAL_POPUP = ('scope.signal.popup', (0, 1, 0, .8)) 95 ColorInfo[COLOR_SIGNAL_POPUP[0]].label = 'Data signal menus' 96 COLOR_SIGNAL_BORDER = ('scope.signal.border', (1, 1, 1, 1)) 97 ColorInfo[COLOR_SIGNAL_BORDER[0]].label = 'Data signal borders' 98 COLOR_SIGNAL_BG = ('scope.signal.bg', (.12, .12, .12, .85)) 99 ColorInfo[COLOR_SIGNAL_BG[0]].label = 'Data signal background' 100 COLOR_SIGNAL_HOVER = ('scope.signal.hover', (.1, 1, .1, .9)) 101 ColorInfo[COLOR_SIGNAL_HOVER[0]].label = 'Data signal name mouseover' 102 SIGNAL_BORDER = 2 103 COLORS_SIGNAL = {'Current' : ('dataGTK.signals.Current', (1,1,1,1))} 104 #ColorInfo[COLORS_SIGNAL['Current'][0]].label = 'Data:Current' 105 # needs to become persistent... 106
107 -def COLOR_SIGNAL(name):
108 if name in COLORS_SIGNAL: 109 return COLORS_SIGNAL[name] 110 else: 111 c = COLORS_SIGNAL[name] = ('dataGTK.signals.%s'%name, 112 tuple(list(HSVtoRGB(random.uniform(0.0, 1.0), random.uniform(.25, .6), 1.0))+[1.0])) 113 #ColorInfo[c[0]].label = 'Data:%s' % name 114 return c
115 116
117 -def SCOPE_FORMAT(num): return '%.4g' % num
118 -def SCOPE_SCROLL_PIXISH(val, u_per_d, pixels, offset, fine, coarse, lo=None, hi=None):
119 """Returns val scrolled by about a pixel's worth. lo and hi in args for compatibility with qubx.util_types.scrolled_* but ignored.""" 120 if coarse: 121 offset *= 10 122 elif fine: 123 offset /= 10.0 124 return val + offset * pixels * u_per_d / TARGET_PIX_PER_DIV
125 126 SIGNAL_WIDTH = 15 127 SIGNAL_HEIGHT = 3.5 128 129
130 -def divs_for_pix(pix, target_ppd=TARGET_PIX_PER_DIV):
131 """Returns the optimal number of divs to fill so many pixels.""" 132 divv = allowed_divs() 133 divs = divv.next() 134 ppd = pix * 1.0 / divs 135 while True: 136 divs_next = divv.next() 137 ppd_next = pix * 1.0 / divs_next 138 if abs(ppd - target_ppd) < abs(ppd_next - target_ppd): 139 return divs 140 divs, ppd = divs_next, ppd_next
141 142
143 -def accept_tuple(n):
144 """Returns a function which accepts only n-tuples, in string form, and returns them as tuples.""" 145 def accept(x): 146 try: 147 tup = tuple(eval(x)) 148 if len(tup) != n: 149 raise Exception() 150 except: 151 raise FieldAcceptException(x, 'must be an %i-tuple'%n) 152 return tup
153 return accept 154 155 156 # gtkglext1 isn't well-enough supported on windows and mac 157 158 #class Scope(qubx.toolspace.GLSpace):
159 -class Scope(qubx.toolspace.ToolSpace):
160 """View for sampled data with transparent overlay controls, modeled after an oscilloscope. 161 162 @ivar divs: (x_divs, y_divs) number of divisions (green grid squares) 163 @ivar pix_per_div: (x_per_div, y_per_div) 164 @ivar signals: {qubx.table.Table} of signal Name, Units, Center, and per div 165 @ivar sig_layers: list of L{ScopeSignal}s for time and other signals 166 @ivar controls: L{qubx.toolspace.LayerSet} of all included layers 167 """ 168 169 __explore_featured = ['divs', 'pix_per_div', 'signals', 'sig_layers', 'controls', 170 'global_name', 'signal_controls', 'layBlank', 'hide_hidden', 'draw_dim', 171 'line_count', 'onClickGrab', 'onClickUnGrab', 'x2u', 'u2x', 'y2u', 'u2y', 'copy_image', 172 'enable_grid', 'compute_divs'] 173
174 - def __init__(self, global_name=None, signal_controls=True):
175 #qubx.toolspace.GLSpace.__init__(self, opengl=True, can_focus=True) 176 qubx.toolspace.ToolSpace.__init__(self, can_focus=True) 177 self.global_name = global_name 178 self.signal_controls = signal_controls 179 self.__ref = Reffer() 180 self.divs = (1, 1) 181 self.__line_count = 1 182 self.__copying = False 183 self.__hide_hidden = False 184 self.__draw_dim = (1, 1) 185 self.pix_per_div = (TARGET_PIX_PER_DIV, TARGET_PIX_PER_DIV) 186 self.sig_layers = [] 187 self.layerset = self.controls = qubx.toolspace.LayerSet() 188 self.OnDraw += self.__ref(self.__onDraw) 189 self.OnDrawGL += self.__ref(self.__onDrawGL) 190 self.__signal_layout = qubx.pyenv.DeferredAction(delay_ms=5) 191 self.signals = qubx.table.SimpleTable('Scope') 192 self.signals.add_field('Name', '', acceptString, str, '') 193 self.signals.add_field('Units', '', acceptString, str, '') 194 self.signals.add_field('Center', 0.0, acceptFloat, '%.4g', '') 195 self.signals.add_field('per div', 1.0, acceptFloatNonzero, '%.4g', '') 196 self.signals.default['Group'] = 1 197 self.signals.OnInsert += self.__ref(self.__onInsertSignal) 198 self.signals.OnRemoved += self.__ref(self.__onRemovedSignal) 199 self.signals.OnSet += self.__ref(self.__onSetSignal) 200 self.signals.append({'Name' : 'Time', 'Units' : 's', 201 'Center' : 0.0, 'per div' : 1.0, 'Group' : 0}) 202 self.layBlank = qubx.toolspace.Layer(y=6, h=4, w=0.01, cBG=qubx.toolspace.COLOR_CLEAR) 203 self.controls.add_layer(self.layBlank) # reserve some space for data to shine through
204 hide_hidden = property(lambda self: self.__hide_hidden, lambda self, x: self.set_hide_hidden(x))
205 - def set_hide_hidden(self, x):
206 if self.__hide_hidden == x: return 207 self.__hide_hidden = x 208 self.__signal_layout(self, self.__layout_signals)
209 draw_dim = property(lambda self: self.__draw_dim, lambda self, x: self.set_draw_dim(x))
210 - def set_draw_dim(self, x):
211 self.__draw_dim = x
212 line_count = property(lambda self: self.__line_count, lambda self, x: self.set_line_count(x))
213 - def set_line_count(self, x):
214 self.__line_count = x
215 - def __layout_signals(self):
216 for layer in self.sig_layers: 217 if layer in self.controls.layers: 218 self.controls.remove_layer(layer) 219 if not self.signal_controls: return 220 if not self.signals: return 221 x = 1 222 for i, layer in enumerate(self.sig_layers): 223 if i >= self.signals.size: 224 pass 225 elif (not self.__hide_hidden) or (SIGNAL_CENTER_HIDDEN != self.signals[i, 'Center']): 226 self.controls.add_layer(layer) 227 layer.x = x 228 x += SIGNAL_WIDTH + 1
229 - def __onInsertSignal(self, i, undoing):
230 self.sig_layers.insert(i, ScopeSignal(self.signals, i, global_name=self.global_name, y=1.5)) 231 self.sig_layers[i].OnClickGrab += self.__ref(self.onClickGrab) 232 self.sig_layers[i].OnClickUnGrab += self.__ref(self.onClickUnGrab) 233 i += 1 234 while i < self.signals.size: 235 self.sig_layers[i].index = i 236 i += 1 237 self.__signal_layout(self, self.__layout_signals)
238 - def __onRemovedSignal(self, i, undoing):
239 self.sig_layers[i].OnClickGrab -= self.__ref(self.onClickGrab) 240 self.sig_layers[i].OnClickUnGrab -= self.__ref(self.onClickUnGrab) 241 if self.sig_layers[i] in self.controls.layers: 242 self.controls.remove_layer(self.sig_layers[i]) 243 del self.sig_layers[i] 244 while i < self.signals.size: 245 self.sig_layers[i].index = i 246 i += 1 247 self.__signal_layout(self, self.__layout_signals)
248 - def __onSetSignal(self, i, field, val, prev, undoing):
249 if (field == 'Center') and ((val == SIGNAL_CENTER_HIDDEN) or (prev == SIGNAL_CENTER_HIDDEN)): 250 self.__signal_layout(self, self.__layout_signals)
251 - def onClickUnGrab(self, signal):
252 print 'ungrab signal',signal.index
253 - def onClickGrab(self, signal):
254 print 'grab signal',signal.index
255 - def x2u(self, x, y, u_mid, u_per_d):
256 """Returns the time corresponding to pixel x.""" 257 #line_height = self.dim[1] / self.line_count 258 #line = y / line_height 259 #line_dur = u_per_d * self.divs[0] 260 261 line = int(y / max(1, int(self.dim[1] / self.line_count))) 262 line_offset = (line - self.line_count/2) * u_per_d * self.divs[0] 263 if not (self.line_count % 2): # even: midpoint is at begin of (line_count/2) 264 line_offset += u_per_d * self.divs[0] / 2 265 return u_mid + (u_per_d / self.pix_per_div[0]) * (x - self.dim[0]/2.0) + line_offset
266 - def u2x(self, u, u_mid, u_per_d):
267 """Returns the x pixel corresponding to time u.""" 268 return ((self.dim[0]/2.0 * (self.line_count%2)) + (self.pix_per_div[0] / (u_per_d or 1.0)) * (u - u_mid)) % self.dim[0]
269 - def y2u(self, y, u_mid, u_per_d):
270 """Returns the value corresponding to pixel y.""" 271 h = self.dim[1] 272 return h and (u_mid + (u_per_d / self.pix_per_div[1]) * (h/2.0 - (y % (h / self.line_count)))) or 0.0
273 - def u2y(self, u, line, u_mid, u_per_d):
274 """Returns the pixel y corresponding to value u.""" 275 return self.dim[1]/2.0 - (self.pix_per_div[1] / u_per_d) * (u - u_mid) + (line and (line*(self.dim[1] / self.line_count)) or 0.0)
276 - def copy_image(self, w, h, overlays=True, clipboard_name='CLIPBOARD'):
277 """Copies a bitmap of the scope to the specified or default clipboard.""" 278 pixmap = gdk.Pixmap(self.window, w, h, -1) 279 ctx = pixmap.cairo_create() 280 self.__copying = True 281 self.draw_to_context(ctx, w, h, overlay=overlays) 282 self.__copying = False 283 del ctx 284 pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, w, h) 285 pixbuf.get_from_drawable(pixmap, gdk.colormap_get_system(), 0, 0, 0, 0, -1, -1) 286 clipboard = gtk.clipboard_get(clipboard_name) 287 clipboard.set_image(pixbuf) 288 del pixmap 289 del pixbuf 290 qubx.pyenv.env.gc_collect_on_idle() 291 self.redraw_canvas() # re-calculate geometry for mouse
292 - def enable_grid(self, enabled=True):
293 """Call after connecting to OnDraw, so it draws on top of your data.""" 294 if enabled: 295 self.OnDraw += self.__ref(self.__onDrawGrid) 296 self.OnDrawGL += self.__ref(self.__onDrawGridGL) 297 else: 298 self.OnDraw -= self.__ref(self.__onDrawGrid) 299 self.OnDrawGL -= self.__ref(self.__onDrawGridGL) 300 self.redraw_canvas()
301 - def compute_divs(self, w, h):
302 return (divs_for_pix(w), divs_for_pix(h))
303 - def __onDrawGL(self, gldrawable, glcontext, w, h):
304 if not self.__copying: 305 self.divs = self.compute_divs(w, h) 306 self.pix_per_div = ((float(w) / self.divs[0]) or 1.0, (float(h) / self.divs[1]) or 1.0) 307 self.draw_dim = self._dim 308 OpenGL.GL.glColor4f(*self.appearance.color(COLOR_BG)) 309 OpenGL.GL.glBegin(OpenGL.GL.GL_QUADS) 310 OpenGL.GL.glVertex3f(0, 0, 0) 311 OpenGL.GL.glVertex3f(w, 0, 0) 312 OpenGL.GL.glVertex3f(w, h, 0) 313 OpenGL.GL.glVertex3f(0, h, 0) 314 OpenGL.GL.glEnd()
315 - def __onDraw(self, context, w, h):
316 if not self.__copying: 317 self.divs = self.compute_divs(w, h) 318 self.pix_per_div = ((float(w) / self.divs[0]) or 1.0, (float(h) / self.divs[1]) or 1.0) 319 self.draw_dim = self._dim 320 context.set_source_rgba(*self.appearance.color(COLOR_BG)) 321 context.paint()
322 - def __onDrawGrid(self, context, w, h):
323 context.save() 324 context.set_line_width(1.0) 325 for x in xrange(self.divs[0]): 326 if x == self.divs[0]/2: 327 context.set_source_rgba(* self.appearance.color(COLOR_MEAN_LINE)) 328 else: 329 context.set_source_rgba(* self.appearance.color(COLOR_GRID)) 330 px = int(round(x*self.pix_per_div[0] - 0.5)) + 0.5 331 context.move_to(px, 0) 332 context.line_to(px, h) 333 context.stroke() 334 for l in xrange(self.line_count): 335 for y in xrange(self.divs[1]): 336 if y == self.divs[1]/2: 337 context.set_source_rgba(* self.appearance.color(COLOR_MEAN_LINE)) 338 else: 339 context.set_source_rgba(* self.appearance.color(COLOR_GRID)) 340 py = l*(self.dim[1] / self.line_count) + int(round(y*self.pix_per_div[1]/self.line_count - 0.5)) + 0.5 341 context.move_to(0, py) 342 context.line_to(w, py) 343 context.stroke() 344 context.restore()
345 - def __onDrawGridGL(self, gldrawable, glcontext, w, h):
346 OpenGL.GL.glLineWidth(1.0) 347 for x in xrange(self.divs[0]): 348 if x == self.divs[0]/2: 349 OpenGL.GL.glColor4f(* self.appearance.color(COLOR_MEAN_LINE)) 350 else: 351 OpenGL.GL.glColor4f(* self.appearance.color(COLOR_GRID)) 352 px = int(round(x*self.pix_per_div[0] - 0.5)) + 0.5 353 OpenGL.GL.glBegin(OpenGL.GL.GL_LINES) 354 OpenGL.GL.glVertex3f(px, 0, 0) 355 OpenGL.GL.glVertex3f(px, h, 0) 356 OpenGL.GL.glEnd() 357 for y in xrange(self.divs[1]): 358 if y == self.divs[1]/2: 359 OpenGL.GL.glColor4f(* self.appearance.color(COLOR_MEAN_LINE)) 360 else: 361 OpenGL.GL.glColor4f(* self.appearance.color(COLOR_GRID)) 362 py = int(round(y*self.pix_per_div[1] - 0.5)) + 0.5 363 OpenGL.GL.glBegin(OpenGL.GL.GL_LINES) 364 OpenGL.GL.glVertex3f(0, py, 0) 365 OpenGL.GL.glVertex3f(w, py, 0) 366 OpenGL.GL.glEnd()
367 368
369 -class ScopeSignal(qubx.toolspace.Layer):
370 """Floating layer controlling the display of one signal (or time). 371 372 @ivar signals: the L{qubx.table.Table} of signals 373 @ivar index: which row of the table to control 374 @ivar OnClickGrab: L{WeakEvent}(ScopeSignal) when the '?' is clicked, so you can adjust Center and per div appropriately 375 @ivar OnClickUnGrab: L{WeakEvent}(ScopeSignal) when the 'x' is clicked, so you can hide the signal 376 """
377 - def __init__(self, signals, index, global_name, *args, **kw):
378 kw['w'] = SIGNAL_WIDTH 379 kw['h'] = SIGNAL_HEIGHT 380 kw['cBG'] = COLOR_SIGNAL_BG 381 kw['border'] = SIGNAL_BORDER 382 kw['cBorder'] = COLOR_SIGNAL_BORDER 383 qubx.toolspace.Layer.__init__(self, *args, **kw) 384 self.OnClickGrab = WeakEvent() # (ScopeSignal) 385 self.OnClickUnGrab = WeakEvent() # (ScopeSignal) 386 self.__ref = Reffer() 387 self.signals = signals 388 self.signals.OnSet += self.__ref(self.__onSetSignal) 389 self._index = index 390 self.global_name = global_name 391 self.defer_scriptable_scroll = qubx.pyenv.DeferredScriptableScroll() 392 ww = SIGNAL_WIDTH - 1 393 name = signals.get(index, 'Name') 394 self.subName = self.add_sublayer(qubx.toolspace.SubLayer_Label(name, 0, 0, x=0, y=0, w=int(round(.62*ww)), h=1.75, 395 color=COLOR_SIGNAL(name), hover_color=COLOR_SIGNAL_HOVER, 396 action=self.__ref(lambda x,y,e: self.signals.select(self._index, 'Name', self)))) 397 self.subUnits = self.add_sublayer(qubx.toolspace.SubLayer_Label(self.format_units(signals.get(index, 'Units')), 0, 0, x=self.subName.rq_w, y=0, 398 w=int(round(.38*ww)), h=1.75, 399 color=COLOR_UNITS_VALUE, hover_color=COLOR_UNITS_HOVER, 400 action=self.__ref(lambda x,y,e: self.signals.select(self._index, 'Units', self)))) 401 self.popup = gtk.Menu() 402 build_menuitem(index and 'Center signal' or 'Show entire segment', self.__ref(lambda it: self.OnClickGrab(self)), menu=self.popup) 403 if index: 404 build_menuitem('Hide signal', self.__ref(lambda it: self.OnClickUnGrab(self)), menu=self.popup) 405 build_menuitem('Change color...', self.__ref(lambda it: qubx.toolspace.EditOneColor(qubx.scope.COLOR_SIGNAL(self.subName.label), appearance=self.appearance)), 406 menu=self.popup) 407 self.subMenu = self.add_sublayer(qubx.toolspace.SubLayer_Popup(self.popup, COLOR_SIGNAL_POPUP, x=ww, y=.5, w=1, h=1)) 408 self.subMean = self.add_sublayer(qubx.toolspace.SubLayer_Label(SCOPE_FORMAT(signals.get(index, 'Center')), 0, 0, x=0, y=1.75, w=ww/2, h=1.75, 409 color=COLOR_MEAN_VALUE, hover_color=COLOR_MEAN_HOVER, 410 action=self.__ref(lambda x,y,e: self.signals.select(self._index, 'Center', self)), 411 scroll=self.__ref(self._onScrollMean))) 412 self.subUperD = self.add_sublayer(qubx.toolspace.SubLayer_Label(SCOPE_FORMAT(signals.get(index, 'per div')), 0, 0, x=ww/2, y=1.75, w=ww/2, h=1.75, 413 color=COLOR_UPERD_VALUE, hover_color=COLOR_UPERD_HOVER, 414 action=self.__ref(lambda x,y,e: self.signals.select(self._index, 'per div', self)), 415 scroll=self.__ref(self._onScrollUperD))) 416 self.subGrab = self.add_sublayer(qubx.toolspace.SubLayer_Label('?', 0, 0, x=ww, y=1.75, w=1, h=1.75, 417 color=COLOR_GRAB, hover_color=COLOR_GRAB_HOVER, 418 action=self.__ref(self.__onClickGrab), mouse_move=self.__ref(self.__onMouseMoveGrab))) 419 self.subGrab.tooltip = 'Auto-scale'
420 - def format_units(self, x):
421 return x and '[%s]'%x or ''
422 - def set_index(self, x):
423 self._index = x
424 index = property(lambda self: self._index, lambda self, x: self.set_index(x))
425 - def _onScrollMean(self, x, y, e, offset):
426 if self.global_name: 427 self.defer_scriptable_scroll('mean', 428 '%s.signals[%i, "Center"] = qubx.scope.SCOPE_SCROLL_PIXISH(%s.signals[%i, "Center"], %s.signals[%i, "per div"], 10, %%s)' % 429 (self.global_name, self._index, self.global_name, self._index, self.global_name, self._index), 430 offset, e.state & gdk.CONTROL_MASK, e.state & gdk.SHIFT_MASK) 431 self.signals[self._index, 'Center'] = SCOPE_SCROLL_PIXISH(self.signals[self._index, 'Center'], 432 self.signals[self._index, 'per div'], 433 10, offset, e.state & gdk.CONTROL_MASK, e.state & gdk.SHIFT_MASK)
434 - def _onScrollUperD(self, x, y, e, offset):
435 if self.global_name: 436 self.defer_scriptable_scroll('per div', 437 '%s.signals[%i, "per div"] = scrolled_float(%s.signals[%i, "per div"], %%s, lo=1e-9)' % 438 (self.global_name, self._index, self.global_name, self._index), 439 offset, e.state & gdk.CONTROL_MASK, e.state & gdk.SHIFT_MASK) 440 self.signals[self._index, 'per div'] = scrolled_float(self.signals[self._index, 'per div'], offset, 441 e.state & gdk.CONTROL_MASK, e.state & gdk.SHIFT_MASK, lo=1e-9)
442 - def __onSetSignal(self, i, field, val, prev, undoing):
443 if i == self._index: 444 if field == 'Name': 445 self.subName.label = val 446 if self.space: 447 if prev: # copy color as default for new name 448 self.space.appearance.color_preset(COLOR_SIGNAL(val)[0], self.space.appearance.color(COLOR_SIGNAL(prev))) 449 elif not i: # first signal deserves white to be consistent? 450 self.space.appearance.color_preset(COLOR_SIGNAL(val)[0], (1,1,1,1)) 451 elif field == 'Units': 452 self.subUnits.label = self.format_units(val) 453 elif field == 'Center': 454 self.subMean.label = SCOPE_FORMAT(val) 455 elif field == 'per div': 456 self.subUperD.label = SCOPE_FORMAT(val)
457 - def __onClickGrab(self, x, y, e):
458 if (self._index > 0 ) and (e.state & gdk.CONTROL_MASK): 459 self.OnClickUnGrab(self) 460 else: 461 self.OnClickGrab(self)
462 - def __onMouseMoveGrab(self, x, y, e):
463 if (self._index > 0 ) and (e.state & gdk.CONTROL_MASK): 464 if self.subGrab.label != 'x': 465 self.subGrab.label = 'x' 466 self.subGrab.tooltip = 'Hide' 467 else: 468 if self.subGrab.label != '?': 469 self.subGrab.label = '?' 470 self.subGrab.tooltip = 'Auto-scale'
471