Package elisa :: Package base_components :: Module view
[hide private]
[frames] | no frames]

Source Code for Module elisa.base_components.view

  1  # -*- coding: utf-8 -*- 
  2  # Elisa - Home multimedia server 
  3  # Copyright (C) 2006-2008 Fluendo Embedded S.L. (www.fluendo.com). 
  4  # All rights reserved. 
  5  # 
  6  # This file is available under one of two license agreements. 
  7  # 
  8  # This file is licensed under the GPL version 3. 
  9  # See "LICENSE.GPL" in the root of this distribution including a special 
 10  # exception to use Elisa with Fluendo's plugins. 
 11  # 
 12  # The GPL part of Elisa is also available under a commercial licensing 
 13  # agreement from Fluendo. 
 14  # See "LICENSE.Elisa" in the root directory of this distribution package 
 15  # for details on that license. 
 16   
 17   
 18  __maintainer__ = 'Florian Boucault <florian@fluendo.com>' 
 19  __maintainer2__ = 'Lionel Martin <lionel@fluendo.com>' 
 20   
 21   
 22  from elisa.core.component import Component, ComponentError 
 23  from elisa.base_components.controller import Controller 
 24  from elisa.core.observers.observer import Observer 
 25  from elisa.core import common 
 26  from elisa.core.interface_controller import UndefinedMVCAssociation 
 27   
 28   
29 -class ControllerNotSupported(Exception):
30 pass
31
32 -class View(Component, Observer):
33 """ 34 A View is responsible for rendering a Model. It can be of various nature 35 such as graphics, audio or even network output. A View does not hold a 36 direct reference to the Model it renders but accesses it through 37 L{controller}. 38 It is notified of all the changes occuring to L{controller}. Part of these 39 changes can be treated automatically using the bindings system. 40 A View can be part of a hierarchy of Views thus potentially having a 41 reference to its L{parent} and to its children defined in L{bindings}. 42 A View belongs to its L{frontend} which encapsulates objects necessary for 43 a View to be rendered (rendering context, theme, etc.) 44 45 @cvar supported_controllers: list of controllers that are compatible with 46 the view identified by their path: 47 'plugin:controller' 48 @type supported_controllers: tuple of strings 49 50 @cvar bindings: associations between L{model}'s attribute and 51 Controller's attributes; if an attribute 52 contained in L{bindings}' keys is changed or 53 created in L{model}, it is replicated in all 54 the corresponding attributes of the 55 controller defined in L{bindings} values 56 @type bindings: dictionary: keys: strings; values: list of 57 strings or strings 58 @ivar controller: controller to which the view is connected in 59 order to access the model; setting it to an 60 incompatible controller will fail 61 @type controller: L{elisa.base_components.controller.Controller} 62 @ivar parent: parent view; None if the view is a root 63 @type parent: L{elisa.base_components.view.View} 64 @ivar frontend: frontend to which the view belongs 65 @type frontend: L{elisa.core.frontend.Frontend} 66 @ivar context_handle: DOCME 67 @type context_handle: any 68 """ 69 70 supported_controllers = ('base:controller',) 71 bindings = {} 72 73 context_path = None 74
75 - def __init__(self):
76 super(View, self).__init__() 77 self._controller = None 78 self.context_handle = None 79 self._frontend = None 80 self._parent_view = None 81 self._supported_controller_classes = ()
82 83
84 - def initialize(self):
85 super(View, self).initialize() 86 plugin_registry = common.application.plugin_registry 87 classes = [ plugin_registry.get_component_class(controller_path) 88 for controller_path in self.supported_controllers ] 89 self._supported_controller_classes = tuple(classes)
90
91 - def clean(self):
92 super(View, self).clean() 93 self.controller = None 94 self.parent = None 95 self.frontend = None
96
97 - def frontend__get(self):
98 return self._frontend
99
100 - def frontend__set(self, new_frontend):
101 previous_frontend, self._frontend = self._frontend, new_frontend 102 if previous_frontend != new_frontend: 103 self.frontend_changed(previous_frontend, new_frontend)
104
105 - def frontend_changed(self, previous_frontend, new_frontend):
106 """ 107 Called when L{frontend} is set to a new value. 108 109 Override if you wish to react to that change. Do not forget to call 110 the parent class method. 111 112 @param old_frontend: value of frontend before 113 @type old_frontend: L{elisa.core.frontend.Frontend} 114 @param new_frontend: value of frontend now 115 @type new_frontend: L{elisa.core.frontend.Frontend} 116 """ 117 pass
118
119 - def controller__get(self):
120 return self._controller
121
122 - def controller__set(self, controller):
123 if self._controller == controller: 124 return 125 126 if controller != None and \ 127 not isinstance(controller, self._supported_controller_classes): 128 msg = "%r does not support %r but supports %r" % \ 129 (self, controller, self.supported_controllers) 130 self.warning(msg) 131 raise ControllerNotSupported(msg) 132 133 old_controller = self._controller 134 self._controller = controller 135 136 # update bindings 137 for controller_attribute in self.bindings.keys(): 138 self._update_bound_attributes(controller_attribute) 139 140 # stop observing the old model and start observing the new one 141 if old_controller != None: 142 old_controller.remove_observer(self) 143 if controller != None: 144 controller.add_observer(self) 145 146 self.controller_changed(old_controller, controller)
147
148 - def controller_changed(self, old_controller, new_controller):
149 """ 150 Called when L{controller} is set to a new value. 151 152 Override if you wish to react to that change. Do not forget to call 153 the parent class method. 154 155 @param old_controller: value of controller before 156 @type old_controller: L{elisa.base_components.controller.Controller} 157 @param new_controller: value of controller now 158 @type new_controller: L{elisa.base_components.controller.Controller} 159 """ 160 pass
161
162 - def update(self):
163 """ 164 Refresh completely the rendering of the associated Controller. This should 165 be called when the Controller has changed. 166 """ 167 pass
168
169 - def parent__set(self, parent):
170 self._parent_view = parent 171 if parent is not None: 172 self.frontend = parent.frontend
173
174 - def parent__get(self):
175 # FIXME: This collapses with Group.parent__get when doing 176 # multiple inheritance 177 return self._parent_view
178
179 - def attribute_set(self, origin, key, old_value, new_value):
180 """ 181 Called when an attribute of the controller to which it is connected 182 changes. 183 184 @param key: attribute changed 185 @type key: string 186 @param old_value: value of the attribute before being set; None if 187 attribute was not existing 188 @type old_value: any 189 @param new_value: value of the attribute after being set 190 @type new_value: any 191 """ 192 # update bindings 193 if key in self.bindings.keys(): 194 self._update_bound_attributes(key)
195
196 - def _delete_bound_attributes(self, controller_attribute):
197 """ 198 Delete view's attributes bound to L{controller_attribute}. 199 """ 200 child_attributes = self.bindings[controller_attribute] 201 if isinstance(child_attributes, str): 202 child_attributes = [child_attributes] 203 204 for child_attribute in child_attributes: 205 try: 206 delattr(self, child_attribute) 207 except AttributeError: 208 pass
209
210 - def _update_bound_attributes(self, controller_attribute):
211 """ 212 Update (create or delete) view's attribute bound to 213 L{controller_attribute}. 214 """ 215 try: 216 controller_child_value = getattr(self._controller, 217 controller_attribute) 218 except AttributeError: 219 self._delete_bound_attributes(controller_attribute) 220 return 221 222 child_attributes = self.bindings[controller_attribute] 223 if isinstance(child_attributes, str): 224 child_attributes = [child_attributes] 225 226 if isinstance(controller_child_value, Controller): 227 for attribute in child_attributes: 228 self._update_bound_view(attribute, controller_child_value) 229 else: 230 for child_attribute in child_attributes: 231 setattr(self, child_attribute, controller_child_value)
232
233 - def _update_bound_view(self, attribute, controller):
234 """ 235 Update (create or delete) a child view stored as L{attribute} 236 which is connected to L{controller}. 237 """ 238 if hasattr(self, attribute): 239 child_view = getattr(self, attribute) 240 241 # reconnect the corresponding view 242 child_view.controller = controller 243 else: 244 try: 245 # create the corresponding view 246 path = self.frontend.get_view_path(controller.model.path) 247 child_view = self._create_child_view(attribute, path) 248 249 # connect the corresponding view 250 child_view.controller = controller 251 except UndefinedMVCAssociation, e: 252 self.warning(e)
253
254 - def _create_child_view(self, attribute, path):
255 """ 256 Create a View of type L{path} and make it a child of 257 self stored as an instance variable named after the content of 258 L{attribute}. 259 260 @param attribute: attribute of self where to store the newly 261 created view 262 @type attribute: string 263 @param path: path to the type of view which is to be 264 created; syntax: plugin:component 265 @type path: string 266 """ 267 # create the new child view 268 registry = common.application.plugin_registry 269 new_view = registry.create_component(path) 270 271 # associate the new child view 272 self._add_child_view(attribute, new_view) 273 274 return new_view
275
276 - def _add_child_view(self, attribute, view):
277 """ 278 Make L{view} a child of self stored as an instance variable named 279 after the content of L{attribute}. 280 281 @param attribute: attribute of self where to store the view 282 @type attribute: string 283 @param view: view to be made child 284 @type path: L{elisa.base_components.view.View} 285 """ 286 view.frontend = self.frontend 287 view.parent = self 288 setattr(self, attribute, view)
289