1
2 """
3 Contains a modular UI able to display quality assurance checks, run them and
4 present their results. It should be easy to override and adjust it to suit additional needs
5 """
6 __docformat__ = "restructuredtext"
7 import control
8 import util as uiutil
9 import layout
10 from mrv.automation.qa import QAWorkflow
11 import maya.OpenMaya as api
12 from itertools import chain
13 import re
14 from mrv.util import capitalize
15
16 import logging
17 log = logging.getLogger("mrv.maya.ui.qa")
20 """Row Layout able to display a qa check and related information
21
22 :note: currently we make assumptions about the positions of the children in the
23 RowLayout, thus you may only append new ones"""
24 reNiceNamePattern = re.compile( "[A-Z][a-z]" )
25
26
27
28
29
30
31
32 icons = [ "offRadioBtnIcon.xpm", "onRadioBtnIcon.xpm", "fstop.xpm", "fstop.xpm" ]
33
34
35 height = 25
36
37
38 numcols = 3
39
40
41 - def __new__( cls, *args, **kwargs ):
65
72
73 @staticmethod
75 """Generate a replace string from the match in the match object
76
77 :note: match should contain only a range of two chars"""
78 assert match.end() - match.start() == 2
79 if match.start() == 0:
80 return match.string[ match.start() : match.end() ]
81
82
83 return " " + match.string[ match.start() : match.end() ]
84
85
87 """
88 :return: nice name version of name, replacing underscores by spaces, and
89 separating camel cases, as well as chaning to the capitalizaion of word"""
90 name_tokens = name.split( "_" )
91
92
93 for i, token in enumerate( name_tokens ):
94 repl_token = self.reNiceNamePattern.sub( self._replInsSpace, token )
95 name_tokens[ i ] = repl_token
96
97
98 final_tokens = list()
99
100
101 for token in name_tokens:
102 final_tokens.extend( capitalize( t ) for t in token.split( " " ) )
103
104 return " ".join( final_tokens )
105
106
128
140
141
142
144 """:return: check we are operating upon"""
145 return self._check
146
147
148
150 """Run our check
151
152 :note: we may also be used as a ui callback and figure out ourselves
153 whether we have been pressed by the fix button or by the run button
154 :param kwargs: will be passed to the workflow's runChecks method. The following
155 additional kwargs may be specified:
156
157 * force_check:
158 if True, default True, a computation will be forced,
159 otherwise a cached result may be used
160
161 :return: result of our check"""
162 check = self.check()
163 wfl = check.node.workflow()
164 force_check = kwargs.pop( "force_check", True )
165
166 mode = check.node.eMode.query
167 if args and isinstance( args[0], control.Button ):
168 mode = check.node.eMode.fix
169
170
171 return wfl.runChecks( [ check ], mode = mode, clear_result = force_check, **kwargs )[0][1]
172
195
197 """Runs before the check starts"""
198 text = self.listChildren()[0]
199 text.p_label = "Running ..."
200
201 - def postCheck( self, result ):
202 """Runs after the check has finished including the given result"""
203 text = self.listChildren()[0]
204 text.p_label = str( self._toNiceName( self.check().plug.name() ) )
205
206 self.setResult( result )
207
209 """Called if the checks fails with an error
210
211 :param exception: exception object that was thrown by our check
212 :param workflow: workflow that ran the check"""
213 text = self.listChildren()[0]
214 text.p_label = str( self._toNiceName( self.check().plug.name() ) + " ( ERROR )" )
215 log.error(str( exception ))
216
236
237
238 -class QALayout( layout.FormLayout, uiutil.iItemSet ):
239 """Layout able to dynamically display QAChecks, run them and display their result"""
240
241
242
243
244 checkuicls = QACheckLayout
245
246
247
248 run_all_button = True
249
250
251 qaworkflowcls = QAWorkflow
252
253
254
255 show_text_if_empty = True
256
257
258
259
260
261
262 scrollable = True
263
264
265 - def __new__( cls, *args, **kwargs ):
274
276 """Initialize our basic interface involving a column layout to store the
277 actual check widgets"""
278 super( QALayout, self ).__init__( *args, **kwargs )
279 scroll_layout = None
280
281 if self.scrollable:
282 scroll_layout = self.add( layout.ScrollLayout( cr=1 ) )
283
284
285 self.col_layout = layout.ColumnLayout( adj = 1 )
286 if scroll_layout:
287 scroll_layout.add( self.col_layout )
288 else:
289 self.add( self.col_layout )
290
291
292 self.setActive()
293
294
295 self.no_checks_text = None
296
297
299 """Set the checks this layout should display
300
301 :param checks: iterable of qa checks as retrieved by `checks`
302 :raise ValueErorr: if one check is from a different workflow and there is a run_all button"""
303
304
305
306 curparent = self.parent()
307 self.setActive()
308
309
310 name_to_check_map = dict( ( ( str( c ), c ) for c in checks ) )
311 name_to_child_map = dict()
312
313 self.setItems( name_to_check_map.keys(), name_to_check_map = name_to_check_map,
314 name_to_child_map = name_to_child_map )
315
316
317
318 if checks and self.no_checks_text:
319 self.no_checks_text.delete()
320 self.no_checks_text = None
321
322
323 if not checks and self.no_checks_text is None and self.show_text_if_empty:
324 prevparent = self.parent()
325 self.col_layout.setActive()
326 self.no_checks_text = control.Text( label = "No checks available" )
327 prevparent.setActive()
328
329
330
331
332
333
334
335 wfls_done = list()
336 for check in checks:
337 cwfl = check.node.workflow()
338 if cwfl in wfls_done:
339 continue
340 wfls_done.append( cwfl )
341
342 if self.run_all_button and len( wfls_done ) > 1:
343 raise ValueError( "This UI can currently only handle checks from only one workflow at a time if run_all_button is set" )
344
345 cwfl.e_preCheck = self.checkHandler
346 cwfl.e_postCheck = self.checkHandler
347 cwfl.e_checkError = self.checkHandler
348
349
350
351
352
353
354 button_layout_name = "additionals_column_layout"
355 layout_child = self.listChildren( predicate = lambda c: c.basename() == button_layout_name )
356 if layout_child:
357 assert len( layout_child ) == 1
358 self.deleteChild( layout_child[0] )
359
360
361 if self.run_all_button:
362 self.setActive()
363 layout_child = self.add( layout.ColumnLayout( adj = 1, name = button_layout_name ) )
364 if layout_child:
365 control.Separator( style = "single", h = 10 )
366 run_button = control.Button( label = "Run All", ann = "Run all checks in one go",
367 enable = len( checks ) > 0 )
368 run_button.e_pressed = self.runAllPressed
369
370 self.setActive()
371
372
373
374
375 children = self.listChildren()
376 assert len( children ) < 3
377 o = 2
378 t,b,l,r = self.kSides
379 if len( children ) == 1:
380 c = children[0]
381 self.setup( af = ( ( c, b, o ), ( c, t, o ), ( c, l, o ), ( c, r, o ) ) )
382
383 else:
384 c1 = children[0]
385 c2 = children[1]
386 self.setup( af = ( ( c1, l, o ), ( c1, r, o ), ( c1, t, o ),
387 ( c2, l, o ), ( c2, r, o ), ( c2, b, o ) ),
388 ac = ( ( c1, b, o, c2 ) ),
389 an = ( ( c2, t ) ) )
390
391
392
393 curparent.setActive()
394
395
397 """:return: list of checkLayouts representing our checks"""
398 ntcm = dict()
399 self.currentItemIds( name_to_child_map = ntcm )
400 return ntcm.values()
401
403 """:return: list of checks we are currently holding in our layout"""
404 return [ l.check() for l in self.checkLayouts() ]
405
406
407
409 """:return: current check ids as defined by exsiting children.
410 :note: additionally fills in the name_to_child_map"""
411 outids = list()
412 for child in self.col_layout.listChildren( predicate = lambda c: isinstance( c, QACheckLayout ) ):
413 check = child.check()
414 cid = str( check )
415 outids.append( cid )
416
417 name_to_child_map[ cid ] = child
418
419 return outids
420
427
428 - def createItem( self, checkid, name_to_child_map = None, name_to_check_map = None, **kwargs ):
429 """Create and return a layout displaying the given check instance
430
431 :param kwargs: will be passed to checkui class's initializer, allowing subclasses to easily
432 adjust the paramter list
433 :note: its using self.checkuicls to create the instance"""
434 self.col_layout.setActive()
435 check_child = self.checkuicls( check = name_to_check_map[ checkid ], **kwargs )
436 name_to_child_map[ checkid ] = check_child
437 newItem = self.col_layout.add( check_child )
438
439 return newItem
440
441 - def updateItem( self, checkid, name_to_child_map = None, **kwargs ):
442 """Update the item identified by the given checkid so that it represents the
443 current state of the application"""
444 name_to_child_map[ checkid ].update( )
445
446 - def removeItem( self, checkid, name_to_child_map = None, **kwargs ):
447 """Delete the user interface portion representing the checkid"""
448 self.col_layout.deleteChild( name_to_child_map[ checkid ] )
449
450
451
453 """:return: True if the given `QACheckLayout` manages the given check"""
454 return checkLayout.check() == check
455
492
494 """Called once the Run-All button is pressed
495
496 :param kwargs: will be passed to runChecks method of workflow
497 :note: we assume all checks are from one workflow only as we
498 do not sort them by workflow
499 :note: currently we only run in query mode as sort of safety measure - check and fix
500 on all might be too much and lead to unexpected results"""
501 checks = self.checks()
502 if not checks:
503 log.error("No checks found to run")
504 return
505
506
507 wfl = checks[0].node.workflow()
508 wfl.runChecks( checks, clear_result = 1, **kwargs )
509
510
511