Package elisa :: Package plugins :: Package bad :: Package poblenou_frontend :: Module tree_view
[hide private]
[frames] | no frames]

Source Code for Module elisa.plugins.bad.poblenou_frontend.tree_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   
 20   
 21  from elisa.core import plugin_registry, common 
 22   
 23  import pgm 
 24  from pgm.timing import implicit 
 25  from pgm.graph.group import Group 
 26  from pgm.graph.text import Text 
 27  from pgm.graph.image import Image 
 28   
 29  from poblenou_widgets.top_level_menu import * 
 30   
 31  from twisted.internet import reactor 
 32   
 33  from elisa.extern.translation import Translatable 
 34   
 35  BaseTreeView = plugin_registry.get_component_class('base:tree_view') 
 36   
37 -class TreeView(BaseTreeView):
38 39 supported_controllers = ('poblenou:tree_controller',) 40
41 - def __init__(self):
42 super(TreeView, self).__init__() 43 self.context_path = 'pigment:pigment_context' 44 45 # description widget 46 self._description = None 47 self._description_string = "" 48 # the descriptive string of the current selected item is displayed 49 # after a given time 50 self._description_delay = 0.100 51 self._description_delayed = None 52 53 # menu widget 54 self._menu = None 55 56 # arrow widget that indicates that an item contains more items 57 self._arrow = None 58 self._arrow_delay = 0.100 59 self._arrow_delayed = None 60 61 # invisible drawable used to capture mouse click and return to the 62 # previous menu level 63 self._previous_menu_zone = None 64 65 # invisible drawable used to capture mouse drag on the menu levels 66 self._drag_level_zone = None 67 self._drag_started = False 68 self._drag_start_position = None 69 self._drag_start_index = 0 70 71 # button giving an hint on how to go to the previous menu 72 self._back_button = None 73 74 # drawable for loading and empty directory user feedback 75 self._loading = None 76 77 # dictionary of drawables that will be reused many times thus avoiding 78 # reloading the pictures from disk; this cache is cleaned when self gets 79 # destroyed 80 self.icons_cache = {}
81
82 - def get_icon_drawable(self, icon_theme_name):
83 if self.icons_cache.has_key(icon_theme_name): 84 return self.icons_cache[icon_theme_name] 85 86 self.debug("drawable for icon %s not in cache; creating it" \ 87 % icon_theme_name) 88 89 img = pgm.Image() 90 image_path = self.frontend.theme.get_media(icon_theme_name) 91 img.set_from_file(image_path) 92 img.set_name(icon_theme_name) 93 self.icons_cache[icon_theme_name] = img 94 95 self.debug("drawables cache now containing %s drawables" \ 96 % len(self.icons_cache)) 97 98 # FIXME: Crashes if the Image is not in the canvas: see Pigment 99 # ticket #163 100 canvas = self.frontend.context.canvas 101 canvas.add(pgm.DRAWABLE_MIDDLE, img) 102 103 return img
104
105 - def frontend_changed(self, previous_frontend, new_frontend):
106 if new_frontend == None: 107 return 108 109 # FIXME: re-bind the widgets to the new canvas 110 canvas = self.frontend.context.canvas 111 self.root_group = Group(canvas, pgm.DRAWABLE_MIDDLE) 112 self.context_handle = self.root_group 113 114 self._create_menu() 115 self._create_description() 116 self._create_arrow() 117 self._create_previous_menu_zone() 118 self._create_drag_level_zone() 119 self._create_back_button() 120 self._create_loading() 121 122 # connects to the canvas_resized signal of the new context 123 if new_frontend != previous_frontend: 124 new_frontend.theme_changed.connect(self.theme_changed)
125
126 - def theme_changed(self, new_theme):
127 index = 0 128 for child_controller in self.controller: 129 icon, blurred, reflected = self._get_child_icons(child_controller) 130 self._menu.update_images(index, icon, blurred, reflected) 131 index += 1 132 133 # update the icon drawables cache 134 for icon_theme_name, drawable in self.icons_cache.iteritems(): 135 icon_path = self.frontend.theme.get_media(icon_theme_name) 136 drawable.set_from_file(icon_path) 137 drawable.set_name('icon_theme_name')
138
139 - def controller_changed(self, old_controller, new_controller):
140 if self._menu: 141 self._scale_menu() 142 super(TreeView, self).controller_changed(old_controller, new_controller)
143
144 - def _create_menu(self):
145 canvas = self.frontend.context.canvas 146 147 # FIXME: move these 2 to canvas_resized 148 size = canvas.width/3.0 149 menu_selected_size = (size, size) 150 padding = size/4.0 151 menu_selected_position = (padding, padding*3.0/4.0, 0.0) 152 menu_selected_position = ((canvas.width-size)/2.0, 0.0, 0.0) 153 154 self._menu = TopLevelMenu(canvas, pgm.DRAWABLE_MIDDLE, 155 width = canvas.width, 156 height = canvas.height, 157 font_family = "Nimbus Sans L", 158 selected_position = menu_selected_position, 159 selected_size = menu_selected_size, 160 selected_opacity = 20, 161 mode = CAROUSEL, 162 path_steps = 1, 163 duration = 500, 164 transformation = implicit.DECELERATE) 165 166 self._menu.bg_color = (0, 0, 0, 0) 167 self._menu.opacity = 255 168 self.root_group.add(self._menu) 169 170 self._animated_menu = implicit.AnimatedObject(self._menu) 171 self._animated_menu.mode = implicit.REPLACE 172 self._animated_menu.setup_next_animations(duration = 550, 173 transformation = implicit.DECELERATE) 174 self._menu.visible = True
175 176
177 - def _scale_menu(self):
178 # set canvas size dependent properties of menu (size, position) 179 canvas = self.frontend.context.canvas 180 self._menu.width = canvas.width 181 self._menu.height = canvas.height 182 183 self._unselected_menu_position = ((canvas.width-self._menu.width)/2.0, 184 (canvas.height-self._menu.height)/2.0, 185 -500.0) 186 self._selected_menu_position = ((canvas.width-self._menu.width)/2.0, 187 (canvas.height-self._menu.height)/2.0, 188 0.0) 189 190 self._animated_menu.stop_animations() 191 if self.controller.selected: 192 self._menu.position = self._selected_menu_position 193 else: 194 self._menu.position = self._unselected_menu_position 195 196 self._menu.layout() 197 self._menu.update()
198
199 - def _create_description(self):
200 # description text 201 self._description = Text() 202 self._description.label = "" 203 self._description.set_name('(empty)') 204 self._description.font_family = "Nimbus Sans L" 205 self._description.bg_color = (0, 0, 0, 0) 206 self._description.alignment = pgm.TEXT_ALIGN_CENTER 207 self._description.ellipsize = pgm.TEXT_ELLIPSIZE_END 208 self._description.opacity = 255 209 210 self._description.connect("clicked", self._description_clicked) 211 212 self._animated_description = implicit.AnimatedObject(self._description) 213 self._animated_description.mode = implicit.REPLACE 214 self._animated_description.setup_next_animations(duration=200, 215 transformation=implicit.DECELERATE) 216 217 self._scale_description() 218 self._description.visible = True 219 220 self.root_group.add(self._description)
221
222 - def _scale_description(self):
223 # set canvas size dependent properties of description (size, position, 224 # font_height) 225 canvas = self.frontend.context.canvas 226 self._description.font_height = canvas.height*0.11 227 self._description.size = (canvas.width, canvas.height*0.11) 228 self._description.x = 0.0 229 self._description.y = canvas.height * (1.0 - 0.035) - \ 230 self._description.height 231 self._description.z = 0.0
232
233 - def display_description(self, description):
234 # no need to do anything if the currently displayed description is the 235 # right one 236 if self._description.label == description: 237 return 238 239 # hide the currently displayed description 240 if self._description.opacity != 0: 241 self._animated_description.stop_animations() 242 self._description.opacity = 0 243 244 if isinstance(description, Translatable): 245 trans = common.application.translator 246 text = trans.translateTranslatable(description, 247 self.frontend.languages) 248 else: 249 text = description 250 251 self._description_string = text 252 253 # (re)schedule the display of description 254 if self._description_delayed != None and self._description_delayed.active(): 255 self._description_delayed.reset(self._description_delay) 256 else: 257 self._description_delayed = reactor.callLater(self._description_delay, self._show_description)
258
259 - def _show_description(self):
260 # show the loaded description 261 self._description_delayed = None 262 self._description.label = self._description_string 263 self._description.set_name(self._description_string) 264 self._animated_description.opacity = 255
265
266 - def _create_arrow(self):
267 # arrow indicating that more items are in the current selected item 268 self._arrow = Image() 269 self._arrow.bg_color = (0, 0, 0, 0) 270 self._arrow.layout = pgm.IMAGE_SCALED 271 self._arrow.alignment = pgm.IMAGE_CENTER 272 self._arrow.opacity = 0 273 274 self._arrow.connect("clicked", self._description_clicked) 275 276 arrow_path = self.frontend.theme.get_media("down_arrow_icon") 277 self._arrow.set_from_file(arrow_path) 278 self._arrow.set_name('down_arrow_icon') 279 280 self._animated_arrow = implicit.AnimatedObject(self._arrow) 281 self._animated_arrow.mode = implicit.REPLACE 282 self._animated_arrow.setup_next_animations(duration=200, 283 transformation=implicit.DECELERATE) 284 self._animated_arrow.setup_next_animations(attribute="y", 285 duration=350, 286 repeat_count=implicit.INFINITE, 287 repeat_behavior=implicit.REVERSE, 288 transformation=implicit.SMOOTH) 289 self._scale_arrow() 290 self._arrow.visible = True 291 self.root_group.add(self._arrow)
292
293 - def _scale_arrow(self):
294 canvas = self.frontend.context.canvas 295 self._arrow.font_height = canvas.height*0.11 296 # FIXME: height and y 297 self._arrow.size = (canvas.width, canvas.height*0.03) 298 self._arrow.x = (canvas.width - self._arrow.width)/2.0 299 self._arrow.y = canvas.height * (1.0 - 0.01) - self._arrow.height 300 self._arrow.z = 0.0
301
302 - def display_arrow(self, visible):
303 # hide the arrow 304 if self._arrow.opacity != 0: 305 self._animated_arrow.stop_animations() 306 self._arrow.opacity = 0 307 self._scale_arrow() 308 309 # (re)schedule the display of the arrow 310 if self._arrow_delayed != None and self._arrow_delayed.active(): 311 if visible: 312 self._arrow_delayed.reset(self._arrow_delay) 313 else: 314 self._arrow_delayed.cancel() 315 elif visible: 316 self._arrow_delayed = reactor.callLater(self._arrow_delay, self._show_arrow)
317
318 - def _show_arrow(self):
319 self._animated_arrow.opacity = 255 320 self._animated_arrow.y += 0.01
321 322
323 - def _create_back_button(self):
324 # button giving an hint on how to go to the previous menu 325 self._back_button = Image() 326 self._back_button.bg_color = (0, 0, 0, 0) 327 self._back_button.layout = pgm.IMAGE_SCALED 328 self._back_button.alignment = pgm.IMAGE_CENTER 329 self._back_button.opacity = 0 330 331 back_button_path = self.frontend.theme.get_media("back_button") 332 self._back_button.set_from_file(back_button_path) 333 self._back_button.set_name("back_button") 334 335 self._animated_back_button = implicit.AnimatedObject(self._back_button) 336 self._animated_back_button.mode = implicit.REPLACE 337 self._animated_back_button.setup_next_animations(duration=400, 338 transformation=implicit.SMOOTH) 339 340 self._scale_back_button() 341 self._back_button.visible = True 342 343 self.root_group.add(self._back_button)
344
345 - def _scale_back_button(self):
346 canvas = self.frontend.context.canvas 347 back_size = canvas.height*0.1 348 offset = canvas.height*0.02 349 self._back_button.size = (back_size, back_size) 350 self._back_button.x = canvas.width - self._back_button.width - offset 351 self._back_button.y = offset 352 self._back_button.z = 0.0
353
354 - def display_back_button(self, visible):
355 if visible: 356 self._animated_back_button.opacity = 255 357 else: 358 self._animated_back_button.opacity = 0
359
360 - def _create_loading(self):
361 self._loading = Image() 362 363 loading_icon = self.frontend.theme.get_media("loading_icon") 364 self._loading.set_from_file(loading_icon) 365 self._loading.set_name("loading_icon") 366 367 self._loading.bg_color = (0, 0, 0, 0) 368 self._loading.opacity = 1 369 self._loading.layout = pgm.IMAGE_SCALED 370 self._loading.alignment = pgm.IMAGE_CENTER 371 372 self._animated_loading = implicit.AnimatedObject(self._loading) 373 self._animated_loading.mode = implicit.REPLACE 374 self._animated_loading.setup_next_animations(attribute="opacity", 375 duration = 200) 376 params = {"duration": 350, \ 377 "repeat_count": implicit.INFINITE, \ 378 "repeat_behavior": implicit.REVERSE, \ 379 "transformation": implicit.SMOOTH} 380 self._animated_loading.setup_next_animations(attribute="width", 381 **params) 382 self._animated_loading.setup_next_animations(attribute="height", 383 **params) 384 self._animated_loading.setup_next_animations(attribute="x", 385 **params) 386 self._animated_loading.setup_next_animations(attribute="y", 387 **params) 388 389 self._scale_loading() 390 self._loading.visible = True 391 392 self.root_group.add(self._loading)
393
394 - def _scale_loading(self):
395 canvas = self.frontend.context.canvas 396 self._loading.width = canvas.width/6.0 397 self._loading.height = self._loading.width*3.0/4.0 398 self._loading.position = (canvas.width/2.0-self._loading.width/2.0, 399 canvas.height*(1.0-0.05)-self._loading.height, 400 0.0)
401
402 - def display_loading(self, value):
403 if value: 404 loading_icon = self.frontend.theme.get_media("loading_icon") 405 self._loading.set_from_file(loading_icon) 406 self._loading.set_name("loading_icon") 407 408 self._animated_loading.opacity = 255 409 delta = 0.05 410 self._animated_loading.x -= delta/2.0 411 self._animated_loading.y -= delta/2.0 412 self._animated_loading.width += delta 413 self._animated_loading.height += delta 414 else: 415 self._animated_loading.stop_animations() 416 self._animated_loading.opacity = 1 417 self._scale_loading()
418
419 - def display_empty(self, value):
420 if value: 421 empty_icon = self.frontend.theme.get_media("empty_icon") 422 self._loading.set_from_file(empty_icon) 423 self._loading.set_name("empty_icon") 424 self._animated_loading.stop_animations() 425 self._animated_loading.opacity = 255 426 else: 427 self._animated_loading.stop_animations() 428 self._animated_loading.opacity = 1 429 self._scale_loading()
430 431
433 # invisible drawable used to capture mouse click and return to the 434 # previous menu level 435 self._previous_menu_zone = Image() 436 self._previous_menu_zone.opacity = 0 437 self._previous_menu_zone.position = (0.0, 0.0, 0.0) 438 439 self._scale_previous_menu_zone() 440 self._previous_menu_zone.visible = True 441 442 canvas = self.frontend.context.canvas 443 canvas.add(pgm.DRAWABLE_FAR, self._previous_menu_zone) 444 445 self._previous_menu_zone.connect('clicked', 446 self._previous_menu_zone_clicked) 447 448 self._previous_menu_zone.connect('drag_begin', self._drag_level_zone_drag_begin) 449 self._previous_menu_zone.connect('drag_motion', self._drag_level_zone_drag_motion) 450 self._previous_menu_zone.connect('drag_end', self._drag_level_zone_drag_end)
451
453 canvas = self.frontend.context.canvas 454 self._previous_menu_zone.size = (canvas.width, canvas.height * 0.5)
455 456
457 - def _create_drag_level_zone(self):
458 # invisible drawable used to capture mouse drag on the menu levels 459 self._drag_level_zone = Image() 460 self._drag_level_zone.opacity = 0 461 462 self._scale_drag_level_zone() 463 self._drag_level_zone.visible = True 464 465 canvas = self.frontend.context.canvas 466 canvas.add(pgm.DRAWABLE_FAR, self._drag_level_zone) 467 468 self._drag_level_zone.connect('drag_begin', self._drag_level_zone_drag_begin) 469 self._drag_level_zone.connect('drag_motion', self._drag_level_zone_drag_motion) 470 self._drag_level_zone.connect('drag_end', self._drag_level_zone_drag_end)
471
472 - def _scale_drag_level_zone(self):
473 canvas = self.frontend.context.canvas 474 self._drag_level_zone.size = (canvas.width, canvas.height * 0.5) 475 self._drag_level_zone.x = 0.0 476 self._drag_level_zone.y = canvas.height - self._drag_level_zone.height 477 self._drag_level_zone.z = 0.0
478
479 - def _drag_level_zone_drag_begin(self, drawable, x, y, z, button, time):
480 481 if self.controller.selected_controller is not self.controller.backend.focused_controller: 482 return False 483 484 if button == pgm.BUTTON_LEFT: 485 self._drag_started = True 486 self._drag_start_position = (x, y) 487 level_controller = self.controller.selected_controller 488 self._drag_start_index = level_controller.current_index 489 return True
490
491 - def _drag_level_zone_drag_end(self, drawable, x, y, z, button, time):
492 if button == pgm.BUTTON_LEFT and self._drag_started: 493 self._drag_started = False 494 495 dx = self._drag_start_position[0] - x 496 dy = self._drag_start_position[1] - y 497 498 level_controller = self.controller.selected_controller 499 if self._drag_start_index == level_controller.current_index: 500 if dy > 0.05: 501 # go up one level if we are not currently at the root level 502 if self.controller.selected_controller != self.controller: 503 self.controller.selected_controller.exit_node() 504 return True 505 506 elif dy < -0.05: 507 # go down one level 508 self.controller.selected_controller.enter_node() 509 return True 510 511 return False
512
513 - def _drag_level_zone_drag_motion(self, drawable, x, y, z, button, time):
514 if self._drag_started == True: 515 dx = self._drag_start_position[0] - x 516 517 # drag the current level 518 level_controller = self.controller.selected_controller 519 520 if level_controller == self.controller: 521 # root menu 522 visible_items = 4 523 increment = int(dx/float(self._drag_level_zone.width)*visible_items) 524 level_controller.current_index = (self._drag_start_index + \ 525 increment) % len(level_controller) 526 else: 527 # other menu levels 528 visible_items = 11 529 increment = int(dx/float(self._drag_level_zone.width)*visible_items) 530 level_controller.current_index = self._drag_start_index+increment 531 532 return True
533
534 - def attribute_set(self, origin, key, old_value, new_value):
535 if key == 'current_index': 536 self.debug("new index for %s: %s" % (id(self), new_value)) 537 text = self[self.controller.current_index].controller.model.text 538 self.display_description(text) 539 self._menu.focused_item = new_value 540 541 elif key == 'selected': 542 self.debug("%s (un)selected: %s" % (id(self), new_value)) 543 if new_value: 544 # display the menu 545 if self._menu.is_selected(): 546 self._menu.select() 547 self._animated_menu.position = self._selected_menu_position 548 549 # empty the description 550 text = self[self.controller.current_index].controller.model.text 551 self.display_description(text) 552 self.display_arrow(False) 553 self.display_back_button(False) 554 555 # selected child 556 child_index = self.controller.current_index 557 if child_index >= 0 and child_index < len(self): 558 self[child_index].hide() 559 560 else: 561 # hide the menu 562 if not self._menu.is_selected(): 563 self._menu.select() 564 self._animated_menu.position = self._unselected_menu_position 565 566 if self.frontend.context.touchscreen: 567 self.display_back_button(True)
568
569 - def _get_child_icons(self, child_controller):
570 # retrieve the paths to the icon, its blurred version and its reflected 571 # version 572 icon = child_controller.model.theme_icon 573 icon_path = self.frontend.theme.get_media(icon) 574 575 blurred_icon = icon[0:icon.find('_')] + "_blur_icon" 576 blurred_icon_path = self.frontend.theme.get_media(blurred_icon) 577 578 reflected_icon = icon[0:icon.find('_')] + "_reflected_icon" 579 reflected_icon_path = self.frontend.theme.get_media(reflected_icon) 580 581 return (icon_path, blurred_icon_path, reflected_icon_path)
582
583 - def child_view_creating(self, view, controller, position):
584 super(TreeView, self).child_view_creating(view, controller, position) 585 586 # add a child view representation to the menu 587 icon, blurred, reflected = self._get_child_icons(controller) 588 if isinstance(controller.model.text, Translatable): 589 trans = common.application.translator 590 text = trans.translateTranslatable(controller.model.text, 591 self.frontend.languages) 592 else: 593 text = controller.model.text 594 self._menu.insert(position, icon, blurred, reflected, text) 595 596 # FIXME: hack to access drawables from the top menu; a better solution 597 # would imply an API addition to the top menu 598 for widget in self._menu._item_handles[position]: 599 widget.connect('clicked', self._entry_clicked, controller) 600 601 self._menu.layout() 602 self._menu.update()
603
604 - def _entry_clicked(self, drawable, x, y, z, button, time, controller):
605 self.debug("%s clicked" % controller.model.text) 606 607 if self.controller.selected_controller == self.controller: 608 # the root menu is currently selected 609 current_index = self.controller.current_index 610 clicked_index = self.controller.index(controller) 611 if current_index != clicked_index: 612 # rotate the menu to the clicked entry 613 self.controller.current_index = clicked_index 614 else: 615 # enter the clicked entry of the menu 616 self.controller.enter_node() 617 618 return True 619 620 return False
621
622 - def _previous_menu_zone_clicked(self, drawable, x, y, z, button, time):
623 self.debug("Previous menu level zone clicked") 624 625 # do not process the click if there was dragging 626 # happening 627 if self._drag_level_zone_drag_end(drawable, x, y, z, button, time): 628 return True 629 630 if self.controller.selected_controller != self.controller: 631 self.controller.selected_controller.exit_node() 632 return True 633 634 return False
635
636 - def _description_clicked(self, drawable, x, y, z, button, time):
637 if not self._animated_description.visible or self._animated_description.opacity == 0 or \ 638 not self.root_group.visible or self.root_group.opacity == 0 or \ 639 self._description_string =="": 640 return False 641 642 self.controller.root.selected_controller.activate_node() 643 644 return True
645