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

Source Code for Module qubx.util_panels

  1  """Components for the top level of the QUB Express or Fitness. 
  2   
  3  Copyright 2008-2013 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   
 24  import os 
 25  import traceback 
 26   
 27  import gtk 
 28  import gobject 
 29  import numpy 
 30  import time 
 31   
 32  import qubx.faces 
 33  import qubx.GTK 
 34  import qubx.notebook 
 35  import qubx.notebookGTK 
 36  import qubx.pyenv 
 37  import qubx.pyenvGTK 
 38  import qubx.settings 
 39  import qubx.settingsGTK 
 40  import qubx.task 
 41  import qubx.tree 
 42  import qubx.treeGTK 
 43  import qubx.table 
 44  import qubx.tableGTK 
 45  import qubx.toolspace 
 46   
 47  from itertools import izip, count 
 48  from math import * 
 49  from gtk import gdk 
 50  from qubx.accept import * 
 51  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem 
 52  from qubx.settings import Propertied, Property 
 53  from qubx.tree import node_data_or_def 
 54  from qubx.util_types import * 
 55   
 56   
 57  SCRIPT_PROXY_CHECK_SEC = 0.3 
58 59 -class TasksFace(qubx.faces.Face):
60 """Panel with "Stop Tasks" button and mean progress bar; monitors L{qubx.task.Tasks}; manages QubX.About.Tasks. 61 62 @ivar all_tasks: L{Face} in QubX.About, with progress and stop button for each L{qubx.task.Task} 63 """ 64 65 __explore_featured = ['btnStop', 'barMean', 'script_proxies', 'all_tasks', 'update_progress'] 66
67 - def __init__(self, global_name=""):
68 qubx.faces.Face.__init__(self, 'Tasks', global_name=global_name) 69 self.__ref = Reffer() 70 qubx.task.Tasks.OnAdd += self.__ref(self.__onAddTask) 71 qubx.task.Tasks.OnRemove += self.__ref(self.__onRemoveTask) 72 qubx.pyenv.env.OnPlayScript += self.__ref(self.__onPlayScript) 73 qubx.pyenv.env.OnStopScript += self.__ref(self.__onStopScript) 74 qubx.pyenv.env.OnPauseScript += self.__ref(self.__onPauseScript) 75 qubx.pyenv.env.OnScriptLine += self.__ref(self.__onScriptLine) 76 self.btnStop = pack_button('Stop Tasks', self, on_click=self.__onClickStop, sensitive=False) 77 self.btnStop.modify_bg(gtk.STATE_NORMAL, gdk.color_parse("#ee9999")) 78 self.btnStop.modify_bg(gtk.STATE_PRELIGHT, gdk.color_parse("#ffbbbb")) 79 self.barMean = pack_item(gtk.ProgressBar(), self) 80 self.barMean.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT) 81 self.__tasks = [] 82 self.__panels = [] 83 self.__bars = [] 84 self.script_proxies = {} 85 self.QubX = None 86 self.all_tasks = qubx.faces.Face('Tasks', global_name='QubX.About.Tasks') 87 self.all_tasks.show() 88 self.update_progress()
89 - def onShow(self, showing):
90 qubx.faces.Face.onShow(self, showing) 91 if not self.QubX: 92 self.QubX = qubx.pyenv.env.globals['QubX'] 93 self.QubX.About.append_face(self.all_tasks)
94 - def __onAddTask(self, tasks, task):
95 panel = pack_item(gtk.HBox(), self.all_tasks) 96 panel.task = task 97 pack_label('%s: ' % task.label, panel) 98 bar = pack_item(gtk.ProgressBar(), panel, expand=True) 99 bar.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT) 100 bar.set_fraction(0.0) 101 bar.set_text(task.status) 102 btn = pack_button('Stop', panel, on_click=bind(task.interrupt)) 103 self.__panels.append(panel) 104 self.__tasks.append(task) 105 self.__bars.append(bar) 106 task.OnProgress += self.__ref(self.__onProgress) 107 task.OnStatus += self.__ref(self.__onStatus) 108 self.btnStop.set_sensitive(True) 109 self.update_progress()
110 - def __onRemoveTask(self, tasks, task):
111 try: 112 task.OnProgress -= self.__ref(self.__onProgress) 113 task.OnStatus -= self.__ref(self.__onStatus) 114 except: 115 pass 116 i = self.__tasks.index(task) 117 self.all_tasks.remove(self.__panels[i]) 118 del self.__bars[i] 119 del self.__panels[i] 120 del self.__tasks[i] 121 self.btnStop.set_sensitive(bool(len(self.__tasks) or qubx.pyenv.env.exec_script)) 122 self.update_progress()
123 - def __onProgress(self, task, x):
124 self.__bars[ self.__tasks.index(task) ].set_fraction(max(0.0, min(1.0, x/100.0))) 125 self.update_progress()
126 - def update_progress(self):
127 ntask = len(self.all_tasks) 128 if not ntask: 129 self.barMean.set_text('(idle)') 130 self.barMean.set_fraction(0.0) 131 return 132 fraction = max(0.0, min(1.0, sum(panel.task.progress for panel in self.all_tasks) / (ntask * 100.0))) 133 self.barMean.set_text('%i%% of %i task%s' % (int(round(100.0*fraction)), ntask, ntask and 's' or '')) 134 self.barMean.set_fraction(fraction)
135 - def __onStatus(self, task, x):
136 self.__bars[ self.__tasks.index(task) ].set_text(x)
137 - def __onPlayScript(self, module):
138 if module.filename in self.script_proxies: 139 raise Exception('%s is already in the task list' % module.filename) 140 task = ScriptProxyTask(module) 141 self.script_proxies[module.filename] = task 142 qubx.task.Tasks.add_task(task)
143 - def __onStopScript(self, module):
144 try: 145 task = self.script_proxies[module.filename] 146 except KeyError: 147 return 148 task.script_done() 149 qubx.task.Tasks.remove_task(task) 150 del self.script_proxies[module.filename]
151 - def __onPauseScript(self, module):
152 self.script_proxies[module.filename].status = 'Paused (line %i)' % module.lineno
153 - def __onScriptLine(self, module, lineno, line):
154 try: 155 task = self.script_proxies[module.filename] 156 except KeyError: 157 return 158 task.status = 'Running (line %i)' % lineno 159 task.progress = lineno * 100.0 / len(module.lines)
160 - def __onClickStop(self, btn):
161 if self.__tasks: 162 qubx.task.Tasks.interrupt()
163
164 -class ScriptProxyTask(qubx.task.Task):
165 """Represents a running script, in order to show progress and status and allow user to stop and pause (first click on "Stop" pauses it, next click permanently stops it)."""
166 - def __init__(self, module):
167 """@param module: L{qubx.pyenv.ScriptModule}""" 168 self.module = module 169 qubx.task.Task.__init__(self, module.filename) 170 self.__ref_interrupt = self.__onInterrupt 171 self.__ref_resume = self.__onResume 172 self.OnInterrupt += self.__ref_interrupt 173 module.OnResume += self.__ref_resume 174 self.not_done = True 175 self.start() 176 self.stop_count = 0
177 - def script_done(self):
178 """Called by creator (TaskFace) when script finishes, stops proxy task.""" 179 self.not_done = False 180 self.module.OnResume -= self.__ref_resume
181 - def run(self):
182 while self.not_done: 183 time.sleep(SCRIPT_PROXY_CHECK_SEC)
184 - def __onInterrupt(self, task, cancel):
185 if self.module.paused or (self.stop_count >= 3): # (stopped while already paused) or (unresponsive and user is clicking wildly) 186 self.module.stop() 187 else: 188 self.module.pause() 189 self.stop_count += 1 190 cancel()
191 - def __onResume(self, task):
192 self.stop_count = 0
193
194 -class ScriptsFace(qubx.faces.Face):
195 """Panel with script editor, python prompt, and output. 196 197 @ivar scripts: L{qubx.pyenvGTK.PythonScriptView} 198 """ 199 200 __explore_featured = ['scripts'] 201
202 - def __init__(self, global_name=""):
203 qubx.faces.Face.__init__(self, 'Scripts', global_name=global_name) 204 self.scripts = pack_item(qubx.pyenvGTK.PythonScriptView(vertical=False), self, expand=True) 205 self.ref = Reffer() 206 QubX = qubx.pyenv.env.globals['QubX'] 207 QubX.OnQuitting += self.ref(self._onQuitting), 'ScriptsFace.onQuitting'
208 - def _onQuitting(self, do_cancel):
209 if qubx.pyenv.env.exec_script: 210 qubx.pyenv.env.exec_script.stop() 211 if not self.scripts.promptSave(): 212 do_cancel()
213 # this was obnoxious:
214 #def onShow(self, showing): 215 # if (not showing) and (not self.scripts.promptSave()): 216 # self.request_show() 217 218 219 -class AltKeysFace(qubx.faces.Face):
220 """Panel with alt-combo -> script mapping.""" 221 __explore_featured = ['altmap']
222 - def __init__(self, global_name=""):
223 qubx.faces.Face.__init__(self, 'AltKeys', global_name=global_name) 224 self.altmap = pack_item(qubx.pyenvGTK.AltMapTable(), self, expand=True)
225
226 227 -class AboutFace(qubx.faces.TextFace):
228 """Panel with welcome message. Should add hyperlinked getting-started ideas."""
229 - def __init__(self, global_name=""):
231 232 233 @Propertied(Property('font_size', qubx.toolspace.Appearance.font_size, "also influences graphical layout sizes"), 234 Property('font_bold', qubx.toolspace.Appearance.font_bold, "True for fatter letters"), 235 Property('line_width', qubx.toolspace.Appearance.line_width, 'used primarily to draw data'), 236 Property('opengl', qubx.toolspace.Appearance.opengl, 'True to allow OpenGL drawing (faster/less compatible); takes effect on program restart'))
237 -class SettingsFace(qubx.faces.Face):
238 """ 239 Panel to manage L{qubx.toolspace.Appearance} and other active L{qubx.settings}. 240 Saves and restores Appearance settings. 241 Editing of qubx.settings is advisory -- the changes are passed to SettingsMgr[category_name].setProperties/OnSet which is free to ignore them. 242 243 @ivar catlist: L{qubx.GTK.SimpleList} 244 @ivar treeView: L{qubx.treeGTK.TreeView} 245 """ 246 247 __explore_featured = ['treeView', 'tree_colors', 'show_colors_dialog', 'show_cat'] 248
249 - def __init__(self, global_name=""):
250 qubx.faces.Face.__init__(self, 'Settings', global_name=global_name) 251 self.__ref = Reffer() 252 253 appname = qubx.pyenv.env.globals['QubX'].appname 254 screen_w = gdk.screen_width() 255 if screen_w < 900: 256 def_font_size = 9 257 elif screen_w < 1100: 258 def_font_size = 10 259 elif screen_w < 1300: 260 def_font_size = 12 261 elif screen_w < 1700: 262 def_font_size = 14 263 else: 264 def_font_size = 16 265 self.font_size = def_font_size 266 267 self.cat_appearance = qubx.settings.SettingsMgr['Appearance'] 268 props = self.cat_appearance.active 269 if not props['version'].data: 270 props['version'].data = 2 271 props.remove(props['Colors']) # no holdover colors from when transparency was wrong 272 self.propertied_connect_settings('Appearance') 273 self.font_size = self.font_size 274 qubx.toolspace.Appearance.font_bold = self.font_bold 275 qubx.toolspace.Appearance.line_width = self.line_width 276 qubx.toolspace.Appearance.opengl = self.opengl 277 qubx.toolspace.Appearance.hide_hidden_signals = bool(node_data_or_def(props['hide_hidden_signals'], False)) 278 qubx.toolspace.Appearance.OnSetHideHiddenSignals += self.__ref(self.__onSetHideHiddenSignals) 279 qubx.toolspace.Appearance.multi_line_data = bool(node_data_or_def(props['multi_line_data'], True)) 280 qubx.toolspace.Appearance.OnSetMultiLineData += self.__ref(self.__onSetMultiLineData) 281 qubx.toolspace.Appearance.auto_scale_data = bool(node_data_or_def(props['auto_scale_data'], False)) 282 qubx.toolspace.Appearance.OnSetAutoScaleData += self.__ref(self.__onSetAutoScaleData) 283 qubx.toolspace.Appearance.color_idealized = bool(node_data_or_def(props['color_idealized'], False)) 284 qubx.toolspace.Appearance.OnSetColorIdealized += self.__ref(self.__onSetColorIdealized) 285 qubx.toolspace.Appearance.gauss_intensity = bool(node_data_or_def(props['gauss_intensity'], True)) 286 qubx.toolspace.Appearance.OnSetGaussIntensity += self.__ref(self.__onSetGaussIntensity) 287 288 h = pack_item(gtk.HBox(), self) 289 self.btnColors = pack_button('Colors...', h, on_click=self.__onClickColors) 290 pack_space(h, expand=True) 291 pack_label('Font size: ', h) 292 self.txtFontSize = pack_item(qubx.GTK.NumEntry(def_font_size, acceptIntGreaterThan(0), width_chars=4), h) 293 self.propertied_connect_NumEntry('font_size', self.txtFontSize) 294 self.chkBold = pack_check('bold', h) 295 self.propertied_connect_check('font_bold', self.chkBold) 296 pack_space(h, expand=True) 297 pack_label('Line width:', h) 298 self.txtLineWidth = pack_item(qubx.GTK.NumEntry(qubx.toolspace.Appearance.line_width, width_chars=4), h) 299 self.propertied_connect_NumEntry('line_width', self.txtLineWidth) 300 pack_space(h, expand=True) 301 self.chkOpenGL = pack_check('Allow OpenGL', h, show=False) ## disabled -- no gl panels, bad x-platform support 302 self.chkOpenGL.set_tooltip_text("Usually draws faster. Takes effect next run.\nTry un-checking if you can't see the data.") 303 self.propertied_connect_check('opengl', self.chkOpenGL) 304 pack_space(h, expand=True) 305 self.mnuPresets = pack_item(qubx.settingsGTK.PresetsMenu('Appearance', appname), h) 306 pack_space(h, expand=True) 307 self.btnSaveAll = pack_button('Save all settings', h, on_click=self.__onClickSaveAll, at_end=True) 308 self.btnSaveAll.set_tooltip_text('Settings are ordinarily saved when closing %s'%appname) 309 310 lr = pack_item(gtk.HPaned(), self, expand=True) 311 self.catlist = qubx.GTK.SimpleList(sortable=False) 312 self.catlist.OnSelect += self.__ref(self._onSelect), 'SettingsFace.onSelect' 313 scr = pack_scrolled(self.catlist, None, size_request=(150, 70)) 314 lr.pack1(scr, False, True) 315 316 self.treeView = qubx.treeGTK.TreeView() 317 self.treeView.OnChange += self.__ref(self._onTreeEdit), 'SettingsFace.onTreeEdit' 318 scr = pack_scrolled(self.treeView, None, size_request=(200, 70)) 319 lr.pack2(scr, True, True) 320 321 self._lastName = None 322 self._names = [] 323 324 self.tree_colors = qubx.settings.SettingsMgr['Appearance'].active['Colors'] 325 for child in qubx.tree.children(self.tree_colors): 326 if child.data: 327 qubx.toolspace.Appearance.color_preset(child.name, tuple(child.data[:])) 328 qubx.toolspace.Appearance.OnAddColor += self.__ref(self.__onAddColor) 329 for color in qubx.toolspace.Appearance.colors.values(): 330 self.__onAddColor(color.name, color.value)
331
332 - def __onAddColor(self, color):
333 color.OnSet += self.__ref(self.__onSetColor) 334 self.__onSetColor(color)
335 - def __onSetColor(self, color):
336 self.tree_colors[color.name].data = color.value
337 - def __onSetHideHiddenSignals(self, x):
338 self.cat_appearance.active['hide_hidden_signals'].data = x
339 - def __onSetMultiLineData(self, x):
340 self.cat_appearance.active['multi_line_data'].data = x
341 - def __onSetAutoScaleData(self, x):
342 self.cat_appearance.active['auto_scale_data'].data = x
343 - def __onSetColorIdealized(self, x):
344 self.cat_appearance.active['color_idealized'].data = x
345 - def __onSetGaussIntensity(self, x):
346 self.cat_appearance.active['gauss_intensity'].data = x
347 - def propertied_on_preset(self, category, updates):
348 super(SettingsFace, self).propertied_on_preset(category, updates) 349 if updates.find('hide_hidden_signals').data: 350 qubx.toolspace.Appearance.hide_hidden_signals = bool(updates['hide_hidden_signals'].data[0]) 351 if updates.find('multi_line_data').data: 352 qubx.toolspace.Appearance.multi_line_data = bool(updates['multi_line_data'].data[0]) 353 if updates.find('auto_scale_data').data: 354 qubx.toolspace.Appearance.auto_scale_data = bool(updates['auto_scale_data'].data[0]) 355 if updates.find('color_idealized').data: 356 qubx.toolspace.Appearance.color_idealized = bool(updates['color_idealized'].data[0]) 357 if updates.find('gauss_intensity').data: 358 qubx.toolspace.Appearance.gauss_intensity = bool(updates['gauss_intensity'].data[0]) 359 for child in qubx.tree.children(updates.find('Colors')): 360 if child.data: 361 qubx.toolspace.Appearance.color_preset(child.name, tuple(child.data[:]))
362 - def propertied_set(self, value, name):
363 super(SettingsFace, self).propertied_set(value, name) 364 setattr(qubx.toolspace.Appearance, name, value) 365 if name == 'font_size': 366 qubx.GTK.set_font_size(int(round(0.82*value)))
367 - def __onClickColors(self, btn):
368 self.show_colors_dialog()
369 - def show_colors_dialog(self):
370 """Shows a dialog for editing all colors.""" 371 backup = [self.cat_appearance.active.clone()] 372 def on_cancel(): 373 qubx.settings.SettingsMgr['Appearance'].setProperties(backup[0])
374 def on_apply(): 375 backup[0] = self.cat_appearance.active.clone()
376 dlg = qubx.toolspace.TS_ColorsDialog(qubx.pyenv.env.globals['QubX'].appname, qubx.toolspace.Appearance) 377 dlg.show(on_cancel, on_apply)
378 - def __onClickSaveAll(self, btn):
379 qubx.pyenv.env.save_altmap() 380 qubx.settings.SettingsMgr.saveAll()
381 - def onShow(self, showing):
382 if showing: 383 self.catlist.clear() 384 names = sorted(qubx.settings.SettingsMgr.cats.keys()) 385 for name in names: 386 self.catlist.append(name) 387 if self._lastName: 388 self.catlist.index = names.index(self._lastName) 389 self._names = names 390 else: 391 self.show_cat(None)
392
393 - def _onSelect(self, list, index):
394 if index < 0: 395 self.show_cat(None) 396 else: 397 self.show_cat(self._names[index])
398 - def show_cat(self, name):
399 if name is None: 400 self.treeView.root = qubx.tree.NullNode() 401 elif self.treeView.root.isNull or (name != self._lastName): 402 self._lastName = name 403 self.treeView.root = qubx.settings.SettingsMgr[name].active.clone()
404
405 - def _onTreeEdit(self, treeView):
406 if self._lastName is None: return 407 qubx.settings.SettingsMgr[self._lastName].setProperties(treeView.root)
408
409 410 -class PluginsFace(qubx.faces.Face):
411 """Face to manage qubx.pyenv.Plugins, the L{qubx.pyenv.PluginManager}.""" 412 __explore_featured = ['path', 'open_plugin']
413 - def __init__(self, global_name=""):
414 qubx.faces.Face.__init__(self, 'Plugins', global_name=global_name) 415 self.__ref = Reffer() 416 self.path = None 417 h = pack_item(gtk.HBox(), self, expand=True) 418 self.lstPlugs = qubx.GTK.SimpleList(sortable=False) 419 self.lstPlugs.OnSelect += self.__ref(self.__onSelect) 420 pack_scrolled(self.lstPlugs, h, size_request=(200, -1)) 421 hv = pack_item(gtk.VBox(), h, expand=True) 422 hvh = pack_item(gtk.HBox(), hv) 423 self.btnAdd = pack_button('Add...', hvh, on_click=self.__onClickAdd) 424 self.btnRemove = pack_button('Remove', hvh, on_click=self.__onClickRemove, sensitive=False) 425 self.btnReload = pack_button('Reload', hvh, on_click=self.__onClickReload, sensitive=False) 426 self.txtAbout = gtk.TextView() 427 self.txtAbout.set_wrap_mode(gtk.WRAP_WORD) 428 pack_scrolled(self.txtAbout, hv, expand=True) 429 self.outAbout = qubx.GTK.TextViewAppender(self.txtAbout) 430 qubx.pyenv.Plugins.OnUpdate += self.__ref(self.__onUpdate) 431 qubx.pyenv.Plugins.OnRemoving += self.__ref(self.__onRemoving) 432 for i in xrange(len(qubx.pyenv.Plugins.names)): 433 self.__onUpdate(qubx.pyenv.Plugins, i)
434 - def __onSelect(self, lst, ix):
435 have = ix >= 0 436 self.btnRemove.set_sensitive(have) 437 self.btnReload.set_sensitive(have) 438 self.txtAbout.get_buffer().set_text('') 439 if have: 440 self.outAbout.write( qubx.pyenv.Plugins.abouts[ix] )
441 - def __onClickAdd(self, btn):
442 qubx.GTK.Open('Open...', self.path or qubx.global_namespace.documents_path, self.open_plugin, [('zip archives', '.zip')], self.parent_window)
443 - def open_plugin(self, path):
444 """@param path: location of a plugin folder or zip file""" 445 qubx.pyenv.Plugins.add( path ) 446 self.path = os.path.split(path)[0]
447 - def __onClickRemove(self, btn):
448 if gtk.RESPONSE_ACCEPT == qubx.pyenvGTK.prompt_choices("Really delete the plugin? This can't be undone."): 449 qubx.pyenv.Plugins.remove( self.lstPlugs.index )
450 - def __onClickReload(self, btn):
451 qubx.pyenv.Plugins.reload( self.lstPlugs.index )
452 - def __onUpdate(self, plugins, index):
453 if len(plugins.names) > len(self.lstPlugs.model): 454 self.lstPlugs.insert(index, plugins.names[index]) 455 self.lstPlugs.index = index 456 self.__onSelect(self.lstPlugs, index)
457 - def __onRemoving(self, plugins, index):
458 self.lstPlugs.remove(index) 459 self.lstPlugs.index = 0
460