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

Source Code for Module qubx.script_seq

  1  """GUI support for user scripting. 
  2   
  3  Copyright 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  import os 
 23  import gobject 
 24  import gtk 
 25   
 26  import qubx.faces 
 27  import qubx.GTK 
 28  import qubx.pyenv 
 29  import qubx.pyenvGTK 
 30  import qubx.settings 
 31  import qubx.tree 
 32  from qubx.GTK import pack_item, pack_label, pack_button, build_menuitem 
 33  from qubx.util_types import * 
 34   
35 -def build_tab_label(txt):
36 lbl = gtk.Label(txt) 37 lbl.show() 38 return lbl
39
40 -def build_tab_button(txt, onclick):
41 btn = gtk.Button(txt) 42 btn.connect('clicked', onclick) 43 btn.show() 44 return btn
45
46 -class ScriptSeq(Anon):
47 - def __init__(self, name, node, paths, path_nodes):
48 Anon.__init__(self, name=name, node=node, paths=paths, path_nodes=path_nodes, 49 OnInserted=WeakEvent(), OnRemoved=WeakEvent())
50 - def append(self, path):
51 self.insert(len(self.paths), path)
52 - def insert(self, i, path):
53 if i == 0: 54 self.path_nodes.insert(i, self.node.insert("Path")) 55 else: 56 self.path_nodes.insert(i, self.node.insert("Path", self.path_nodes[i-1])) 57 self.path_nodes[i].data = path 58 self.paths.insert(i, path) 59 self.OnInserted(self, i)
60 - def remove(self, path_or_i):
61 try: 62 i = path_or_i / 1 63 except: 64 i = self.paths.index(path_or_i) 65 self.node.remove(self.path_nodes[i]) 66 del self.path_nodes[i] 67 del self.paths[i] 68 self.OnRemoved(self, i)
69 - def reorder(self, a, b):
70 p, pn = self.paths[a], self.path_nodes[a] 71 self.node.remove(self.path_nodes[a]) 72 del self.paths[a] 73 del self.path_nodes[a] 74 if b: 75 print 'ins',a,b 76 self.node.insert(pn, self.path_nodes[b-1]) 77 else: 78 print 'ins',a 79 self.node.insert(pn) 80 self.paths.insert(b, p) 81 self.path_nodes.insert(b, pn)
82 - def relocate(self, i, new_path):
83 self.paths[i] = new_path 84 self.path_nodes[i].data = new_path
85
86 -class ScriptSeqs(Anon):
87 - def __init__(self):
88 Anon.__init__(self, OnInserted=WeakEvent(), OnRemoved=WeakEvent(), OnRenamed=WeakEvent()) 89 self.props = qubx.settings.SettingsMgr['ScriptSeqs'] 90 self.sequences = [] 91 for seq_node in qubx.tree.children(self.props.active, 'Seq'): 92 self.sequences.append(ScriptSeq(str(seq_node.data), seq_node, 93 [str(path_node.data) for path_node in qubx.tree.children(seq_node, 'Path')], 94 [path_node for path_node in qubx.tree.children(seq_node, 'Path')]))
95 - def add_seq(self, name):
96 i = len(self.sequences) 97 node = self.props.active.append('Seq') 98 node.data = name 99 self.sequences.append(ScriptSeq(name, node, [], [])) 100 self.OnInserted(i, self.sequences[i])
101 - def del_seq(self, name_or_i):
102 try: 103 i = name_or_i / 1 104 except: 105 i = 0 106 while i < len(self.sequences): 107 if self.sequences[i].name == name_or_i: 108 break 109 else: 110 raise KeyError(name_or_i) 111 self.props.active.remove(self.sequences[i].node) 112 del self.sequences[i] 113 self.OnRemoved(i)
114 - def rename_seq(self, i, name):
115 self.sequences[i].name = name 116 self.OnRenamed(i, name)
117
118 -class SeqFace(qubx.faces.Face):
119 __explore_featured = ['seqs', 'toolbar', 'book', 'index', 'seq', 'import_seq', 'export_seq']
120 - def __init__(self, name='Seq', global_name='QubX.Admin.Seq'):
121 super(SeqFace, self).__init__(name, global_name) 122 self.__ref = Reffer() 123 self.seqs = ScriptSeqs() 124 self.seqs.OnInserted += self.__ref(self.__onInsertedSeq) 125 self.seqs.OnRemoved += self.__ref(self.__onRemovedSeq) 126 self.seqs.OnRenamed += self.__ref(self.__onRenamedSeq) 127 self.__index = -1 128 self.__seq = None 129 self.__serial = 1 130 self.__running_all = False 131 self.__run_count = 0 132 self.__views = [] 133 self.__path = str(self.seqs.props.active['Path'].data) 134 135 h = pack_item(gtk.HBox(), self) 136 mb = pack_item(gtk.MenuBar(), h) 137 mnu = gtk.Menu() 138 mnuRoot = build_menuitem('Sequence', submenu=mnu) 139 mb.append(mnuRoot) 140 self.itemNew = build_menuitem('New sequence...', self.__ref(self.__onClickNew), menu=mnu) 141 self.mnuChoose = gtk.Menu() 142 self.mnuChooseItems = [] 143 self.itemChoose = build_menuitem('Choose sequence', submenu=self.mnuChoose, menu=mnu) 144 self.itemDel = build_menuitem('Delete sequence', self.__ref(self.__onClickDel), menu=mnu) 145 mnu.append(gtk.SeparatorMenuItem()) 146 self.itemExport = build_menuitem('Export sequence...', self.__ref(self.__onClickExport), menu=mnu) 147 self.itemImport = build_menuitem('Import sequence...', self.__ref(self.__onClickImport), menu=mnu) 148 mnu.append(gtk.SeparatorMenuItem()) 149 self.itemAdd = build_menuitem('Add script to sequence...', self.__ref(self.__onClickAdd), menu=mnu) 150 self.itemRem = build_menuitem('Remove script from sequence', self.__ref(self.__onClickRem), menu=mnu) 151 mnu.append(gtk.SeparatorMenuItem()) 152 self.itemRun = build_menuitem('Run sequence', self.__ref(self.__onClickRun), menu=mnu) 153 mnu.show_all() 154 155 pack_label('Current sequence: ', h) 156 self.txtSeqName = pack_label('', h) 157 158 self.toolbar = pack_item(gtk.Toolbar(), h) 159 self.toolbar.set_size_request(100, -1) 160 self.toolbar.set_style(gtk.TOOLBAR_ICONS) 161 self.toolbar.set_tooltips(True) 162 self.btnStopSeq = gtk.ToolButton(gtk.STOCK_MEDIA_STOP) 163 self.btnStopSeq.connect('clicked', self.__onClickRun) 164 self.btnStopSeq.show() 165 self.btnStopSeq.set_sensitive(False) 166 self.toolbar.insert(self.btnStopSeq, -1) 167 self.btnPlaySeq = gtk.ToolButton(gtk.STOCK_MEDIA_PLAY) 168 self.btnPlaySeq.connect('clicked', self.__onClickRun) 169 self.btnPlaySeq.show() 170 self.toolbar.insert(self.btnPlaySeq, -1) 171 172 self.btnRem = pack_button('X', h, self.__onClickRem, at_end=True) 173 self.btnRem.set_tooltip_text('Remove this tab from the sequence') 174 self.panTabTools = pack_item(gtk.HBox(), h, at_end=True) # toolbar of current view 175 self.txtScriptName = pack_label('', h, at_end=True) 176 177 self.book = pack_item(gtk.Notebook(), self, expand=True) 178 self.book.set_scrollable(True) 179 self.book.connect('switch_page', self.__onSwitchPage) 180 self.book.connect('page_reordered', self.__onPageReordered) 181 page0 = gtk.VBox() 182 page0.show() 183 self.book.insert_page(page0, build_tab_button('+', self.__onClickAdd)) 184 # - special '+' tab adds to sequence (like web browser add tab) 185 186 for i, seq in enumerate(self.seqs.sequences): 187 self.__onInsertedSeq(i, seq) 188 if self.seqs.sequences and (self.__index < 0): 189 self.index = 0
190 191 index = property(lambda self: self.__index, lambda self, x: self.set_index(x)) 192 seq = property(lambda self: self.__seq) 193
194 - def __onInsertedSeq(self, i, seq):
195 self.mnuChooseItems.insert(i, build_menuitem(seq.name, self.__ref(bind(self.__onChoose, seq)))) 196 self.mnuChoose.insert(self.mnuChooseItems[i], i) 197 if i == self.seqs.props.active['Index']: 198 self.index = i
199 - def __onRemovedSeq(self, i):
200 self.mnuChoose.remove(self.mnuChooseItems[i]) 201 del self.mnuChooseItems[i] 202 if i == self.__index: 203 if i == len(self.mnuChooseItems): 204 self.index = i - 1 205 else: 206 self.index = i
207 - def __onRenamedSeq(self, i, name):
208 self.mnuChoose.remove(self.mnuChooseItems[i]) 209 self.mnuChooseItems[i] = build_menuitem(self.seqs.sequences[i].name, self.__ref(bind(self.__onChoose, self.seqs.sequences[i]))) 210 self.mnuChoose.insert(self.mnuChooseItems[i], i)
211 - def __onChoose(self, seq):
212 self.index = self.seqs.sequences.index(seq)
213 - def __onClickNew(self, item):
214 name = qubx.pyenvGTK.prompt_entry('Name of new script sequence:', "seq%i"%self.__serial) 215 if name is None: 216 return 217 self.__serial += 1 218 self.seqs.add_seq(name) 219 self.index = len(self.seqs.sequences) - 1
220 - def __onClickDel(self, item):
221 if qubx.pyenvGTK.prompt_choices("Delete the sequence? This can't be undone. (Individual scripts will not be deleted.)"): 222 self.seqs.del_seq(self.index)
223 - def __onClickExport(self, item):
224 qubx.GTK.SaveAs("Export script sequence...", self.__path, self.seq.name, self.export_seq, parent=self.parent_window)
225 - def export_seq(self, pathname, index=None):
226 qubx.pyenv.env.OnScriptable('%s.export_seq(%s, %s)' % (self.global_name, repr(pathname), index)) 227 path, name = os.path.split(pathname) 228 self.__path = path 229 base, ext = os.path.splitext(name) 230 if not ext: 231 name = base+".txt" 232 out = open(os.path.join(path, name), "w") 233 seq = self.__seq if (index is None) else self.seqs[index] 234 pathnode = seq.node.find('Path') 235 while not pathnode.isNull: 236 out.write("%s\n" % os.path.relpath(str(pathnode.data), path)) 237 pathnode = pathnode.nextSameName()
238 - def __onClickImport(self, item):
239 qubx.GTK.Open("Import script sequence...", self.__path, self.import_seq, parent=self.parent_window)
240 - def import_seq(self, pathname):
241 qubx.pyenv.env.OnScriptable('%s.import_seq(%s)' % (self.global_name, repr(pathname))) 242 path, name = os.path.split(pathname) 243 self.__path = path 244 base, ext = os.path.splitext(name) 245 rels = [line.strip() for line in open(pathname, "r")] 246 self.seqs.add_seq(name) 247 self.index = len(self.seqs.sequences) - 1 248 seq = self.seqs.sequences[-1] 249 self.__try_appends(seq, rels, [path])
250 - def __try_appends(self, seq, relpaths, altpaths):
251 for ir, relpath in enumerate(relpaths): 252 if not self.__try_append(seq, relpath, altpaths): 253 self.__prompt_missing(seq, relpaths[ir:], altpaths) 254 return
255 - def __try_append(self, seq, relpath, altpaths):
256 for p in altpaths: 257 abspath = os.path.abspath(os.path.join(p, relpath)) 258 if os.path.exists(abspath): 259 seq.append(abspath) 260 return True 261 return False
262 - def __prompt_missing(self, seq, relpaths, altpaths):
263 name = os.path.split(relpaths[0])[1] 264 response = qubx.pyenvGTK.show_message("A script could not be found:\n%s\nTry to locate it?" % relpaths[0], 265 buttons=gtk.BUTTONS_YES_NO, 266 title="Importing script sequence...", 267 parent=self.parent_window) 268 if response == gtk.RESPONSE_YES: 269 qubx.GTK.Open("Locate %s" % name, self.__path, 270 lambda pathname: self.__pick_missing(seq, relpaths, altpaths, pathname), 271 filters=[('Python scripts', '.py')], 272 do_on_cancel=lambda: self.__pick_missing(seq, relpaths, altpaths, None), 273 parent=self.parent_window) 274 elif len(relpaths) > 1: 275 self.__try_appends(seq, relpaths[1:], altpaths)
276 - def __pick_missing(self, seq, relpaths, altpaths, pathname):
277 if pathname: 278 path = os.path.split(pathname)[0] 279 self.__path = path 280 altpaths.append(path) 281 seq.append(pathname) 282 if len(relpaths) > 1: 283 self.__try_appends(seq, relpaths[1:], altpaths)
284
285 - def set_index(self, x):
286 if self.__seq: 287 for i in reversed(xrange(len(self.__seq.paths))): 288 self.__onRemovedScript(self.__seq, i) 289 self.__seq.OnInserted -= self.__ref(self.__onInsertedScript) 290 self.__seq.OnRemoved -= self.__ref(self.__onRemovedScript) 291 self.__seq = self.seqs.sequences[x] if x >= 0 else None 292 self.seqs.props.active['Index'].data = x 293 if self.__seq: 294 self.__seq.OnInserted += self.__ref(self.__onInsertedScript) 295 self.__seq.OnRemoved += self.__ref(self.__onRemovedScript) 296 for i in xrange(len(self.__seq.paths)): 297 self.__onInsertedScript(self.__seq, i) 298 self.txtSeqName.set_text(self.__seq.name) 299 else: 300 self.txtSeqName.set_text('')
301 - def __onInsertedScript(self, seq, ix):
302 path = seq.paths[ix] 303 fname = os.path.split(path)[1] 304 mod = qubx.pyenv.ScriptModule(fname, qubx.pyenv.env.globals) 305 mod.OnPlay += self.__ref(self.__onPlayScript) 306 mod.OnStop += self.__ref(self.__onStopScript) 307 view = qubx.pyenvGTK.PythonScriptView(vertical=None, script_module=mod) 308 self.__views.insert(ix, view) 309 try: 310 view.open(path) 311 self.__after_insert(seq, ix, path, view) 312 except: 313 qubx.GTK.Open('The script "%s" could not be opened. Please find it, or Cancel' % path, os.path.split(path)[0], 314 bind_with_args(self.__after_insert, seq, ix, path, view), 315 [('Python Scripts', '.py')], 316 do_on_cancel=bind(self.__after_insert_missing, seq, ix, path, view))
317 - def __after_insert_missing(self, seq, ix, path, view):
318 view.append_script("# missing file: %s\n# asking for new location...Canceled.\n# will try again next launch." % path) 319 self.__after_insert(seq, ix, path, view)
320 - def __after_insert(self, seq, ix, path, view, new_path=None):
321 if not (new_path is None): 322 try: 323 view.open(new_path) 324 seq.relocate(ix, new_path) 325 except: 326 view.append_script("# failed to open %s" % new_path) 327 view.show() 328 self.book.insert_page(view, build_tab_label(os.path.split(new_path or path)[1]), ix) 329 self.book.set_tab_reorderable(view, True) 330 if self.book.get_n_pages() == 2: 331 self.book.set_current_page(0)
332 - def __onRemovedScript(self, seq, ix):
333 mod = self.__views[ix].exec_script 334 mod.OnPlay -= self.__ref(self.__onPlayScript) 335 mod.OnStop -= self.__ref(self.__onStopScript) 336 del self.__views[ix] 337 self.book.remove_page(ix) 338 n = self.book.get_n_pages() 339 if (n > 1) and (self.book.get_current_page() == (n-1)): 340 self.book.set_current_page(n-2)
341 - def __onClickAdd(self, item):
342 if self.__seq is None: 343 self.__onClickNew(None) 344 qubx.GTK.Open('Add script to sequence', self.__path, self.__add_script, [('Python scripts', '.py')])
345 - def __add_script(self, path):
346 self.seqs.props.active['Path'].data = self.__path = os.path.split(path)[0] 347 self.__seq.append(path) 348 gobject.idle_add(self.book.set_current_page, self.book.get_n_pages()-2)
349 - def __onClickRem(self, *args):
350 if self.book.get_n_pages() > 1: 351 self.seq.remove(self.book.get_current_page())
352 - def __onSwitchPage(self, book, page, ix):
353 self.panTabTools.foreach(lambda item: self.panTabTools.remove(item)) 354 if ix < len(self.__views): 355 pack_item(self.__views[ix].toolbar, self.panTabTools, expand=True) 356 self.txtScriptName.set_text("%s:" % os.path.split(self.__seq.paths[ix])[1]) 357 elif len(self.__views): 358 gobject.idle_add(self.book.set_current_page, len(self.__views)-1) 359 else: 360 self.txtScriptName.set_text('')
361 # prompt save?
362 - def __onPageReordered(self, book, child, page_num):
363 old_num = self.__views.index(child) 364 if page_num == len(self.__views): 365 print 'unreorder',page_num,old_num 366 gobject.idle_add(self.book.reorder_child, child, old_num) 367 if old_num != (len(self.__views)-1): 368 gobject.idle_add(self.book.reorder_child, child, len(self.__views)-1) 369 elif page_num != old_num: 370 print 'real reorder',page_num,old_num 371 self.__views[old_num], self.__views[page_num] = self.__views[page_num], self.__views[old_num] 372 self.__seq.reorder(old_num, page_num) 373 else: 374 print 'neither',page_num,old_num
375
376 - def __onPlayScript(self, module):
377 if self.__running_all: 378 self.panTabTools.hide() 379 self.book.set_current_page(self.__run_ix) 380 return 381 if not self.__run_count: 382 self.toolbar.set_sensitive(False) 383 self.__run_count += 1
384 - def __onStopScript(self, module):
385 if self.__running_all: 386 self.__run_ix += 1 387 if self.__run_ix == len(self.__seq.paths): 388 self.__running_all = False 389 self.btnPlaySeq.set_sensitive(True) 390 self.btnStopSeq.set_sensitive(False) 391 self.panTabTools.show() 392 else: 393 self.__views[self.__run_ix].run() 394 self.__run_count -= 1 395 if not self.__run_count: 396 self.toolbar.set_sensitive(True) 397 self.btnPlaySeq.set_sensitive(True)
398
399 - def __onClickRun(self, *args):
400 if (not self.__seq) or (not self.__seq.paths): 401 return 402 if not self.__running_all: 403 self.__running_all = True 404 self.__run_ix = 0 405 self.btnStopSeq.set_sensitive(True) 406 self.btnPlaySeq.set_sensitive(False) 407 self.__views[0].run() 408 else: 409 self.__running_all = False 410 self.__run_count = 1 411 self.btnStopSeq.set_sensitive(False) 412 self.__views[self.__run_ix].exec_script.stop()
413