Package muntjac :: Package ui :: Module abstract_text_field
[hide private]
[frames] | no frames]

Source Code for Module muntjac.ui.abstract_text_field

  1  # Copyright (C) 2012 Vaadin Ltd.  
  2  # Copyright (C) 2012 Richard Lincoln 
  3  #  
  4  # Licensed under the Apache License, Version 2.0 (the "License");  
  5  # you may not use this file except in compliance with the License.  
  6  # You may obtain a copy of the License at  
  7  #  
  8  #     http://www.apache.org/licenses/LICENSE-2.0  
  9  #  
 10  # Unless required by applicable law or agreed to in writing, software  
 11  # distributed under the License is distributed on an "AS IS" BASIS,  
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
 13  # See the License for the specific language governing permissions and  
 14  # limitations under the License. 
 15   
 16  from warnings import warn 
 17   
 18  from muntjac.ui.abstract_field import AbstractField 
 19  from muntjac.terminal.gwt.client.ui.v_text_field import VTextField 
 20   
 21  from muntjac.event.field_events import \ 
 22      BlurEvent, IBlurListener, IBlurNotifier, FocusEvent, IFocusListener, \ 
 23      IFocusNotifier, TextChangeEvent, ITextChangeListener, ITextChangeNotifier,\ 
 24      EVENT_METHOD 
25 26 27 -class AbstractTextField(AbstractField, IBlurNotifier, IFocusNotifier, 28 ITextChangeNotifier):
29
30 - def __init__(self):
31 super(AbstractTextField, self).__init__() 32 33 #: Value formatter used to format the string contents. 34 self._format = None 35 36 #: Null representation. 37 self._nullRepresentation = 'null' 38 39 #: Is setting to null from non-null value allowed by setting 40 # with null representation. 41 self._nullSettingAllowed = False 42 43 #: Maximum character count in text field. 44 self._maxLength = -1 45 46 #: Number of visible columns in the TextField. 47 self._columns = 0 48 49 #: The prompt to display in an empty field. Null when disabled. 50 self._inputPrompt = None 51 52 #: The text content when the last messages to the server was sent. 53 # Cleared when value is changed. 54 self._lastKnownTextContent = None 55 56 #: The position of the cursor when the last message to the server 57 # was sent. 58 self._lastKnownCursorPosition = None 59 60 #: Flag indicating that a text change event is pending to be 61 # triggered. Cleared by L{setInternalValue} and when the event 62 # is fired. 63 self._textChangeEventPending = None 64 65 self._textChangeEventMode = TextChangeEventMode.LAZY 66 67 self._DEFAULT_TEXTCHANGE_TIMEOUT = 400 68 69 self._textChangeEventTimeout = self._DEFAULT_TEXTCHANGE_TIMEOUT 70 71 #: Temporarily holds the new selection position. Cleared on paint. 72 self._selectionPosition = -1 73 74 #: Temporarily holds the new selection length. 75 self._selectionLength = None 76 77 #: Flag used to determine whether we are currently handling a state 78 # change triggered by a user. Used to properly fire text change 79 # event before value change event triggered by the client side. 80 self._changingVariables = None
81 82
83 - def paintContent(self, target):
84 super(AbstractTextField, self).paintContent(target) 85 86 if self.getMaxLength() >= 0: 87 target.addAttribute('maxLength', self.getMaxLength()) 88 89 # Adds the number of column and rows 90 columns = self.getColumns() 91 if columns != 0: 92 target.addAttribute('cols', str(columns)) 93 94 if self.getInputPrompt() is not None: 95 target.addAttribute('prompt', self.getInputPrompt()) 96 97 # Adds the content as variable 98 value = self.getFormattedValue() 99 if value is None: 100 value = self.getNullRepresentation() 101 if value is None: 102 raise ValueError('Null values are not allowed if ' 103 'the null-representation is null') 104 105 target.addVariable(self, 'text', value) 106 107 if self._selectionPosition != -1: 108 target.addAttribute('selpos', self._selectionPosition) 109 target.addAttribute('sellen', self._selectionLength) 110 self._selectionPosition = -1 111 112 if self.hasListeners(TextChangeEvent): 113 target.addAttribute(VTextField.ATTR_TEXTCHANGE_EVENTMODE, 114 str(self.getTextChangeEventMode())) 115 target.addAttribute(VTextField.ATTR_TEXTCHANGE_TIMEOUT, 116 self.getTextChangeTimeout()) 117 118 if self._lastKnownTextContent is not None: 119 # The field has be repainted for some reason (e.g. caption, 120 # size, stylename), but the value has not been changed since 121 # the last text change event. Let the client side know about 122 # the value the server side knows. Client side may then ignore 123 # the actual value, depending on its state. 124 target.addAttribute( 125 VTextField.ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS, True)
126 127
128 - def getFormattedValue(self):
129 """Gets the formatted string value. Sets the field value by using 130 the assigned format. 131 132 @return: the Formatted value. 133 @see: L{setFormat} 134 @deprecated: 135 """ 136 warn('deprecated', DeprecationWarning) 137 138 v = self.getValue() 139 if v is None: 140 return None 141 return str(v)
142 143
144 - def getValue(self):
145 v = super(AbstractTextField, self).getValue() 146 if self._format is None or v is None: 147 return v 148 149 try: 150 warn('deprecated', DeprecationWarning) 151 return self._format.format(v) # FIXME format 152 except ValueError: 153 return v
154 155
156 - def changeVariables(self, source, variables):
157 self._changingVariables = True 158 159 try: 160 super(AbstractTextField, self).changeVariables(source, variables) 161 162 if VTextField.VAR_CURSOR in variables: 163 obj = variables.get( VTextField.VAR_CURSOR ) 164 self._lastKnownCursorPosition = int(obj) 165 166 if VTextField.VAR_CUR_TEXT in variables: 167 # NOTE, we might want to develop this further so that on a 168 # value change event the whole text content don't need to 169 # be sent from the client to server. Just "commit" the value 170 # from currentText to the value. 171 self.handleInputEventTextChange(variables) 172 173 # Sets the text 174 if 'text' in variables and not self.isReadOnly(): 175 176 # Only do the setting if the string representation of the 177 # value has been updated 178 newValue = variables.get('text') 179 180 # server side check for max length 181 if (self.getMaxLength() != -1 182 and len(newValue) > self.getMaxLength()): 183 newValue = newValue[:self.getMaxLength()] 184 185 oldValue = self.getFormattedValue() 186 187 if (newValue is not None 188 and (oldValue is None or self.isNullSettingAllowed()) 189 and newValue == self.getNullRepresentation()): 190 newValue = None 191 192 if (newValue != oldValue 193 and (newValue is None or newValue != oldValue)): 194 wasModified = self.isModified() 195 self.setValue(newValue, True) 196 197 # If the modified status changes, or if we have a 198 # formatter, repaint is needed after all. 199 if (self._format is not None 200 or wasModified != self.isModified()): 201 self.requestRepaint() 202 203 self.firePendingTextChangeEvent() 204 205 if FocusEvent.EVENT_ID in variables: 206 self.fireEvent( FocusEvent(self) ) 207 208 if BlurEvent.EVENT_ID in variables: 209 self.fireEvent( BlurEvent(self) ) 210 finally: 211 self._changingVariables = False
212 213
214 - def getType(self):
215 return str
216 217
218 - def getNullRepresentation(self):
219 """Gets the null-string representation. 220 221 The null-valued strings are represented on the user interface by 222 replacing the null value with this string. If the null representation 223 is set null (not 'null' string), painting null value throws exception. 224 225 The default value is string 'null'. 226 227 @return: the textual string representation for null strings. 228 @see: L{TextField.isNullSettingAllowed} 229 """ 230 return self._nullRepresentation
231 232
233 - def isNullSettingAllowed(self):
234 """Is setting nulls with null-string representation allowed. 235 236 If this property is true, writing null-representation string to text 237 field always sets the field value to real null. If this property is 238 false, null setting is not made, but the null values are maintained. 239 Maintenance of null-values is made by only converting the textfield 240 contents to real null, if the text field matches the null-string 241 representation and the current value of the field is null. 242 243 By default this setting is false 244 245 @return: Should the null-string represenation be always 246 converted to null-values. 247 @see: L{TextField.getNullRepresentation} 248 """ 249 return self._nullSettingAllowed
250 251
252 - def setNullRepresentation(self, nullRepresentation):
253 """Sets the null-string representation. 254 255 The null-valued strings are represented on the user interface by 256 replacing the null value with this string. If the null representation 257 is set null (not 'null' string), painting null value throws exception. 258 259 The default value is string 'null' 260 261 @param nullRepresentation: 262 Textual representation for null strings. 263 @see: L{TextField.setNullSettingAllowed} 264 """ 265 self._nullRepresentation = nullRepresentation 266 self.requestRepaint()
267 268
269 - def setNullSettingAllowed(self, nullSettingAllowed):
270 """Sets the null conversion mode. 271 272 If this property is true, writing null-representation string to text 273 field always sets the field value to real null. If this property is 274 false, null setting is not made, but the null values are maintained. 275 Maintenance of null-values is made by only converting the textfield 276 contents to real null, if the text field matches the null-string 277 representation and the current value of the field is null. 278 279 By default this setting is false. 280 281 @param nullSettingAllowed: 282 Should the null-string representation always be converted 283 to null-values. 284 @see: L{TextField.getNullRepresentation} 285 """ 286 self._nullSettingAllowed = nullSettingAllowed 287 self.requestRepaint()
288 289
290 - def getFormat(self):
291 """Gets the value formatter of TextField. 292 293 @return: the format used to format the value. 294 @deprecated: replaced by L{PropertyFormatter} 295 """ 296 warn('replaced by PropertyFormatter', DeprecationWarning) 297 return self._format
298 299
300 - def setFormat(self, fmt):
301 """Gets the value formatter of TextField. 302 303 @param fmt: 304 the Format used to format the value. Null disables the 305 formatting. 306 @deprecated: replaced by L{PropertyFormatter} 307 """ 308 warn('replaced by PropertyFormatter', DeprecationWarning) 309 self._format = fmt 310 self.requestRepaint()
311 312
313 - def isEmpty(self):
314 return (super(AbstractTextField, self).isEmpty() 315 or len(str(self)) == 0)
316 317
318 - def getMaxLength(self):
319 """Returns the maximum number of characters in the field. Value -1 is 320 considered unlimited. Terminal may however have some technical limits. 321 322 @return: the maxLength 323 """ 324 return self._maxLength
325 326
327 - def setMaxLength(self, maxLength):
328 """Sets the maximum number of characters in the field. Value -1 is 329 considered unlimited. Terminal may however have some technical limits. 330 331 @param maxLength: 332 the maxLength to set 333 """ 334 self._maxLength = maxLength 335 self.requestRepaint()
336 337
338 - def getColumns(self):
339 """Gets the number of columns in the editor. If the number of columns 340 is set 0, the actual number of displayed columns is determined 341 implicitly by the adapter. 342 343 @return: the number of columns in the editor. 344 """ 345 return self._columns
346 347
348 - def setColumns(self, columns):
349 """Sets the number of columns in the editor. If the number of columns 350 is set 0, the actual number of displayed columns is determined 351 implicitly by the adapter. 352 353 @param columns: 354 the number of columns to set. 355 """ 356 if columns < 0: 357 columns = 0 358 self._columns = columns 359 self.requestRepaint()
360 361
362 - def getInputPrompt(self):
363 """Gets the current input prompt. 364 365 @see: L{setInputPrompt} 366 @return: the current input prompt, or null if not enabled 367 """ 368 return self._inputPrompt
369 370
371 - def setInputPrompt(self, inputPrompt):
372 """Sets the input prompt - a textual prompt that is displayed when 373 the field would otherwise be empty, to prompt the user for input. 374 """ 375 self._inputPrompt = inputPrompt 376 self.requestRepaint()
377 378
380 if self._textChangeEventPending: 381 self._textChangeEventPending = False 382 self.fireEvent( TextChangeEventImpl(self) )
383 384
385 - def setInternalValue(self, newValue):
386 if self._changingVariables and not self._textChangeEventPending: 387 # TODO: check for possible (minor?) issue (not tested) 388 # 389 # -field with e.g. PropertyFormatter. 390 # 391 # -TextChangeListener and it changes value. 392 # 393 # -if formatter again changes the value, do we get an extra 394 # simulated text change event ? 395 396 # Fire a "simulated" text change event before value change event 397 # if change is coming from the client side. 398 # 399 # Iff there is both value change and textChangeEvent in same 400 # variable burst, it is a text field in non immediate mode and 401 # the text change event "flushed" queued value change event. In 402 # this case textChangeEventPending flag is already on and text 403 # change event will be fired after the value change event. 404 if (newValue is None and self._lastKnownTextContent is not None 405 and self._lastKnownTextContent != \ 406 self.getNullRepresentation()): 407 # Value was changed from something to null representation 408 self._lastKnownTextContent = self.getNullRepresentation() 409 self._textChangeEventPending = True 410 elif (newValue is not None 411 and str(newValue) != self._lastKnownTextContent): 412 # Value was changed to something else than null 413 # representation 414 self._lastKnownTextContent = str(newValue) 415 self._textChangeEventPending = True 416 417 self.firePendingTextChangeEvent() 418 419 # Reset lastKnownTextContent field on value change. We know the value 420 # now. 421 self._lastKnownTextContent = None 422 super(AbstractTextField, self).setInternalValue(newValue)
423 424
425 - def setValue(self, newValue, repaintIsNotNeeded=None):
426 if repaintIsNotNeeded is not None: 427 super(AbstractTextField, self).setValue(newValue, 428 repaintIsNotNeeded) 429 else: 430 super(AbstractTextField, self).setValue(newValue) 431 432 # Make sure w reset lastKnownTextContent field on value change. The 433 # clearing must happen here as well because TextChangeListener can 434 # revert the original value. Client must respect the value in this 435 # case. AbstractField optimizes value change if the existing value is 436 # reset. Also we need to force repaint if the flag is on. 437 438 if self._lastKnownTextContent is not None: 439 self._lastKnownTextContent = None 440 self.requestRepaint()
441 442
443 - def handleInputEventTextChange(self, variables):
444 # TODO we could vastly optimize the communication of values by 445 # using some sort of diffs instead of always sending the whole 446 # text content. Also on value change events we could use the 447 # mechanism. 448 obj = variables.get(VTextField.VAR_CUR_TEXT) 449 self._lastKnownTextContent = obj 450 self._textChangeEventPending = True
451 452
453 - def setTextChangeEventMode(self, inputEventMode):
454 """Sets the mode how the TextField triggers L{TextChangeEvent}s. 455 456 @param inputEventMode: the new mode 457 458 @see: L{TextChangeEventMode} 459 """ 460 self._textChangeEventMode = inputEventMode 461 self.requestRepaint()
462 463
464 - def getTextChangeEventMode(self):
465 """@return: the mode used to trigger L{TextChangeEvent}s.""" 466 return self._textChangeEventMode
467 468
469 - def addListener(self, listener, iface=None):
470 if (isinstance(listener, IBlurListener) and 471 (iface is None or issubclass(iface, IBlurListener))): 472 self.registerListener(BlurEvent.EVENT_ID, BlurEvent, 473 listener, IBlurListener.blurMethod) 474 475 if (isinstance(listener, IFocusListener) and 476 (iface is None or issubclass(iface, IFocusListener))): 477 self.registerListener(FocusEvent.EVENT_ID, FocusEvent, 478 listener, IFocusListener.focusMethod) 479 480 if (isinstance(listener, ITextChangeListener) and 481 (iface is None or issubclass(iface, ITextChangeListener))): 482 self.registerListener(ITextChangeListener.EVENT_ID, 483 TextChangeEvent, listener, EVENT_METHOD) 484 485 super(AbstractTextField, self).addListener(listener, iface)
486 487
488 - def addCallback(self, callback, eventType=None, *args):
489 if eventType is None: 490 eventType = callback._eventType 491 492 if issubclass(eventType, BlurEvent): 493 self.registerCallback(BlurEvent, callback, 494 BlurEvent.EVENT_ID, *args) 495 496 elif issubclass(eventType, FocusEvent): 497 self.registerCallback(FocusEvent, callback, 498 FocusEvent.EVENT_ID, *args) 499 500 elif issubclass(eventType, TextChangeEvent): 501 self.registerCallback(TextChangeEvent, callback, 502 ITextChangeListener.EVENT_ID, *args) 503 504 else: 505 super(AbstractTextField, self).addCallback(callback, 506 eventType, *args)
507 508
509 - def removeListener(self, listener, iface=None):
510 if (isinstance(listener, IBlurListener) and 511 (iface is None or issubclass(iface, IBlurListener))): 512 self.withdrawListener(BlurEvent.EVENT_ID, BlurEvent, listener) 513 514 if (isinstance(listener, IFocusListener) and 515 (iface is None or issubclass(iface, IFocusListener))): 516 self.withdrawListener(FocusEvent.EVENT_ID, FocusEvent, listener) 517 518 if (isinstance(listener, ITextChangeListener) and 519 (iface is None or issubclass(iface, ITextChangeListener))): 520 self.withdrawListener(ITextChangeListener.EVENT_ID, 521 TextChangeEvent, listener) 522 523 super(AbstractTextField, self).addListener(listener, iface)
524 525
526 - def removeCallback(self, callback, eventType=None):
527 if eventType is None: 528 eventType = callback._eventType 529 530 if issubclass(eventType, BlurEvent): 531 self.withdrawCallback(BlurEvent, callback, BlurEvent.EVENT_ID) 532 533 elif issubclass(eventType, FocusEvent): 534 self.withdrawCallback(FocusEvent, callback, FocusEvent.EVENT_ID) 535 536 elif issubclass(eventType, TextChangeEvent): 537 self.withdrawCallback(TextChangeEvent, callback, 538 ITextChangeListener.EVENT_ID) 539 540 else: 541 super(AbstractTextField, self).removeCallback(callback, eventType)
542 543
544 - def setTextChangeTimeout(self, timeout):
545 """The text change timeout modifies how often text change events 546 are communicated to the application when 547 L{getTextChangeEventMode} is L{TextChangeEventMode.LAZY} 548 or L{TextChangeEventMode.TIMEOUT}. 549 550 @see: L{getTextChangeEventMode} 551 552 @param timeout: the timeout in milliseconds 553 """ 554 self._textChangeEventTimeout = timeout 555 self.requestRepaint()
556 557
558 - def getTextChangeTimeout(self):
559 """Gets the timeout used to fire L{TextChangeEvent}s when the 560 L{getTextChangeEventMode} is L{TextChangeEventMode.LAZY} 561 or L{TextChangeEventMode.TIMEOUT}. 562 563 @return: the timeout value in milliseconds 564 """ 565 return self._textChangeEventTimeout
566 567
568 - def getCurrentTextContent(self):
569 """Gets the current (or the last known) text content in the field. 570 571 Note the text returned by this method is not necessary the same that 572 is returned by the L{getValue} method. The value is updated 573 when the terminal fires a value change event via e.g. blurring the 574 field or by pressing enter. The value returned by this method is 575 updated also on L{TextChangeEvent}s. Due to this high dependency 576 to the terminal implementation this method is (at least at this 577 point) not published. 578 579 @return: the text which is currently displayed in the field. 580 """ 581 if self._lastKnownTextContent is not None: 582 return self._lastKnownTextContent 583 else: 584 text = self.getValue() 585 if text is None: 586 return self.getNullRepresentation() 587 return str(text)
588 589
590 - def selectAll(self):
591 """Selects all text in the field. 592 """ 593 text = '' if self.getValue() is None else str(self.getValue()) 594 self.setSelectionRange(0, len(text))
595 596
597 - def setSelectionRange(self, pos, length):
598 """Sets the range of text to be selected. 599 600 As a side effect the field will become focused. 601 602 @param pos: 603 the position of the first character to be selected 604 @param length: 605 the number of characters to be selected 606 """ 607 self._selectionPosition = pos 608 self._selectionLength = length 609 self.focus() 610 self.requestRepaint()
611 612
613 - def setCursorPosition(self, pos):
614 """Sets the cursor position in the field. As a side effect the 615 field will become focused. 616 617 @param pos: 618 the position for the cursor 619 """ 620 self.setSelectionRange(pos, 0) 621 self._lastKnownCursorPosition = pos
622 623
624 - def getCursorPosition(self):
625 """Returns the last known cursor position of the field. 626 627 Note that due to the client server nature or the GWT terminal, Muntjac 628 cannot provide the exact value of the cursor position in most 629 situations. The value is updated only when the client side terminal 630 communicates to TextField, like on L{ValueChangeEvent}s and 631 L{TextChangeEvent}s. This may change later if a deep push 632 integration is built to Muntjac. 633 634 @return: the cursor position 635 """ 636 return self._lastKnownCursorPosition
637
638 639 -class TextChangeEventMode(object):
640 """Different modes how the TextField can trigger L{TextChangeEvent}s. 641 """ 642 643 #: An event is triggered on each text content change, most commonly key 644 # press events. 645 EAGER = 'EAGER' 646 647 #: Each text change event in the UI causes the event to be communicated 648 # to the application after a timeout. The length of the timeout can be 649 # controlled with L{TextField.setInputEventTimeout}. Only the 650 # last input event is reported to the server side if several text 651 # change events happen during the timeout. 652 # 653 # In case of a L{ValueChangeEvent} the schedule is not kept 654 # strictly. Before a L{ValueChangeEvent} a L{TextChangeEvent} 655 # is triggered if the text content has changed since the previous 656 # TextChangeEvent regardless of the schedule. 657 TIMEOUT = 'TIMEOUT' 658 659 #: An event is triggered when there is a pause of text modifications. 660 # The length of the pause can be modified with 661 # L{TextField.setInputEventTimeout}. Like with the 662 # L{TIMEOUT} mode, an event is forced before 663 # L{ValueChangeEvent}s, even if the user did not keep a pause 664 # while entering the text. 665 # 666 # This is the default mode. 667 LAZY = 'LAZY' 668 669 _values = [EAGER, TIMEOUT, LAZY] 670 671 @classmethod
672 - def values(cls):
673 return cls._values[:]
674
675 676 -class TextChangeEventImpl(TextChangeEvent):
677
678 - def __init__(self, tf):
679 super(TextChangeEventImpl, self).__init__(tf) 680 self._curText = tf.getCurrentTextContent() 681 self._cursorPosition = tf.getCursorPosition()
682 683
684 - def getComponent(self):
685 return super(TextChangeEventImpl, self).getComponent()
686 687
688 - def getText(self):
689 return self._curText
690 691
692 - def getCursorPosition(self):
693 return self._cursorPosition
694