1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Defines the default implementation for the IComponent interface."""
17
18 import re
19
20 from warnings import warn
21
22 from muntjac.event.method_event_source import IMethodEventSource
23 from muntjac.event.event_router import EventRouter
24 from muntjac.terminal.terminal import IErrorEvent as ITerminalErrorEvent
25 from muntjac.terminal.paintable import RepaintRequestEvent,\
26 IRepaintRequestListener
27 from muntjac.util import fullname
28
29 from muntjac.ui.component import \
30 IComponent, IListener, IFocusable, Event as ComponentEvent
31 from muntjac.ui import component
32
33
34 _COMPONENT_EVENT_METHOD = getattr(IListener, 'componentEvent')
35
36
38 """An abstract class that defines default implementation for the
39 L{IComponent} interface. Basic UI components that are not derived
40 from an external component can inherit this class to easily qualify
41 as Muntjac components. Most components in Muntjac do just that.
42
43 @author: Vaadin Ltd.
44 @author: Richard Lincoln
45 @version: 1.1.2
46 """
47
48 SIZE_PATTERN = '^(-?\\d+(\\.\\d+)?)(%|px|em|ex|in|cm|mm|pt|pc)?$'
49
51 """Constructs a new IComponent."""
52 super(AbstractComponent, self).__init__()
53
54
55 self._styles = None
56
57
58 self._caption = None
59
60
61
62 self._applicationData = None
63
64
65 self._icon = None
66
67
68 self._enabled = True
69
70
71 self._visible = True
72
73
74 self._readOnly = False
75
76
77 self._description = None
78
79
80 self._parent = None
81
82
83 self._eventRouter = None
84
85
86 self._eventIdentifiers = None
87
88
89 self._componentError = None
90
91
92
93 self._immediate = False
94
95
96 self._locale = None
97
98
99
100 self._delayedFocus = None
101
102
103 self._repaintRequestListeners = list()
104
105 self._repaintRequestCallbacks = dict()
106
107
108 self._repaintRequestListenersNotified = False
109
110 self._testingId = None
111
112
113 self._width = self.SIZE_UNDEFINED
114 self._height = self.SIZE_UNDEFINED
115 self._widthUnit = self.UNITS_PIXELS
116 self._heightUnit = self.UNITS_PIXELS
117
118 self._sizePattern = re.compile(self.SIZE_PATTERN)
119
120 self.errorHandler = None
121
122
123
124
126 result = self.__dict__.copy()
127 del result['_sizePattern']
128 return result
129
130
132 self.__dict__ = d
133 self._sizePattern = re.compile(self.SIZE_PATTERN)
134
135
137 self._testingId = idd
138
139
141 return self._testingId
142
143
145 """Gets style for component. Multiple styles are joined with spaces.
146
147 @return: the component's styleValue of property style.
148 @deprecated: Use getStyleName() instead; renamed for consistency and
149 to indicate that "style" should not be used to switch
150 client side implementation, only to style the component.
151 """
152 warn('Use getStyleName() instead', DeprecationWarning)
153 return self.getStyleName()
154
155
157 """Sets and replaces all previous style names of the component. This
158 method will trigger a L{RepaintRequestEvent}.
159
160 @param style:
161 the new style of the component.
162 @deprecated: Use setStyleName() instead; renamed for consistency and
163 to indicate that "style" should not be used to switch
164 client side implementation, only to style the component.
165 """
166 warn('Use setStyleName() instead', DeprecationWarning)
167 self.setStyleName(style)
168
169
171
172 s = ''
173 if self._styles is not None:
174 s = ' '.join(self._styles)
175 return s
176
177
179
180 if style is None or style == '':
181 self._styles = None
182 self.requestRepaint()
183 return
184
185 if self._styles is None:
186 self._styles = list()
187
188 del self._styles[:]
189
190 styleParts = style.split()
191 for part in styleParts:
192 if len(part) > 0:
193 self._styles.append(part)
194
195 self.requestRepaint()
196
197
199 if style is None or style == '':
200 return
201
202 if self._styles is None:
203 self._styles = list()
204
205 for s in style.split():
206 if s not in self._styles:
207 self._styles.append(s)
208 self.requestRepaint()
209
210
212 if self._styles is not None:
213 styleParts = style.split()
214 for part in styleParts:
215 if len(part) > 0 and part in self._styles:
216 self._styles.remove(part)
217 self.requestRepaint()
218
219
221
222 return self._caption
223
224
226 """Sets the component's caption string. Caption is the
227 visible name of the component. This method will trigger a
228 L{RepaintRequestEvent}.
229
230 @param caption:
231 the new caption string for the component.
232 """
233 self._caption = caption
234 self.requestRepaint()
235
236
238 if self._locale is not None:
239 return self._locale
240
241 if self._parent is not None:
242 return self._parent.getLocale()
243
244 app = self.getApplication()
245 if app is not None:
246 return app.getLocale()
247
248 return None
249
250
252 """Sets the locale of this component::
253
254 # IComponent for which the locale is meaningful
255 date = InlineDateField("Datum")
256
257 # German language specified with ISO 639-1 language
258 # code and ISO 3166-1 alpha-2 country code.
259 date.setLocale(Locale("de", "DE"))
260
261 date.setResolution(DateField.RESOLUTION_DAY)
262 layout.addComponent(date)
263
264 @param locale:
265 the locale to become this component's locale.
266 """
267 self._locale = locale
268 self.requestRepaint()
269
270
272
273 return self._icon
274
275
277 """Sets the component's icon. This method will trigger a
278 L{RepaintRequestEvent<IPaintable.RepaintRequestEvent>}.
279
280 @param icon:
281 the icon to be shown with the component's caption.
282 """
283 self._icon = icon
284 self.requestRepaint()
285
286
288
289 return (self._enabled and ((self._parent is None)
290 or (self._parent.isEnabled())) and self.isVisible())
291
292
294
295
296 if self._enabled != enabled:
297 wasEnabled = self._enabled
298 wasEnabledInContext = self.isEnabled()
299
300 self._enabled = enabled
301
302 isEnabled = enabled
303 isEnabledInContext = self.isEnabled()
304
305
306
307
308
309
310
311
312 needRepaint = ((wasEnabledInContext != isEnabledInContext)
313 or (wasEnabled != isEnabled
314 and (self.getParent() is None)
315 or (not self.getParent().isVisible())))
316
317 if needRepaint:
318 self.requestRepaint()
319
320
324
325
337
338
343
344
346 if self._visible != visible:
347 self._visible = visible
348
349
350
351
352 self.fireRequestRepaintEvent(None)
353
354
356 """Gets the component's description, used in tooltips and can be
357 displayed directly in certain other components such as forms. The
358 description can be used to briefly describe the state of the
359 component to the user. The description string may contain certain
360 XML tags::
361
362 <table border=1>
363 <tr>
364 <td width=120><b>Tag</b></td>
365 <td width=120><b>Description</b></td>
366 <td width=120><b>Example</b></td>
367 </tr>
368 <tr>
369 <td><b></td>
370 <td>bold</td>
371 <td><b>bold text</b></td>
372 </tr>
373 <tr>
374 <td><i></td>
375 <td>italic</td>
376 <td><i>italic text</i></td>
377 </tr>
378 <tr>
379 <td><u></td>
380 <td>underlined</td>
381 <td><u>underlined text</u></td>
382 </tr>
383 <tr>
384 <td><br></td>
385 <td>linebreak</td>
386 <td>N/A</td>
387 </tr>
388 <tr>
389 <td><ul><br>
390 <li>item1<br>
391 <li>item1<br>
392 </ul></td>
393 <td>item list</td>
394 <td>
395 <ul>
396 <li>item1
397 <li>item2
398 </ul>
399 </td>
400 </tr>
401 </table>
402
403 These tags may be nested.
404
405 @return: component's description string
406 """
407 return self._description
408
409
411 """Sets the component's description. See L{getDescription}
412 for more information on what the description is. This method will
413 trigger a L{RepaintRequestEvent}.
414
415 The description is displayed as HTML/XHTML in tooltips or directly in
416 certain components so care should be taken to avoid creating the
417 possibility for HTML injection and possibly XSS vulnerabilities.
418
419 @param description:
420 the new description string for the component.
421 """
422 self._description = description
423 self.requestRepaint()
424
425
427
428 return self._parent
429
430
432
433
434
435 if parent == self._parent:
436 return
437
438 if parent is not None and self._parent is not None:
439 raise ValueError, fullname(self) + ' already has a parent.'
440
441
442 if self.getApplication() is not None:
443 self.detach()
444
445
446 self._parent = parent
447
448
449 if self.getApplication() is not None:
450 self.attach()
451
452
454 """Gets the error message for this component.
455
456 @return: ErrorMessage containing the description of the error state
457 of the component or null, if the component contains no errors.
458 Extending classes should override this method if they support
459 other error message types such as validation errors or
460 buffering errors. The returned error message contains
461 information about all the errors.
462 """
463 return self._componentError
464
465
467 """Gets the component's error message.
468
469 @return: the component's error message.
470 """
471 return self._componentError
472
473
475 """Sets the component's error message. The message may contain
476 certain XML tags.
477
478 @param componentError:
479 the new C{ErrorMessage} of the component.
480 """
481 self._componentError = componentError
482 self.fireComponentErrorEvent()
483 self.requestRepaint()
484
485
487
488 return self._readOnly
489
490
495
496
498
499 if self._parent is None:
500 return None
501 else:
502 return self._parent.getWindow()
503
504
516
517
521
522
524 """Sets the focus for this component if the component is
525 L{IFocusable}.
526 """
527 if isinstance(self, IFocusable):
528 app = self.getApplication()
529 if app is not None:
530 self.getWindow().setFocusedComponent(self)
531 self._delayedFocus = False
532 else:
533 self._delayedFocus = True
534
535
537 """Gets the application object to which the component is attached.
538
539 The method will return C{None} if the component is not currently
540 attached to an application. This is often a problem in constructors
541 of regular components and in the initializers of custom composite
542 components. A standard workaround is to move the problematic
543 initialization to L{attach}, as described in the documentation
544 of the method.
545
546 B{This method is not meant to be overridden.}
547
548 @return: the parent application of the component or C{None}.
549 @see: L{attach}
550 """
551 if self._parent is None:
552 return None
553 else:
554 return self._parent.getApplication()
555
556
558 self._repaintRequestListenersNotified = False
559
560
561 - def paint(self, target):
562 """Paints the Paintable into a UIDL stream. This method creates the
563 UIDL sequence describing it and outputs it to the given UIDL stream.
564
565 It is called when the contents of the component should be painted in
566 response to the component first being shown or having been altered so
567 that its visual representation is changed.
568
569 B{Do not override this to paint your component.} Override
570 L{paintContent} instead.
571
572 @param target:
573 the target UIDL stream where the component should paint
574 itself to.
575 @raise PaintException:
576 if the paint operation failed.
577 """
578 tag = target.getTag(self)
579 if ((not target.startTag(self, tag))
580 or self._repaintRequestListenersNotified):
581
582
583
584
585 if self.isVisible():
586 from muntjac.terminal.gwt.server.component_size_validator \
587 import ComponentSizeValidator
588
589 if (self.getHeight() >= 0
590 and (self.getHeightUnits() != self.UNITS_PERCENTAGE
591 or ComponentSizeValidator.parentCanDefineHeight(self))):
592
593 target.addAttribute('height', '' + self.getCSSHeight())
594
595 if (self.getWidth() >= 0
596 and (self.getWidthUnits() != self.UNITS_PERCENTAGE
597 or ComponentSizeValidator.parentCanDefineWidth(self))):
598
599 target.addAttribute('width', '' + self.getCSSWidth())
600
601 if self._styles is not None and len(self._styles) > 0:
602 target.addAttribute('style', self.getStyle())
603
604 if self.isReadOnly():
605 target.addAttribute('readonly', True)
606
607 if self.isImmediate():
608 target.addAttribute('immediate', True)
609
610 if not self.isEnabled():
611 target.addAttribute('disabled', True)
612
613 if self.getCaption() is not None:
614 target.addAttribute('caption', self.getCaption())
615
616 if self.getIcon() is not None:
617 target.addAttribute('icon', self.getIcon())
618
619 if (self.getDescription() is not None
620 and len(self.getDescription()) > 0):
621 target.addAttribute('description', self.getDescription())
622
623 if self._eventIdentifiers is not None:
624 target.addAttribute('eventListeners',
625 list(self._eventIdentifiers))
626
627 self.paintContent(target)
628
629 error = self.getErrorMessage()
630 if error is not None:
631 error.paint(target)
632 else:
633 target.addAttribute('invisible', True)
634 else:
635
636 target.addAttribute('cached', True)
637 target.endTag(tag)
638
639 self._repaintRequestListenersNotified = False
640
641
653
654
666
667
668 - def paintContent(self, target):
669 """Paints any needed component-specific things to the given UIDL
670 stream. The more general L{paint} method handles
671 all general attributes common to all components, and it calls this
672 method to paint any component-specific attributes to the UIDL stream.
673
674 @param target:
675 the target UIDL stream where the component should paint
676 itself to
677 @raise PaintException:
678 if the paint operation failed.
679 """
680 pass
681
682
687
688
695
696
698 """Fires the repaint request event.
699 """
700
701 if not self._repaintRequestListenersNotified:
702
703 event = RepaintRequestEvent(self)
704
705 for listener in self._repaintRequestListeners:
706 if alreadyNotified is None:
707 alreadyNotified = list()
708
709 if listener not in alreadyNotified:
710 listener.repaintRequested(event)
711 alreadyNotified.append(listener)
712 self._repaintRequestListenersNotified = True
713
714 for callback, args in self._repaintRequestCallbacks.iteritems():
715 if alreadyNotified is None:
716 alreadyNotified = list()
717
718 if callback not in alreadyNotified:
719 callback(event, *args)
720 alreadyNotified.append(callback)
721 self._repaintRequestListenersNotified = True
722
723
724 parent = self.getParent()
725 if parent is not None:
726 parent.childRequestedRepaint(alreadyNotified)
727
728
739
740
741 - def addCallback(self, callback, eventType=None, *args):
742 if eventType is None:
743 eventType = callback._eventType
744
745 if issubclass(eventType, ComponentEvent):
746 self.registerCallback(ComponentEvent, callback, None, *args)
747
748 elif issubclass(eventType, RepaintRequestEvent):
749 self._repaintRequestCallbacks[callback] = args
750
751 else:
752 super(AbstractComponent, self).addCallback(callback,
753 eventType, *args)
754
755
757 """Registers a new listener with the specified activation method to
758 listen events generated by this component. If the activation method
759 does not have any arguments the event object will not be passed to
760 it when it's called.
761
762 This method additionally informs the event-api to route events with
763 the given eventIdentifier to the components handleEvent function
764 call.
765
766 For more information on the inheritable event mechanism see the
767 L{muntjac.event package documentation<muntjac.event>}.
768
769 @param args: tuple of the form
770 - (eventIdentifier, eventType, target, method)
771 1. the identifier of the event to listen for
772 2. the type of the listened event. Events of this type or
773 its subclasses activate the listener.
774 3. the object instance who owns the activation method.
775 4. the activation method.
776 - (eventType, target, method)
777 1. the type of the listened event. Events of this type or
778 its subclasses activate the listener.
779 2. the object instance who owns the activation method.
780 3. the activation method or the name of the activation method.
781 """
782 nargs = len(args)
783 if nargs == 3:
784 eventType, target, method = args
785
786 if self._eventRouter is None:
787 self._eventRouter = EventRouter()
788
789 self._eventRouter.addListener(eventType, target, method)
790 elif nargs == 4:
791 eventIdentifier, eventType, target, method = args
792
793 if self._eventRouter is None:
794 self._eventRouter = EventRouter()
795
796 if self._eventIdentifiers is None:
797 self._eventIdentifiers = set()
798
799 needRepaint = not self._eventRouter.hasListeners(eventType)
800
801 self._eventRouter.addListener(eventType, target, method)
802
803 if needRepaint:
804 self._eventIdentifiers.add(eventIdentifier)
805 self.requestRepaint()
806 else:
807 raise ValueError, 'invalid number of arguments'
808
809
811
812 if hasattr(callback, 'im_self'):
813 target = callback.im_self
814 elif hasattr(callback, 'func_name'):
815 target = None
816 else:
817 raise ValueError('invalid callback: %s' % callback)
818
819
820 if len(args) > 0:
821 arguments = (None,) + args
822 eventArgIdx = 0
823 else:
824 arguments = None
825 eventArgIdx = None
826
827
828 if self._eventRouter is None:
829 self._eventRouter = EventRouter()
830
831 if self._eventIdentifiers is None:
832 self._eventIdentifiers = set()
833
834 if eventId is not None:
835 needRepaint = not self._eventRouter.hasListeners(eventType)
836
837 self._eventRouter.addListener(eventType, target, callback,
838 arguments, eventArgIdx)
839
840 if (eventId is not None) and needRepaint:
841 self._eventIdentifiers.add(eventId)
842 self.requestRepaint()
843
844
855
856
858 if eventType is None:
859 eventType = callback._eventType
860
861 if eventType == ComponentEvent:
862 self.withdrawCallback(ComponentEvent, callback)
863
864 elif eventType == RepaintRequestEvent:
865 if callback in self._repaintRequestCallbacks:
866 del self._repaintRequestCallbacks[callback]
867 else:
868 super(AbstractComponent, self).removeCallback(callback, eventType)
869
870
872 """Removes all registered listeners matching the given parameters.
873 Since this method receives the event type and the listener object
874 as parameters, it will unregister all C{object}'s methods that are
875 registered to listen to events of type C{eventType} generated by
876 this component.
877
878 This method additionally informs the event-api to stop routing
879 events with the given eventIdentifier to the components handleEvent
880 function call.
881
882 For more information on the inheritable event mechanism see the
883 L{muntjac.event package documentation<muntjac.event>}.
884
885 @param args: tuple of the form
886 - (eventIdentifier, eventType, target)
887 1. the identifier of the event to stop listening for
888 2. the exact event type the C{object} listens to.
889 3. the target object that has registered to listen to events
890 of type C{eventType} with one or more methods.
891 - (eventType, target)
892 1. the exact event type the C{object} listens to.
893 2. the target object that has registered to listen to events
894 of type C{eventType} with one or more methods.
895 - (eventType, target, method)
896 1. the exact event type the C{object} listens to.
897 2. the target object that has registered to listen to events
898 of type C{eventType} with one or more methods.
899 3. the method or the name of the method owned by C{target}
900 that's registered to listen to events of type C{eventType}.
901 """
902 nargs = len(args)
903 if nargs == 2:
904 eventType, target = args
905
906 if self._eventRouter is not None:
907 self._eventRouter.removeListener(eventType, target)
908 elif nargs == 3:
909 if isinstance(args[0], basestring):
910 eventIdentifier, eventType, target = args
911
912 if self._eventRouter is not None:
913 self._eventRouter.removeListener(eventType, target)
914 if not self._eventRouter.hasListeners(eventType):
915 self._eventIdentifiers.remove(eventIdentifier)
916 self.requestRepaint()
917 else:
918 if isinstance(args[2], basestring):
919 eventType, target, methodName = args
920
921 if self._eventRouter is not None:
922 self._eventRouter.removeListener(eventType, target,
923 methodName)
924 else:
925 eventType, target, method = args
926
927 if self._eventRouter is not None:
928 self._eventRouter.removeListener(eventType, target,
929 method)
930 else:
931 raise ValueError, 'invalid number of arguments'
932
933
935 if self._eventRouter is not None:
936
937 if hasattr(callback, 'im_self'):
938 target = callback.im_self
939 elif hasattr(callback, 'func_name'):
940 target = None
941 else:
942 raise ValueError('invalid callback: %s' % callback)
943
944 self._eventRouter.removeListener(eventType, target, callback)
945
946 if (eventId is not None and
947 not self._eventRouter.hasListeners(eventType)):
948 self._eventIdentifiers.remove(eventId)
949 self.requestRepaint()
950
951
955
956
958 """Checks if the given L{Event} type is listened for this
959 component.
960
961 @param eventType:
962 the event type to be checked
963 @return: true if a listener is registered for the given event type
964 """
965 return (self._eventRouter is not None
966 and self._eventRouter.hasListeners(eventType))
967
968
970 """Returns all listeners that are registered for the given event
971 type or one of its subclasses.
972
973 @param eventType:
974 The type of event to return listeners for.
975 @return: A collection with all registered listeners. Empty if no
976 listeners are found.
977 """
978 if issubclass(eventType, RepaintRequestEvent):
979
980 return list(self._repaintRequestListeners)
981
982 if self._eventRouter is None:
983 return list()
984
985 return self._eventRouter.getListeners(eventType)
986
987
989 """Sends the event to all listeners.
990
991 @param event:
992 the Event to be sent to all listeners.
993 """
994 if self._eventRouter is not None:
995 self._eventRouter.fireEvent(event)
996
997
999 """Emits the component event. It is transmitted to all registered
1000 listeners interested in such events.
1001 """
1002 event = ComponentEvent(self)
1003 self.fireEvent(event)
1004
1005
1012
1013
1015 """Sets the data object, that can be used for any application
1016 specific data. The component does not use or modify this data.
1017
1018 @param data:
1019 the Application specific data.
1020 """
1021 self._applicationData = data
1022
1023
1025 """Gets the application specific data. See L{setData}.
1026
1027 @return: the Application specific data set with setData function.
1028 """
1029 return self._applicationData
1030
1031
1034
1035
1037 return self._heightUnit
1038
1039
1042
1043
1045 return self._widthUnit
1046
1047
1059
1060
1061
1064
1065
1069
1070
1074
1075
1087
1088
1089
1092
1093
1095 """Returns array with size in index 0 unit in index 1. Null or empty
1096 string will produce {-1,UNITS_PIXELS}.
1097 """
1098 values = [-1, self.UNITS_PIXELS]
1099 if s == None:
1100 return values
1101
1102 s = s.strip()
1103 if s == '':
1104 return values
1105
1106 match = self._sizePattern.match(s)
1107 if bool(match) == True:
1108 values[0] = float( match.group(1) )
1109 if values[0] < 0:
1110 values[0] = -1
1111 else:
1112 unit = match.group(3)
1113 if unit == None:
1114 values[1] = self.UNITS_PIXELS
1115 elif unit == "px":
1116 values[1] = self.UNITS_PIXELS
1117 elif unit == "%":
1118 values[1] = self.UNITS_PERCENTAGE
1119 elif unit == "em":
1120 values[1] = self.UNITS_EM
1121 elif unit == "ex":
1122 values[1] = self.UNITS_EX
1123 elif unit == "in":
1124 values[1] = self.UNITS_INCH
1125 elif unit == "cm":
1126 values[1] = self.UNITS_CM
1127 elif unit == "mm":
1128 values[1] = self.UNITS_MM
1129 elif unit == "pt":
1130 values[1] = self.UNITS_POINTS
1131 elif unit == "pc":
1132 values[1] = self.UNITS_PICAS
1133 else:
1134 raise ValueError, "Invalid size argument: " + s
1135
1136 return values
1137
1138
1140 """Gets the error handler for the component.
1141
1142 The error handler is dispatched whenever there is an error
1143 processing the data coming from the client.
1144 """
1145 return self.errorHandler
1146
1147
1149 """Sets the error handler for the component.
1150
1151 The error handler is dispatched whenever there is an error
1152 processing the data coming from the client.
1153
1154 If the error handler is not set, the application error handler
1155 is used to handle the exception.
1156
1157 @param errorHandler:
1158 AbstractField specific error handler
1159 """
1160 self.errorHandler = errorHandler
1161
1162
1164 """Handle the component error event.
1165
1166 @param error:
1167 Error event to handle
1168 @return: True if the error has been handled False, otherwise. If
1169 the error haven't been handled by this component, it will
1170 be handled in the application error handler.
1171 """
1172 if self.errorHandler != None:
1173 return self.errorHandler.handleComponentError(error)
1174
1175 return False
1176
1177
1179 """Handle the component error
1180 """
1181
1183 """@return: True if the error has been handled False, otherwise
1184 """
1185 pass
1186
1187
1190