| Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
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 #{ Configuration
27 # paths to icons to display
28 # [0] = check not run
29 # [1] = check success
30 # [2] = check failed
31 # [3] = check threw exception
32 icons = [ "offRadioBtnIcon.xpm", "onRadioBtnIcon.xpm", "fstop.xpm", "fstop.xpm" ] # explicitly a list to allow assignments
33
34 # height of the UI control
35 height = 25
36
37 # number of columns to use - assure to fill the respective slots
38 numcols = 3
39 #} END configuration
40
42 """Initialize this RowColumnLayout instance with a check instance
43
44 :param kwargs:
45 * check:
46 the check this instance should attach itself to - it needs to be set
47 or the instance creation will fail"""
48 check = kwargs.pop( "check" )
49
50 numcols = cls.numcols # without fix
51 if check.plug.implements_fix:
52 numcols += 1
53
54 assert numcols < 7 # more than 6 not supported by underlying layout
55
56 kwargs[ 'numberOfColumns' ] = numcols
57 kwargs[ 'adj' ] = 1
58 kwargs[ 'h' ] = cls.height
59 kwargs[ 'cw%i' % numcols ] = ( cls.height + 2, ) * numcols
60 self = super( QACheckLayout, cls ).__new__( cls, *args, **kwargs )
61
62 # create instance variables
63 self._check = check
64 return self
65
67 """Initialize our instance with members"""
68 super( QACheckLayout, self ).__init__( *args, **kwargs )
69
70 # populate
71 self._create( )
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: # in the beginning , replace by itself
80 return match.string[ match.start() : match.end() ]
81
82 # otherwise insert a space between items
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 # parse camel case
93 for i, token in enumerate( name_tokens ):
94 repl_token = self.reNiceNamePattern.sub( self._replInsSpace, token )
95 name_tokens[ i ] = repl_token
96 # END for each token camel case parse
97
98 final_tokens = list()
99
100 # split once more on inserted spaces, capitalize
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
108 """Create our layout elements according to the details given in check"""
109 # assume we are active
110 checkplug = self.check().plug
111 nice_name = self._toNiceName( checkplug.name() )
112 self.add( control.Text( label = nice_name, ann = checkplug.annotation ) )
113
114 ibutton = self.add( control.IconTextButton( style="iconOnly",
115 h = self.height, w = self.height ) )
116 sbutton = self.add( control.Button( label = "S", w = self.height,
117 ann = "Select faild or fixed items" ) )
118
119 # if we can actually fix the item, we add an additional button
120 if checkplug.implements_fix:
121 fbutton = self.add( control.Button( label = "Fix", ann = "Attempt to fix failed items" ) )
122 fbutton.e_released = self._runCheck
123 # END fix button setup
124
125 # attach callbacks
126 ibutton.e_command = self._runCheck
127 sbutton.e_released = self.selectPressed
128
130 """Update ourselves to match information in our stored check"""
131 # check the cache for a result - if so, ask it for its state
132 # otherwise we are not run and indicate that
133 bicon = self.listChildren()[1]
134 bicon.p_image = self.icons[0]
135
136 check = self.check()
137 if check.hasCache():
138 result = check.cache()
139 self.setResult( result )
140 # END if previous result exists
141
142
146
147 #{ Check Callbacks
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 # END fix button handling
170
171 return wfl.runChecks( [ check ], mode = mode, clear_result = force_check, **kwargs )[0][1]
172
174 """Called if the selected button has been pressed
175 Triggers a workflow run if not yet done"""
176 # use the cache directly to prevent the whole runprocess to be kicked on
177 # although the result is already known
178 check = self.check()
179 result = None
180 if check.hasCache():
181 result = check.cache()
182 else:
183 result = self._runCheck( force_check = False )
184
185 # select items , ignore erorrs if it is not selectable
186 sellist = api.MSelectionList()
187 for item in chain( result.fixedItems(), result.failedItems() ):
188 try:
189 sellist.add( str( item ) )
190 except RuntimeError:
191 pass
192 # END for each item to select
193
194 api.MGlobal.setActiveSelectionList( sellist )
195
197 """Runs before the check starts"""
198 text = self.listChildren()[0]
199 text.p_label = "Running ..."
200
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
218 """Setup ourselves to indicate the given check result
219
220 :return: our adjusted iconTextButton Member"""
221 target_icon = self.icons[2] # failed by default
222
223 if result.isSuccessful():
224 target_icon = self.icons[1]
225 elif result.isNull(): # indicates failure, something bad happened
226 target_icon = self.icons[3]
227
228 # annotate the text with the result
229 children = self.listChildren()
230 text = children[0]
231
232 bicon = children[1]
233 bicon.p_image = target_icon
234
235 return bicon
236 #} END interface
239 """Layout able to dynamically display QAChecks, run them and display their result"""
240
241 #{ Configuration
242 # class used to create a layout displaying details about the check
243 # it must be compatible to QACheckLayout as a certain API is expected
244 checkuicls = QACheckLayout
245
246 # if True, a button to run all checks at once will be appended
247 # Can be passed in as per-instance value during creation
248 run_all_button = True
249
250 # class used to access default workflow events
251 qaworkflowcls = QAWorkflow
252
253 # if True, there will be an informational text if no checks have been found
254 # otherwiise the layout will simply be empty
255 show_text_if_empty = True
256
257
258 # if True, a scroll layout will be created around the layout containing a
259 # possibly long list of checks. Set False if you would like to handle the
260 # scrolling with an own interface
261 # Can be passed in as per-instance value during creation
262 scrollable = True
263 #} END configuration
264
266 """Set some default arguments"""
267 scrollable = kwargs.pop( "scrollable", cls.scrollable )
268 run_all_button = kwargs.pop( "run_all_button", cls.run_all_button )
269 self = super( QALayout, cls ).__new__( cls, *args, **kwargs )
270 self.scrollable = scrollable
271 self.run_all_button = run_all_button
272
273 return self
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 # will contain the checks
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 # END scroll_layout
292 self.setActive()
293
294 # name of text indicating there are no checks set
295 self.no_checks_text = None
296 #{ Interface
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 # we might change the layout, so be active
304 # IMPORTANT: if this is not the case, we might easily confuse layouts ...
305 # figure out why exactly that happens
306 curparent = self.parent()
307 self.setActive()
308
309 # map check names to actual checks
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 # HANDLE NO CHECKS
317 ####################
318 if checks and self.no_checks_text:
319 self.no_checks_text.delete()
320 self.no_checks_text = None
321 # END checks text existed
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 # END no checks existed handling
329
330
331 # SET EVENTS
332 #############
333 # NOTE: currently we only register ourselves for callbacks, and deregeister
334 # automatically through the weak reference system
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 # END for each check
349
350 # POSSIBLY ADD BUTTON TO THE END
351 #################################
352 # remove possibly existing button ( ignore the flag, its about the button here )
353 # its stored in a column layout
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 # create child layout ?
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 # END button layout setup
370 self.setActive()
371 # END if run all button is requested
372
373 # setup form layout - depending on the amount of items - we have 1 or two
374 # children, never more
375 children = self.listChildren()
376 assert len( children ) < 3
377 o = 2 # offset
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 # END case one child
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 # END case two children
391
392 # reset to the previous parent
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 #} END interface
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 # END for each of our children
419 return outids
420
422 """Assure we have the proper layouts active"""
423 if eventid == self.eSetItemCBID.preCreate:
424 self.col_layout.setActive()
425 if eventid == self.eSetItemCBID.postCreate:
426 self.setActive()
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
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
447 """Delete the user interface portion representing the checkid"""
448 self.col_layout.deleteChild( name_to_child_map[ checkid ] )
449
450 #{ Eventhandlers
451
453 """:return: True if the given `QACheckLayout` manages the given check"""
454 return checkLayout.check() == check
455
457 """Called for the given event - it will find the UI element handling the
458 call respective function on the UI instance
459
460 :note: find the check using predefined names as they server as unique-enough keys.
461 This would possibly be faster, but might not make a difference after all"""
462
463 # as we do not track the deletion of the window, our class might actually
464 # persist even though the window is long gone - throw if we are not existing
465 # to get auto-removed from the handler
466 assert self.exists()
467
468 # find a child handling the given check
469 # skip ones we do not find
470 checkchild = None
471 for child in self.checkLayouts():
472 if self._checkLayoutHasCheck( child, check ):
473 checkchild = child
474 break
475 # END if check matches
476 # END for each child in children
477
478 # this could actually happen as we get calls for all checks, not only
479 # for the ones we actually have
480 if checkchild is None:
481 return
482
483 if event == self.qaworkflowcls.e_preCheck:
484 checkchild.preCheck( )
485 elif event == self.qaworkflowcls.e_postCheck:
486 result = args[0]
487 checkchild.postCheck( result )
488 elif event == self.qaworkflowcls.e_checkError:
489 exc = args[0]
490 wfl = args[1]
491 checkchild.checkError( exc, wfl )
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 # END check assertion
506
507 wfl = checks[0].node.workflow()
508 wfl.runChecks( checks, clear_result = 1, **kwargs )
509
510 #} END Eventhandlers
511
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Apr 19 18:00:25 2011 | http://epydoc.sourceforge.net |