2408
+ − 1 #!/usr/bin/env python2
+ − 2 # -*- coding: utf-8 -*-
+ − 3
+ − 4 # JP: a SàT frontend
+ − 5 # Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+ − 6
+ − 7 # This program is free software: you can redistribute it and/or modify
+ − 8 # it under the terms of the GNU Affero 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 # This program 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 Affero General Public License for more details.
+ − 16
+ − 17 # You should have received a copy of the GNU Affero General Public License
+ − 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
+ − 19
+ − 20 from sat.core.log import getLogger
+ − 21 log = getLogger ( __name__ )
+ − 22 from sat_frontends.tools import xmlui as xmlui_manager
+ − 23 from sat_frontends.jp.constants import Const as C
+ − 24 from sat.tools.common.ansi import ANSI as A
+ − 25 from sat.core.i18n import _
+ − 26 from functools import partial
+ − 27
+ − 28 # workflow constants
+ − 29
+ − 30 SUBMIT = 'SUBMIT' # submit form
+ − 31
+ − 32
+ − 33
+ − 34 ## Widgets ##
+ − 35
+ − 36 class Base ( object ):
+ − 37 """Base for Widget and Container"""
+ − 38 type = None
+ − 39 _root = None
+ − 40
+ − 41 def __init__ ( self , xmlui_parent ):
+ − 42 self . xmlui_parent = xmlui_parent
+ − 43 self . host = self . xmlui_parent . host
+ − 44
+ − 45 @property
+ − 46 def root ( self ):
+ − 47 """retrieve main XMLUI parent class"""
+ − 48 if self . _root is not None :
+ − 49 return self . _root
+ − 50 root = self
+ − 51 while not isinstance ( root , xmlui_manager . XMLUIBase ):
+ − 52 root = root . xmlui_parent
+ − 53 self . _root = root
+ − 54 return root
+ − 55
+ − 56 def disp ( self , * args , ** kwargs ):
+ − 57 self . host . disp ( * args , ** kwargs )
+ − 58
+ − 59
+ − 60 class Widget ( Base ):
+ − 61 category = u 'widget'
+ − 62 enabled = True
+ − 63
+ − 64 @property
+ − 65 def name ( self ):
+ − 66 return self . _xmlui_name
+ − 67
+ − 68 def show ( self ):
+ − 69 """display current widget
+ − 70
+ − 71 must be overriden by subclasses
+ − 72 """
+ − 73 raise NotImplementedError ( self . __class__ )
+ − 74
+ − 75 def verboseName ( self , elems = None , value = None ):
+ − 76 """add name in color to the elements
+ − 77
+ − 78 helper method to display name which can then be used to automate commands
+ − 79 elems is only modified if verbosity is > 0
+ − 80 @param elems(list[unicode], None): elements to display
+ − 81 None to display name directly
+ − 82 @param value(unicode, None): value to show
+ − 83 use self.name if None
+ − 84 """
+ − 85 if value is None :
+ − 86 value = self . name
+ − 87 if self . host . verbosity :
+ − 88 to_disp = [ A . FG_MAGENTA ,
+ − 89 u ' ' if elems else u '' ,
+ − 90 u '( {} )' . format ( value ), A . RESET ]
+ − 91 if elems is None :
+ − 92 self . host . disp ( A . color ( * to_disp ))
+ − 93 else :
+ − 94 elems . extend ( to_disp )
+ − 95
+ − 96 class ValueWidget ( Widget ):
+ − 97
+ − 98 def __init__ ( self , xmlui_parent , value ):
+ − 99 super ( ValueWidget , self ) . __init__ ( xmlui_parent )
+ − 100 self . value = value
+ − 101
+ − 102 @property
+ − 103 def values ( self ):
+ − 104 return [ self . value ]
+ − 105
+ − 106
+ − 107 class InputWidget ( ValueWidget ):
+ − 108
+ − 109 def __init__ ( self , xmlui_parent , value , read_only = False ):
+ − 110 super ( InputWidget , self ) . __init__ ( xmlui_parent , value )
+ − 111 self . read_only = read_only
+ − 112
+ − 113 def _xmluiGetValue ( self ):
+ − 114 return self . value
+ − 115
+ − 116
+ − 117 class OptionsWidget ( Widget ):
+ − 118
+ − 119 def __init__ ( self , xmlui_parent , options , selected , style ):
+ − 120 super ( OptionsWidget , self ) . __init__ ( xmlui_parent )
+ − 121 self . options = options
+ − 122 self . selected = selected
+ − 123 self . style = style
+ − 124
+ − 125 @property
+ − 126 def values ( self ):
+ − 127 return self . selected
+ − 128
+ − 129 @values . setter
+ − 130 def values ( self , values ):
+ − 131 self . selected = values
+ − 132
+ − 133 @property
+ − 134 def value ( self ):
+ − 135 return self . selected [ 0 ]
+ − 136
+ − 137 @value . setter
+ − 138 def value ( self , value ):
+ − 139 self . selected = [ value ]
+ − 140
+ − 141 def _xmluiSelectValue ( self , value ):
+ − 142 self . value = value
+ − 143
+ − 144 def _xmluiSelectValues ( self , values ):
+ − 145 self . values = values
+ − 146
+ − 147 def _xmluiGetSelectedValues ( self ):
+ − 148 return self . values
+ − 149
+ − 150 @property
+ − 151 def labels ( self ):
+ − 152 """return only labels from self.items"""
+ − 153 for value , label in self . items :
+ − 154 yield label
+ − 155
+ − 156 @property
+ − 157 def items ( self ):
+ − 158 """return suitable items, according to style"""
+ − 159 no_select = self . no_select
+ − 160 for value , label in self . options :
+ − 161 if no_select or value in self . selected :
+ − 162 yield value , label
+ − 163
+ − 164 @property
+ − 165 def inline ( self ):
+ − 166 return u 'inline' in self . style
+ − 167
+ − 168 @property
+ − 169 def no_select ( self ):
+ − 170 return u 'noselect' in self . style
+ − 171
+ − 172
+ − 173 class EmptyWidget ( xmlui_manager . EmptyWidget , Widget ):
+ − 174
+ − 175 def __init__ ( self , _xmlui_parent ):
+ − 176 Widget . __init__ ( self )
+ − 177
+ − 178
+ − 179 class TextWidget ( xmlui_manager . TextWidget , ValueWidget ):
+ − 180 type = u "text"
+ − 181
+ − 182 def show ( self ):
+ − 183 self . host . disp ( self . value )
+ − 184
+ − 185
+ − 186 class LabelWidget ( xmlui_manager . LabelWidget , ValueWidget ):
+ − 187 type = u "label"
+ − 188
+ − 189 @property
+ − 190 def for_name ( self ):
+ − 191 try :
+ − 192 return self . _xmlui_for_name
+ − 193 except AttributeError :
+ − 194 return None
+ − 195
+ − 196 def show ( self , no_lf = False , ansi = u '' ):
+ − 197 """show label
+ − 198
+ − 199 @param no_lf(bool): same as for [JP.disp]
+ − 200 @param ansi(unicode): ansi escape code to print before label
+ − 201 """
+ − 202 self . disp ( A . color ( ansi , self . value ), no_lf = no_lf )
+ − 203
+ − 204
+ − 205 class StringWidget ( xmlui_manager . StringWidget , InputWidget ):
+ − 206 type = u "string"
+ − 207
+ − 208 def show ( self ):
+ − 209 if self . read_only :
+ − 210 self . disp ( self . value )
+ − 211 else :
+ − 212 elems = []
+ − 213 self . verboseName ( elems )
+ − 214 if self . value :
+ − 215 elems . append ( _ ( u '(enter: {default} )' ) . format ( default = self . value ))
+ − 216 elems . extend ([ C . A_HEADER , u '> ' ])
+ − 217 value = raw_input ( A . color ( * elems ))
+ − 218 if value :
+ − 219 # TODO: empty value should be possible
+ − 220 # an escape key should be used for default instead of enter with empty value
+ − 221 self . value = value
+ − 222
+ − 223
+ − 224
+ − 225 class JidInputWidget ( xmlui_manager . JidInputWidget , StringWidget ):
+ − 226 type = u 'jid_input'
+ − 227
+ − 228
+ − 229 class TextBoxWidget ( xmlui_manager . TextWidget , StringWidget ):
+ − 230 type = u "textbox"
+ − 231
+ − 232
+ − 233 class ListWidget ( xmlui_manager . ListWidget , OptionsWidget ):
+ − 234 type = u 'list'
+ − 235 # TODO: handle flags, notably multi
+ − 236
+ − 237 def show ( self ):
+ − 238 if not self . options :
+ − 239 return
+ − 240
+ − 241 # list display
+ − 242 self . verboseName ()
+ − 243
+ − 244 for idx , ( value , label ) in enumerate ( self . options ):
+ − 245 elems = []
+ − 246 if not self . root . readonly :
+ − 247 elems . extend ([ C . A_SUBHEADER , unicode ( idx ), A . RESET , u ': ' ])
+ − 248 elems . append ( label )
+ − 249 self . verboseName ( elems , value )
+ − 250 self . disp ( A . color ( * elems ))
+ − 251
+ − 252 if self . root . readonly :
+ − 253 return
+ − 254
+ − 255 if len ( self . options ) == 1 :
+ − 256 # we have only one option, no need to ask
+ − 257 self . value = self . options [ 0 ][ 0 ]
+ − 258 return
+ − 259
+ − 260 # we ask use to choose an option
+ − 261 choice = None
+ − 262 limit_max = len ( self . options ) - 1
+ − 263 while choice is None or choice < 0 or choice > limit_max :
+ − 264 choice = raw_input ( A . color ( C . A_HEADER , _ ( u 'your choice (0- {max} ): ' ) . format ( max = limit_max )))
+ − 265 try :
+ − 266 choice = int ( choice )
+ − 267 except ValueError :
+ − 268 choice = None
+ − 269 self . value = self . options [ choice ][ 0 ]
+ − 270 self . disp ( '' )
+ − 271
+ − 272
+ − 273 class BoolWidget ( xmlui_manager . BoolWidget , InputWidget ):
+ − 274 type = u 'bool'
+ − 275
+ − 276 def show ( self ):
+ − 277 disp_true = A . color ( A . FG_GREEN , u 'TRUE' )
+ − 278 disp_false = A . color ( A . FG_RED , u 'FALSE' )
+ − 279 if self . read_only :
+ − 280 self . disp ( disp_true if self . value else disp_false )
+ − 281 else :
+ − 282 self . disp ( A . color ( C . A_HEADER , u '0: ' , disp_false ))
+ − 283 self . disp ( A . color ( C . A_HEADER , u '1: ' , disp_true ))
+ − 284 choice = None
+ − 285 while choice not in ( '0' , '1' ):
+ − 286 elems = [ C . A_HEADER , _ ( u 'your choice (0,1): ' )]
+ − 287 self . verboseName ( elems )
+ − 288 choice = raw_input ( A . color ( * elems ))
+ − 289 self . value = bool ( int ( choice ))
+ − 290 self . disp ( '' )
+ − 291
+ − 292 def _xmluiGetValue ( self ):
+ − 293 return C . boolConst ( self . value )
+ − 294
+ − 295 ## Containers ##
+ − 296
+ − 297 class Container ( Base ):
+ − 298 category = u 'container'
+ − 299
+ − 300 def __init__ ( self , xmlui_parent ):
+ − 301 super ( Container , self ) . __init__ ( xmlui_parent )
+ − 302 self . children = []
+ − 303
+ − 304 def __iter__ ( self ):
+ − 305 return iter ( self . children )
+ − 306
+ − 307 def _xmluiAppend ( self , widget ):
+ − 308 self . children . append ( widget )
+ − 309
+ − 310 def show ( self ):
+ − 311 for child in self . children :
+ − 312 child . show ()
+ − 313
+ − 314
+ − 315 class VerticalContainer ( xmlui_manager . VerticalContainer , Container ):
+ − 316 type = u 'vertical'
+ − 317
+ − 318
+ − 319 class PairsContainer ( xmlui_manager . PairsContainer , Container ):
+ − 320 type = u 'pairs'
+ − 321
+ − 322
+ − 323 class LabelContainer ( xmlui_manager . PairsContainer , Container ):
+ − 324 type = u 'label'
+ − 325
+ − 326 def show ( self ):
+ − 327 for child in self . children :
+ − 328 no_lf = False
+ − 329 # we check linked widget type
+ − 330 # to see if we want the label on the same line or not
+ − 331 if child . type == u 'label' :
+ − 332 for_name = child . for_name
+ − 333 if for_name is not None :
+ − 334 for_widget = self . root . widgets [ for_name ]
+ − 335 wid_type = for_widget . type
+ − 336 if wid_type in ( 'text' , 'string' , 'jid_input' ):
+ − 337 no_lf = True
+ − 338 elif wid_type == 'bool' and for_widget . read_only :
+ − 339 no_lf = True
+ − 340 child . show ( no_lf = no_lf , ansi = A . FG_CYAN )
+ − 341 else :
+ − 342 child . show ()
+ − 343
+ − 344 ## Dialogs ##
+ − 345
+ − 346
+ − 347 class Dialog ( object ):
+ − 348
+ − 349 def __init__ ( self , xmlui_parent ):
+ − 350 self . xmlui_parent = xmlui_parent
+ − 351 self . host = self . xmlui_parent . host
+ − 352
+ − 353 def disp ( self , * args , ** kwargs ):
+ − 354 self . host . disp ( * args , ** kwargs )
+ − 355
+ − 356 def show ( self ):
+ − 357 """display current dialog
+ − 358
+ − 359 must be overriden by subclasses
+ − 360 """
+ − 361 raise NotImplementedError ( self . __class__ )
+ − 362
+ − 363
+ − 364 class NoteDialog ( xmlui_manager . NoteDialog , Dialog ):
+ − 365
+ − 366 def show ( self ):
+ − 367 # TODO: handle title and level
+ − 368 self . disp ( self . message )
+ − 369
+ − 370 def __init__ ( self , _xmlui_parent , title , message , level ):
+ − 371 Dialog . __init__ ( self , _xmlui_parent )
+ − 372 xmlui_manager . NoteDialog . __init__ ( self , _xmlui_parent )
+ − 373 self . title , self . message , self . level = title , message , level
+ − 374
+ − 375 ## Factory ##
+ − 376
+ − 377
+ − 378 class WidgetFactory ( object ):
+ − 379
+ − 380 def __getattr__ ( self , attr ):
+ − 381 if attr . startswith ( "create" ):
+ − 382 cls = globals ()[ attr [ 6 :]]
+ − 383 return cls
+ − 384
+ − 385
+ − 386 class XMLUIPanel ( xmlui_manager . XMLUIPanel ):
+ − 387 widget_factory = WidgetFactory ()
+ − 388 _actions = 0 # use to keep track of bridge's launchAction calls
+ − 389 readonly = False
+ − 390 workflow = None
+ − 391 _submit_cb = None
+ − 392
+ − 393 def __init__ ( self , host , parsed_dom , title = None , flags = None , callback = None , profile = None ):
+ − 394 xmlui_manager . XMLUIPanel . __init__ ( self , host , parsed_dom , title , flags , profile = host . profile )
+ − 395 self . submitted = False
+ − 396
+ − 397 @property
+ − 398 def command ( self ):
+ − 399 return self . host . command
+ − 400
+ − 401 def show ( self , workflow = None ):
+ − 402 """display the panel
+ − 403
+ − 404 @param workflow(list, None): command to execute if not None
+ − 405 put here for convenience, the main workflow is the class attribute
+ − 406 (because workflow can continue in subclasses)
+ − 407 command are a list of consts or lists:
+ − 408 - SUBMIT is the only constant so far, it submits the XMLUI
+ − 409 - list must contain widget name/widget value to fill
+ − 410 """
+ − 411 if workflow :
+ − 412 XMLUIPanel . workflow = workflow
+ − 413 if XMLUIPanel . workflow :
+ − 414 self . runWorkflow ()
+ − 415 else :
+ − 416 self . main_cont . show ()
+ − 417
+ − 418 def runWorkflow ( self ):
+ − 419 """loop into workflow commands and execute commands
+ − 420
+ − 421 SUBMIT will interrupt workflow (which will be continue on callback)
+ − 422 @param workflow(list): same as [show]
+ − 423 """
+ − 424 workflow = XMLUIPanel . workflow
+ − 425 while True :
+ − 426 try :
+ − 427 cmd = workflow . pop ( 0 )
+ − 428 except IndexError :
+ − 429 break
+ − 430 if cmd == SUBMIT :
+ − 431 self . onFormSubmitted ()
+ − 432 self . submit_id = None # avoid double submit
+ − 433 return
+ − 434 elif isinstance ( cmd , list ):
+ − 435 name , value = cmd
+ − 436 self . widgets [ name ] . value = value
+ − 437 self . show ()
+ − 438
+ − 439 def submitForm ( self , callback = None ):
+ − 440 XMLUIPanel . _submit_cb = callback
+ − 441 self . onFormSubmitted ()
+ − 442
+ − 443 def onFormSubmitted ( self , ignore = None ):
+ − 444 # self.submitted is a Q&D workaround to avoid
+ − 445 # double submit when a workflow is set
+ − 446 if self . submitted :
+ − 447 return
+ − 448 self . submitted = True
+ − 449 super ( XMLUIPanel , self ) . onFormSubmitted ( ignore )
+ − 450
+ − 451 def _xmluiClose ( self ):
+ − 452 pass
+ − 453
+ − 454 def _launchActionCb ( self , data ):
+ − 455 XMLUIPanel . _actions -= 1
+ − 456 assert XMLUIPanel . _actions >= 0
+ − 457 if u 'xmlui' in data :
+ − 458 xmlui_raw = data [ 'xmlui' ]
+ − 459 xmlui = xmlui_manager . create ( self . host , xmlui_raw )
+ − 460 xmlui . show ()
+ − 461 if xmlui . submit_id :
+ − 462 xmlui . onFormSubmitted ()
+ − 463 # TODO: handle data other than XMLUI
+ − 464 if not XMLUIPanel . _actions :
+ − 465 if self . _submit_cb is None :
+ − 466 self . host . quit ()
+ − 467 else :
+ − 468 self . _submit_cb ()
+ − 469
+ − 470 def _xmluiLaunchAction ( self , action_id , data ):
+ − 471 XMLUIPanel . _actions += 1
+ − 472 self . host . bridge . launchAction (
+ − 473 action_id ,
+ − 474 data ,
+ − 475 self . profile ,
+ − 476 callback = self . _launchActionCb ,
+ − 477 errback = partial ( self . command . errback ,
+ − 478 msg = _ ( u "can't launch XMLUI action: {} " ),
+ − 479 exit_code = C . EXIT_BRIDGE_ERRBACK ))
+ − 480
+ − 481
+ − 482 class XMLUIDialog ( xmlui_manager . XMLUIDialog ):
+ − 483 type = 'dialog'
+ − 484 dialog_factory = WidgetFactory ()
+ − 485 readonly = False
+ − 486
+ − 487 def show ( self ):
+ − 488 self . dlg . show ()
+ − 489
+ − 490 def _xmluiClose ( self ):
+ − 491 pass
+ − 492
+ − 493
+ − 494 xmlui_manager . registerClass ( xmlui_manager . CLASS_PANEL , XMLUIPanel )
+ − 495 xmlui_manager . registerClass ( xmlui_manager . CLASS_DIALOG , XMLUIDialog )
+ − 496 create = xmlui_manager . create