Merge lp:~samuel-buffet/entertainer/reactive_scrollarea into lp:entertainer

Proposed by Samuel Buffet
Status: Merged
Approved by: Matt Layman
Approved revision: 396
Merged at revision: not available
Proposed branch: lp:~samuel-buffet/entertainer/reactive_scrollarea
Merge into: lp:entertainer
Diff against target: None lines
To merge this branch: bzr merge lp:~samuel-buffet/entertainer/reactive_scrollarea
Reviewer Review Type Date Requested Status
Matt Layman Approve
Review via email: mp+9162@code.launchpad.net

Commit message

The ScrollArea widget is now reactive to pointer events.

To post a comment you must log in.
Revision history for this message
Samuel Buffet (samuel-buffet) wrote :
Download full text (26.6 KiB)

Matt,

Same story than others but with the ScrollArea widget.

The core has been refactored to be driven by a property called "offset".
Old friends are all gathered: "active", "on_top", "on_bottom", "_on_button_press_event", "MotionBuffer" etc.

Screens using this widget have been updated. The lyrics tab also (small fix included).

The test is updated.

Well this should be routine.

Note: I've merged this branch with "reactive_menus" which is not yet merged with trunk (you were right about the distance of the wheels :)) so the diff provided by lp shouldn't be trusted.

diff vs "reactive_menus":
=== modified file 'entertainerlib/frontend/gui/screens/feed_entry.py'
--- entertainerlib/frontend/gui/screens/feed_entry.py 2009-06-21 22:38:01 +0000
+++ entertainerlib/frontend/gui/screens/feed_entry.py 2009-06-25 18:56:04 +0000
@@ -71,8 +71,8 @@
             entry_text.set_use_markup(True)
             entry_text.set_size(0.5857, 0.5208)

- self.scroll_area = ScrollArea(0.5857, 0.4948, 0.2072, 0.3,
- entry_text, active=True)
+ self.scroll_area = ScrollArea(0.2072, 0.3, 0.5857, 0.4948,
+ entry_text)
             self.add(self.scroll_area)

         # Add the additional actions that are needed but not handled by default

=== modified file 'entertainerlib/frontend/gui/screens/movie.py'
--- entertainerlib/frontend/gui/screens/movie.py 2009-07-19 19:27:31 +0000
+++ entertainerlib/frontend/gui/screens/movie.py 2009-07-21 21:51:16 +0000
@@ -109,7 +109,7 @@
         plot.set_line_wrap_mode(pango.WRAP_WORD)
         plot.set_line_wrap(True)
         plot.width = 0.5124
- self.scroll_area = ScrollArea(0.5124, 0.3516, 0.33, 0.38, plot)
+ self.scroll_area = ScrollArea(0.33, 0.38, 0.5124, 0.3516, plot)
         self.add(self.scroll_area)

         # Actors
@@ -178,12 +178,12 @@
     def _handle_left(self):
         '''Handle UserEvent.NAVIGATE_LEFT.'''
         self.menu.active = True
- self.scroll_area.set_active(False)
+ self.scroll_area.active = False

     def _handle_right(self):
         '''Handle UserEvent.NAVIGATE_RIGHT.'''
         self.menu.active = False
- self.scroll_area.set_active(True)
+ self.scroll_area.active = True

     def _handle_select(self, event=None):
         '''Handle UserEvent.NAVIGATE_SELECT.'''

=== modified file 'entertainerlib/frontend/gui/screens/tv_episodes.py'
--- entertainerlib/frontend/gui/screens/tv_episodes.py 2009-07-19 19:27:31 +0000
+++ entertainerlib/frontend/gui/screens/tv_episodes.py 2009-07-22 19:50:12 +0000
@@ -44,6 +44,7 @@

         self.menu.connect("moved", self._update_episode_info)
         self.menu.connect("selected", self._handle_select)
+ self.menu.connect("activated", self._on_menu_activated)

     def _create_episode_menu(self):
         """Create a list of available seasons."""
@@ -108,7 +109,8 @@
             self.menu.selected_userdata.get_plot())
         plot.width = 0.4

- self.scroll_area = ScrollArea(0.4, 0.15, 0.05, 0.63, plot)
+ self.scroll_area = ScrollArea(0.05, 0.63, 0.4, 0.15, plot)
+ self.scroll_area.connect("activated", self._on_scroll_area_activated)
        ...

Revision history for this message
Matt Layman (mblayman) wrote :

Samuel,

Minimal comments on this one.

test_scrollarea.py:
* test_create is testing the label instance and not the scroll area.

scroll_area.py:
 * _set_active: Is there much benefit to testing for "if self._offset_max >= 0"? It seems like that would only occur when the content hasn't been added yet. I'm just trying to understand what situation this would occur in.
 * _update_motion_behaviour: comment bracket for 0, 1 is backwards. I also don't really understand the comment on the line above. I'm not sure how to read "index = 1 => index = 0".
 * _on_button_release_event: 200 is a very unclear magic number (Is it pixels?).

Thanks!

review: Approve
396. By Samuel Buffet

Fixes after Matt's comments.

Revision history for this message
Samuel Buffet (samuel-buffet) wrote :

Hi Matt,

> test_scrollarea.py:
> * test_create is testing the label instance and not the scroll area.

Yeah, a stupid mistake which is now fixed.

> scroll_area.py:
> * _set_active: Is there much benefit to testing for "if self._offset_max >=
> 0"? It seems like that would only occur when the content hasn't been added
> yet. I'm just trying to understand what situation this would occur in.

Yes there's a benefit because self._offset_max stands for :
self._offset_max = self.content.get_height() - self.area_height
In words it's the different between the height of the content and the height of the allowed on screen area. So if it is >=0 it means that the content is higher so there's a need to scroll something.

> * _update_motion_behaviour: comment bracket for 0, 1 is backwards. I also
> don't really understand the comment on the line above. I'm not sure how to
> read "index = 1 => index = 0".

Yep, was not clear enough. The comment as been reworked.

> * _on_button_release_event: 200 is a very unclear magic number (Is it
> pixels?).

Unfortunately yes it's a bit magic. It is in ms because speed is in pixel / ms. This magic gives pretty good result but definitely a much more physics would be necessary providing a speed continuity in the motion when the button is released and a deceleration factor.

Thanks for the review Matt.

Samuel-

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (338.4 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_arrowtexture
  ArrowTextureTest
    test_bounce ... IOError: Couldn't read configuration file.
                                                    [ERROR]
    test_create ... [ERROR]
entertainerlib.tests.test_base
  BaseTest
    test_create ... [ERROR]
    test_get_abs_x ... [ERROR]
                                                 [ERROR]
    test_get_abs_y ... [ERROR]
                                                 [ERROR]
    test_y_for_x ... [ERROR]
                                                   [ERROR]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [ERROR]
    testCreate ... [ERROR]
    testGetCfgDir ... [ERROR]
    testGetSlideshowStep ... [ERROR]
    testGetStageHeight ... [ERROR]
                                             [ERROR]
    testGetStageWidth ... [ERROR]
                                              [ERROR]
    testGetThemeName ... [ERROR]
    testSetStageHeight ... [ERROR]
                                             [ERROR]
    testSetStageWidth ... [ERROR]
                                              [ERROR]
    testStartAutoServer ... [FAIL]
                                            [ERROR]
    testTrayIconEnabled ... [ERROR]
    test_create_dir ... [ERROR]
    test_hidden_files_folders ... [ERROR]
    test_sanitize ... [ERROR]
                                                  [ERROR]
    test_taint ... [ERROR]
                                                     [ERROR]
    test_taint_in_memory ... [ERROR]
    test_write_content_value ... [ERROR]
                                       [ERROR]
    test_write_preference_value ... [ERROR]
                                    [ERROR]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [ERROR]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [ERROR]
    testUseExisting ... [ERROR]
entertainerlib.tests.test_eyecandytexture
 ...

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (338.4 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_arrowtexture
  ArrowTextureTest
    test_bounce ... IOError: Couldn't read configuration file.
                                                    [ERROR]
    test_create ... [ERROR]
entertainerlib.tests.test_base
  BaseTest
    test_create ... [ERROR]
    test_get_abs_x ... [ERROR]
                                                 [ERROR]
    test_get_abs_y ... [ERROR]
                                                 [ERROR]
    test_y_for_x ... [ERROR]
                                                   [ERROR]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [ERROR]
    testCreate ... [ERROR]
    testGetCfgDir ... [ERROR]
    testGetSlideshowStep ... [ERROR]
    testGetStageHeight ... [ERROR]
                                             [ERROR]
    testGetStageWidth ... [ERROR]
                                              [ERROR]
    testGetThemeName ... [ERROR]
    testSetStageHeight ... [ERROR]
                                             [ERROR]
    testSetStageWidth ... [ERROR]
                                              [ERROR]
    testStartAutoServer ... [FAIL]
                                            [ERROR]
    testTrayIconEnabled ... [ERROR]
    test_create_dir ... [ERROR]
    test_hidden_files_folders ... [ERROR]
    test_sanitize ... [ERROR]
                                                  [ERROR]
    test_taint ... [ERROR]
                                                     [ERROR]
    test_taint_in_memory ... [ERROR]
    test_write_content_value ... [ERROR]
                                       [ERROR]
    test_write_preference_value ... [ERROR]
                                    [ERROR]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [ERROR]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [ERROR]
    testUseExisting ... [ERROR]
entertainerlib.tests.test_eyecandytexture
 ...

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (338.4 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_arrowtexture
  ArrowTextureTest
    test_bounce ... IOError: Couldn't read configuration file.
                                                    [ERROR]
    test_create ... [ERROR]
entertainerlib.tests.test_base
  BaseTest
    test_create ... [ERROR]
    test_get_abs_x ... [ERROR]
                                                 [ERROR]
    test_get_abs_y ... [ERROR]
                                                 [ERROR]
    test_y_for_x ... [ERROR]
                                                   [ERROR]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [ERROR]
    testCreate ... [ERROR]
    testGetCfgDir ... [ERROR]
    testGetSlideshowStep ... [ERROR]
    testGetStageHeight ... [ERROR]
                                             [ERROR]
    testGetStageWidth ... [ERROR]
                                              [ERROR]
    testGetThemeName ... [ERROR]
    testSetStageHeight ... [ERROR]
                                             [ERROR]
    testSetStageWidth ... [ERROR]
                                              [ERROR]
    testStartAutoServer ... [FAIL]
                                            [ERROR]
    testTrayIconEnabled ... [ERROR]
    test_create_dir ... [ERROR]
    test_hidden_files_folders ... [ERROR]
    test_sanitize ... [ERROR]
                                                  [ERROR]
    test_taint ... [ERROR]
                                                     [ERROR]
    test_taint_in_memory ... [ERROR]
    test_write_content_value ... [ERROR]
                                       [ERROR]
    test_write_preference_value ... [ERROR]
                                    [ERROR]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [ERROR]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [ERROR]
    testUseExisting ... [ERROR]
entertainerlib.tests.test_eyecandytexture
 ...

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (339.6 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_arrowtexture
  ArrowTextureTest
    test_bounce ... IOError: Couldn't read configuration file.
                                                    [ERROR]
    test_create ... [ERROR]
entertainerlib.tests.test_base
  BaseTest
    test_create ... [ERROR]
    test_get_abs_x ... [ERROR]
                                                 [ERROR]
    test_get_abs_y ... [ERROR]
                                                 [ERROR]
    test_y_for_x ... [ERROR]
                                                   [ERROR]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [ERROR]
    testCreate ... [ERROR]
    testGetCfgDir ... [ERROR]
    testGetSlideshowStep ... [ERROR]
    testGetStageHeight ... [ERROR]
                                             [ERROR]
    testGetStageWidth ... [ERROR]
                                              [ERROR]
    testGetThemeName ... [ERROR]
    testSetStageHeight ... [ERROR]
                                             [ERROR]
    testSetStageWidth ... [ERROR]
                                              [ERROR]
    testStartAutoServer ... [FAIL]
                                            [ERROR]
    testTrayIconEnabled ... [ERROR]
    test_create_dir ... [ERROR]
    test_hidden_files_folders ... [ERROR]
    test_sanitize ... [ERROR]
                                                  [ERROR]
    test_taint ... [ERROR]
                                                     [ERROR]
    test_taint_in_memory ... [ERROR]
    test_write_content_value ... [ERROR]
                                       [ERROR]
    test_write_preference_value ... [ERROR]
                                    [ERROR]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [ERROR]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [ERROR]
    testUseExisting ... [ERROR]
entertainerlib.tests.test_eyecandytexture
 ...

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (39.1 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_arrowtexture
  ArrowTextureTest
    test_bounce ... [OK]
    test_create ... [OK]
entertainerlib.tests.test_base
  BaseTest
    test_create ... [OK]
    test_get_abs_x ... [OK]
    test_get_abs_y ... [OK]
    test_y_for_x ... [OK]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [OK]
    testCreate ... [OK]
    testGetCfgDir ... [OK]
    testGetSlideshowStep ... [OK]
    testGetStageHeight ... [OK]
    testGetStageWidth ... [OK]
    testGetThemeName ... [OK]
    testSetStageHeight ... [OK]
    testSetStageWidth ... [OK]
    testStartAutoServer ... [OK]
    testTrayIconEnabled ... [OK]
    test_create_dir ... [OK]
    test_hidden_files_folders ... [OK]
    test_sanitize ... [OK]
    test_taint ... [OK]
    test_taint_in_memory ... [OK]
    test_write_content_value ... [OK]
    test_write_preference_value ... [OK]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [OK]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [OK]
    testUseExisting ... [OK]
entertainerlib.tests.test_eyecandytexture
  EyeCandyTextureTest
    test_create ... [OK]
entertainerlib.tests.test_feedconfigtools
  FeedConfigToolsTest
    testAddFeedsToWidget001 ... [OK]
    testAddFeedsToWidget002 ... [OK]
    testAddFileFeedsToWidget001 ... [OK]
    testAddFileFeedsToWidget002 ... [OK]
entertainerlib.tests.test_feedentryparser
  FeedEntryParserTest
    testConvert ... [OK]
    testStripNonPangoTags ... ...

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (39.1 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_arrowtexture
  ArrowTextureTest
    test_bounce ... [OK]
    test_create ... [OK]
entertainerlib.tests.test_base
  BaseTest
    test_create ... [OK]
    test_get_abs_x ... [OK]
    test_get_abs_y ... [OK]
    test_y_for_x ... [OK]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [OK]
    testCreate ... [OK]
    testGetCfgDir ... [OK]
    testGetSlideshowStep ... [OK]
    testGetStageHeight ... [OK]
    testGetStageWidth ... [OK]
    testGetThemeName ... [OK]
    testSetStageHeight ... [OK]
    testSetStageWidth ... [OK]
    testStartAutoServer ... [OK]
    testTrayIconEnabled ... [OK]
    test_create_dir ... [OK]
    test_hidden_files_folders ... [OK]
    test_sanitize ... [OK]
    test_taint ... [OK]
    test_taint_in_memory ... [OK]
    test_write_content_value ... [OK]
    test_write_preference_value ... [OK]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [OK]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [OK]
    testUseExisting ... [OK]
entertainerlib.tests.test_eyecandytexture
  EyeCandyTextureTest
    test_create ... [OK]
entertainerlib.tests.test_feedconfigtools
  FeedConfigToolsTest
    testAddFeedsToWidget001 ... [OK]
    testAddFeedsToWidget002 ... [OK]
    testAddFileFeedsToWidget001 ... [OK]
    testAddFileFeedsToWidget002 ... [OK]
entertainerlib.tests.test_feedentryparser
  FeedEntryParserTest
    testConvert ... [OK]
    testStripNonPangoTags ... ...

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (157.0 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_arrowtexture
  ArrowTextureTest
    test_bounce ... /home/rockstar/.cache/entertainertree/entertainerlib/utils/theme.py:53: GtkWarning: gdk_pango_context_get_for_screen: assertion `GDK_IS_SCREEN (screen)' failed
/home/rockstar/.cache/entertainertree/entertainerlib/utils/theme.py:53: PangoWarning: pango_context_set_font_description: assertion `context != NULL' failed
/home/rockstar/.cache/entertainertree/entertainerlib/utils/theme.py:53: PangoWarning: pango_context_set_base_dir: assertion `context != NULL' failed
/home/rockstar/.cache/entertainertree/entertainerlib/utils/theme.py:53: PangoWarning: pango_context_set_language: assertion `context != NULL' failed
                                                    [ERROR]
    test_create ... [ERROR]
entertainerlib.tests.test_base
  BaseTest
    test_create ... [OK]
    test_get_abs_x ... [ERROR]
    test_get_abs_y ... [ERROR]
    test_y_for_x ... [ERROR]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [OK]
    testCreate ... [OK]
    testGetCfgDir ... [OK]
    testGetSlideshowStep ... [OK]
    testGetStageHeight ... [ERROR]
    testGetStageWidth ... [ERROR]
    testGetThemeName ... [OK]
    testSetStageHeight ... [ERROR]
    testSetStageWidth ... [ERROR]
    testStartAutoServer ... [OK]
    testTrayIconEnabled ... [OK]
    test_create_dir ... [OK]
    test_hidden_files_folders ... [OK]
    test_sanitize ... [ERROR]
    test_taint ... [OK]
    test_taint_in_memory ... [OK]
    test_write_content_value ... [OK]
    test_write_preference_value ... [OK]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [OK]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [OK]
    testUseExisting ... [OK]
entertainerlib.tests.test_eyecandytexture
  EyeCandyTextureTest
    test_create ... ...

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (157.0 KiB)

`which trial` entertainerlib.tests
entertainerlib.tests.test_arrowtexture
  ArrowTextureTest
    test_bounce ... /home/rockstar/.cache/entertainertree/entertainerlib/utils/theme.py:53: GtkWarning: gdk_pango_context_get_for_screen: assertion `GDK_IS_SCREEN (screen)' failed
/home/rockstar/.cache/entertainertree/entertainerlib/utils/theme.py:53: PangoWarning: pango_context_set_font_description: assertion `context != NULL' failed
/home/rockstar/.cache/entertainertree/entertainerlib/utils/theme.py:53: PangoWarning: pango_context_set_base_dir: assertion `context != NULL' failed
/home/rockstar/.cache/entertainertree/entertainerlib/utils/theme.py:53: PangoWarning: pango_context_set_language: assertion `context != NULL' failed
                                                    [ERROR]
    test_create ... [ERROR]
entertainerlib.tests.test_base
  BaseTest
    test_create ... [OK]
    test_get_abs_x ... [ERROR]
    test_get_abs_y ... [ERROR]
    test_y_for_x ... [ERROR]
entertainerlib.tests.test_configuration
  ConfigurationTest
    testBorg ... [OK]
    testCreate ... [OK]
    testGetCfgDir ... [OK]
    testGetSlideshowStep ... [OK]
    testGetStageHeight ... [ERROR]
    testGetStageWidth ... [ERROR]
    testGetThemeName ... [OK]
    testSetStageHeight ... [ERROR]
    testSetStageWidth ... [ERROR]
    testStartAutoServer ... [OK]
    testTrayIconEnabled ... [OK]
    test_create_dir ... [OK]
    test_hidden_files_folders ... [OK]
    test_sanitize ... [ERROR]
    test_taint ... [OK]
    test_taint_in_memory ... [OK]
    test_write_content_value ... [OK]
    test_write_preference_value ... [OK]
entertainerlib.tests.test_connection
  ConnectionServerTest
    testPortBinding ... [OK]
entertainerlib.tests.test_database
  DatabaseTest
    testCreate ... [OK]
    testUseExisting ... [OK]
entertainerlib.tests.test_eyecandytexture
  EyeCandyTextureTest
    test_create ... ...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'entertainerlib/frontend/gui/screens/album.py'
2--- entertainerlib/frontend/gui/screens/album.py 2009-05-10 07:43:27 +0000
3+++ entertainerlib/frontend/gui/screens/album.py 2009-07-14 10:46:47 +0000
4@@ -11,7 +11,6 @@
5 from entertainerlib.frontend.gui.widgets.label import Label
6 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
7 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
8-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
9
10 class Album(Screen):
11 '''Screen that allows user to browse and play tracks of the music album.'''
12@@ -28,21 +27,25 @@
13 self.track_menu = None
14
15 # Create and initialize screen items
16- self._create_track_menu()
17+ self.track_menu = self._create_track_menu()
18+ self.add(self.track_menu)
19 self._create_album_cover_texture()
20 self._create_album_info()
21
22 self.screen_title = Label(0.13, "screentitle", 0, 0.87, "")
23 self.screen_title.set_ellipsize(pango.ELLIPSIZE_END)
24 self.screen_title.width = 0.8
25- self.show_artist()
26 self.add(self.screen_title)
27
28 #List indicator
29- self.li = ListIndicator(0.74, 0.8, 0.2, 0.045, ListIndicator.VERTICAL)
30- self.li.set_maximum(self.track_menu.get_number_of_items())
31+ self.li = ListIndicator(0.74, 0.85, 0.2, 0.045, ListIndicator.VERTICAL)
32+ self.li.set_maximum(len(self.album.get_tracks()))
33 self.add(self.li)
34
35+ self.track_menu.active = True
36+ self.track_menu.connect('selected', self._on_menu_selected)
37+ self.track_menu.connect('moved', self._display_selected_track)
38+
39 def _create_album_cover_texture(self):
40 """
41 Create a texture that is displayed next to track list. This texture
42@@ -83,31 +86,14 @@
43 """
44 Create track menu. This menu contains list of all tracks on album.
45 """
46- self.track_menu = TextMenu(self.theme, self.config.show_effects())
47- self.track_menu.set_row_count(1)
48- self.track_menu.set_visible_column_count(7)
49- self.track_menu.set_item_size(self.get_abs_x(0.4393),
50- self.get_abs_y(0.0781))
51- self.track_menu.set_position(self.get_abs_x(0.4978),
52- self.get_abs_y(0.2344))
53+ menu = TextMenu(0.4978, 0.2344, 0.4393, 0.0781)
54
55- # Create menu items based on MusicLibrary
56 tracks = self.album.get_tracks()
57- for track in tracks:
58- track_length = str(track.get_length() / 60) + ":" + \
59- str(track.get_length() % 60).zfill(2)
60- item = TextMenuItem(0.4393, 0.0781, track.get_title(), track_length)
61- item.set_userdata(track)
62- self.track_menu.add_actor(item)
63-
64- self.track_menu.set_active(True)
65- self.add(self.track_menu)
66-
67- def show_artist(self):
68- # Screen Title (Displayed at the bottom left corner)
69- track = self.track_menu.get_current_menuitem().get_userdata()
70- self.screen_title.set_text(track.get_artist())
71- self.screen_title.show()
72+ tracks_list = [[track.get_title(), track.get_length_string(), track] \
73+ for track in tracks]
74+ menu.async_add(tracks_list)
75+
76+ return menu
77
78 def is_interested_in_play_action(self):
79 """
80@@ -121,27 +107,32 @@
81 Override function from Screen class. See Screen class for
82 better documentation.
83 """
84- track = self.track_menu.get_current_menuitem().get_userdata()
85+ track = self.track_menu.selected_userdata
86 self.media_player.set_media(track)
87 self.media_player.play()
88
89- def _move_menu(self, menu_direction):
90- '''Move the text menu in the provided direction.'''
91- self.track_menu.move(menu_direction)
92- self.li.set_current(self.track_menu.get_current_position() + 1)
93- self.show_artist()
94-
95 def _handle_up(self):
96 '''Handle UserEvent.NAVIGATE_UP.'''
97- self._move_menu(TextMenu.UP)
98+ self.track_menu.up()
99
100 def _handle_down(self):
101 '''Handle UserEvent.NAVIGATE_DOWN.'''
102- self._move_menu(TextMenu.DOWN)
103+ self.track_menu.down()
104
105- def _handle_select(self):
106+ def _handle_select(self, event=None):
107 '''Handle UserEvent.NAVIGATE_SELECT.'''
108- track = self.track_menu.get_current_menuitem().get_userdata()
109+ track = self.track_menu.selected_userdata
110 kwargs = { 'track' : track }
111 self.callback("audio_play", kwargs)
112
113+ def _on_menu_selected(self, actor=None):
114+ '''Handle a *select command* if an item was selected.'''
115+ self._handle_select()
116+
117+ def _display_selected_track(self, actor=None):
118+ '''Update of the list indicator and the screen's title'''
119+ self.li.set_current(self.track_menu.selected_index + 1)
120+ track = self.track_menu.selected_userdata
121+ self.screen_title.set_text(track.get_artist())
122+ self.screen_title.show()
123+
124
125=== modified file 'entertainerlib/frontend/gui/screens/disc.py'
126--- entertainerlib/frontend/gui/screens/disc.py 2009-05-10 07:43:27 +0000
127+++ entertainerlib/frontend/gui/screens/disc.py 2009-07-19 20:23:56 +0000
128@@ -18,7 +18,6 @@
129 LoadingAnimation)
130 from entertainerlib.frontend.gui.widgets.texture import Texture
131 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
132-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
133 from entertainerlib.frontend.medialibrary.playlist import Playlist
134 from entertainerlib.utils.albumart_downloader import AlbumArtDownloader
135
136@@ -69,9 +68,14 @@
137
138 self.playlist = Playlist(tracks)
139 self._create_album_info(title, artist, tracks, disc.get_length())
140- self._create_track_menu(tracks)
141+ self.track_menu = self._create_track_menu(tracks)
142+ self.add(self.track_menu)
143 self._create_album_cover_texture(artist, title)
144- self._create_list_indicator()
145+
146+ self.li = ListIndicator(0.75, 0.8, 0.2, 0.045,
147+ ListIndicator.VERTICAL)
148+ self.li.set_maximum(len(tracks))
149+ self.add(self.li)
150
151 art_file = os.path.join(self.config.ALBUM_ART_DIR,
152 artist + " - " + title + ".jpg")
153@@ -172,45 +176,30 @@
154 Create a track menu. This menu contains list of all tracks on album.
155 @param tracks: List of CompactDisc objects
156 """
157- self.track_menu = TextMenu(self.theme, self.config.show_effects())
158- self.track_menu.set_row_count(1)
159- self.track_menu.set_visible_column_count(7)
160- self.track_menu.set_item_size(self.get_abs_x(0.4393),
161- self.get_abs_y(0.0781))
162- self.track_menu.set_position(self.get_abs_x(0.4978),
163- self.get_abs_y(0.2344))
164-
165- for index, track in enumerate(tracks):
166- length = str(track.get_length() / 60) + ":" + \
167- str(track.get_length() % 60).zfill(2)
168- item = TextMenuItem(0.4393, 0.0781, track.get_title(), length)
169- item.set_userdata(index)
170- self.track_menu.add_actor(item)
171- self.track_menu.set_active(True)
172- self.add(self.track_menu)
173-
174- def _create_list_indicator(self):
175- '''Create list indicator for track list.'''
176- self.li = ListIndicator(0.75, 0.8, 0.2, 0.045, ListIndicator.VERTICAL)
177- self.li.set_maximum(self.track_menu.get_number_of_items())
178- self.add(self.li)
179-
180- def _move_menu(self, menu_direction):
181- '''Move the text menu in the provided direction.'''
182- self.track_menu.move(menu_direction)
183- self.li.set_current(self.track_menu.get_current_position() + 1)
184+ menu = TextMenu(0.4978, 0.2344, 0.4393, 0.0781)
185+ menu.visible_rows = 7
186+
187+ tracks_list = [[track.get_title(), track.get_length_string(), index] \
188+ for index, track in enumerate(tracks)]
189+ menu.async_add(tracks_list)
190+
191+ menu.active = True
192+ menu.connect('selected', self._handle_select)
193+ menu.connect('moved', self._display_selected_track)
194+
195+ return menu
196
197 def _handle_up(self):
198 '''Handle UserEvent.NAVIGATE_UP.'''
199- self._move_menu(TextMenu.UP)
200+ self.track_menu.up()
201
202 def _handle_down(self):
203 '''Handle UserEvent.NAVIGATE_DOWN.'''
204- self._move_menu(TextMenu.DOWN)
205+ self.track_menu.down()
206
207- def _handle_select(self):
208+ def _handle_select(self, event=None):
209 '''Handle UserEvent.NAVIGATE_SELECT.'''
210- track_index = self.track_menu.get_current_menuitem().get_userdata()
211+ track_index = self.track_menu.selected_userdata
212 self.playlist.set_current(track_index)
213 self.media_player.set_playlist(self.playlist)
214 self.media_player.play()
215@@ -220,3 +209,7 @@
216 if self.has_disc:
217 Screen.handle_user_event(self, event)
218
219+ def _display_selected_track(self, event=None):
220+ '''Update of the list indicator.'''
221+ self.li.set_current(self.track_menu.selected_index + 1)
222+
223
224=== modified file 'entertainerlib/frontend/gui/screens/feed.py'
225--- entertainerlib/frontend/gui/screens/feed.py 2009-06-25 19:06:28 +0000
226+++ entertainerlib/frontend/gui/screens/feed.py 2009-07-12 07:57:05 +0000
227@@ -8,7 +8,6 @@
228 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
229 from entertainerlib.frontend.gui.widgets.texture import Texture
230 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
231-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
232 from entertainerlib.utils.feed_utils import FeedEntryParser
233
234 class Feed(Screen):
235@@ -21,7 +20,8 @@
236 self.feed = feed
237 self.menu = None
238
239- self._create_entry_menu()
240+ self.menu = self._create_entry_menu()
241+ self.add(self.menu)
242
243 # Screen Title (Displayed at the bottom left corner)
244 screen_title = Label(0.13, "screentitle", 0, 0.87, _("Feed"))
245@@ -56,44 +56,47 @@
246
247 #List indicator
248 self.li = ListIndicator(0.57, 0.9, 0.2, 0.045, ListIndicator.VERTICAL)
249- self.li.set_maximum(self.menu.get_number_of_items())
250+ self.li.set_maximum(len(self.feed.entries))
251 self.add(self.li)
252
253+ self._display_selected_feed()
254+
255+ self.menu.connect('selected', self._on_menu_selected)
256+ self.menu.connect('moved', self._display_selected_feed)
257+
258 def _create_entry_menu(self):
259- """
260- Create Entries-menu. This menu contains list of entries.
261- """
262- self.menu = TextMenu(self.theme, self.config.show_effects())
263- self.menu.set_row_count(1)
264- self.menu.set_item_size(self.get_abs_x(0.5124), self.get_abs_y(0.0781))
265- self.menu.set_position(self.get_abs_x(0.2438), self.get_abs_y(0.2604))
266-
267- # Create menu items based on FeedLibrary
268- for entry in self.feed.entries:
269- item = TextMenuItem(0.5124, 0.0781,
270- FeedEntryParser().strip_tags(entry.title))
271- item.set_userdata(entry)
272- self.menu.add_actor(item)
273-
274- self.menu.set_active(True)
275- self.add(self.menu)
276-
277- def _move_menu(self, menu_direction):
278- '''Move the text menu in the given direction.'''
279- self.menu.move(menu_direction)
280- self.li.set_current(self.menu.get_current_position() + 1)
281+ """Create Entries-menu. This menu contains list of entries."""
282+ menu = TextMenu(0.2438, 0.2604, 0.5124, 0.0781)
283+
284+ parser = FeedEntryParser()
285+ feeds = self.feed.entries
286+ feeds_list = [[parser.strip_tags(feed.title), None, feed] \
287+ for feed in feeds]
288+ menu.async_add(feeds_list)
289+
290+ menu.active = True
291+
292+ return menu
293
294 def _handle_up(self):
295 '''Handle UserEvent.NAVIGATE_UP.'''
296- self._move_menu(TextMenu.UP)
297+ self.menu.up()
298
299 def _handle_down(self):
300 '''Handle UserEvent.NAVIGATE_DOWN.'''
301- self._move_menu(TextMenu.DOWN)
302+ self.menu.down()
303
304- def _handle_select(self):
305+ def _handle_select(self, event=None):
306 '''Handle UserEvent.NAVIGATE_SELECT.'''
307- entry = self.menu.get_current_menuitem().get_userdata()
308+ entry = self.menu.selected_userdata
309 kwargs = { 'feed' : self.feed, 'entry' : entry }
310 self.callback("entry", kwargs)
311
312+ def _on_menu_selected(self, event=None):
313+ '''Handle a *select command* if an item was selected.'''
314+ self._handle_select()
315+
316+ def _display_selected_feed(self, event=None):
317+ '''Update of the list indicator'''
318+ self.li.set_current(self.menu.selected_index + 1)
319+
320
321=== modified file 'entertainerlib/frontend/gui/screens/feed_entry.py'
322--- entertainerlib/frontend/gui/screens/feed_entry.py 2009-06-21 22:38:01 +0000
323+++ entertainerlib/frontend/gui/screens/feed_entry.py 2009-06-25 18:56:15 +0000
324@@ -71,8 +71,8 @@
325 entry_text.set_use_markup(True)
326 entry_text.set_size(0.5857, 0.5208)
327
328- self.scroll_area = ScrollArea(0.5857, 0.4948, 0.2072, 0.3,
329- entry_text, active=True)
330+ self.scroll_area = ScrollArea(0.2072, 0.3, 0.5857, 0.4948,
331+ entry_text)
332 self.add(self.scroll_area)
333
334 # Add the additional actions that are needed but not handled by default
335
336=== modified file 'entertainerlib/frontend/gui/screens/main.py'
337--- entertainerlib/frontend/gui/screens/main.py 2009-06-28 20:12:13 +0000
338+++ entertainerlib/frontend/gui/screens/main.py 2009-07-19 19:27:31 +0000
339@@ -9,7 +9,6 @@
340 from entertainerlib.frontend.gui.widgets.scroll_menu import ScrollMenu
341 from entertainerlib.frontend.gui.widgets.texture import Texture
342 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
343-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
344 from entertainerlib.utils.cd_utils import eject_cd
345 from entertainerlib.utils.feed_utils import FeedEntryParser
346
347@@ -47,23 +46,27 @@
348
349 self.menu = self._create_main_menu()
350 self.add(self.menu)
351- self.menu.connect('selected', self._on_menu_selected)
352+
353+ self._update_preview_area()
354+
355+ self.add(ClockLabel(0.13, "screentitle", 0, 0.87))
356+
357+ self.menu.connect('selected', self._handle_select)
358 self.menu.connect('moved', self._on_menu_moved)
359- self.menu.connect('activated', self._main_menu_activation)
360+ self.menu.connect('activated', self._on_menu_activated)
361+
362 self.menu.active = True
363
364- self.add(ClockLabel(0.13, "screentitle", 0, 0.87))
365-
366 def get_type(self):
367 """Return screen type."""
368 return Screen.NORMAL
369
370 def get_name(self):
371- """Return screen name (human readble)"""
372+ """Return screen name (human readble)."""
373 return "Main"
374
375 def _create_main_menu(self):
376- """Create main menu of the home screen"""
377+ """Create main menu of the home screen."""
378 menu = ScrollMenu(10, 60, 0.045, "menuitem_active")
379 menu.set_name("mainmenu")
380
381@@ -96,24 +99,18 @@
382 '''Create the RSS preview menu that will show feed highlights. An
383 uninitialized menu will be returned to prevent move errors if there
384 is nothing in the feed library.'''
385- menu = TextMenu(self.theme, self.config.show_effects())
386+ menu = TextMenu(0.035, 0.12, 0.549, 0.078)
387
388 if self.feed_library.is_empty() is False:
389- menu.set_row_count(1)
390- menu.set_position(self.get_abs_x(0.035), self.get_abs_y(0.12))
391- menu.set_item_size(self.get_abs_x(0.549), self.get_abs_y(0.078))
392-
393- # List of latest entries. pack = (Feed object, Entry object)
394+ parser = FeedEntryParser()
395 entries = self.feed_library.get_latest_entries(5)
396- for pack in entries:
397- text = pack[0].title + " - " + pack[1].title
398- item = TextMenuItem(0.549, 0.078,
399- FeedEntryParser().strip_tags(text), pack[1].date)
400- kwargs = { 'feed' : pack[0], 'entry' : pack[1] }
401- item.set_userdata(kwargs)
402- menu.add_actor(item)
403+ entries_list = [[parser.strip_tags(entry[0].title + " - " + \
404+ entry[1].title), entry[1].date, \
405+ { 'feed' : entry[0], 'entry' : entry[1] }] for entry in entries]
406+ menu.async_add(entries_list)
407
408- menu.set_active(False)
409+ menu.connect('selected', self._handle_select)
410+ menu.connect('activated', self._on_rss_menu_activated)
411
412 return menu
413
414@@ -198,11 +195,15 @@
415 # No headlines available in the library
416 info = Label(0.05, "title", 0.1, 0.35, _("No headlines available"))
417 preview.add(info)
418+ self.rss_preview_menu = None
419
420 return preview
421
422 def _update_preview_area(self):
423 '''Update the preview area to display the current menu item.'''
424+ if self.rss_preview_menu:
425+ rss_preview_menu_active = self.rss_preview_menu.active
426+
427 self.preview.remove_all()
428 item = self.menu.get_selected()
429
430@@ -214,10 +215,8 @@
431 self.preview.add(self._create_playing_preview())
432 elif item.get_name() == "rss":
433 self.preview.add(self._create_rss_preview())
434- # When we update the rss preview menu, we must set it active if
435- # main menu is not.
436- if not self.menu.active:
437- self.rss_preview_menu.set_active(True)
438+ if self.rss_preview_menu:
439+ self.rss_preview_menu.active = rss_preview_menu_active
440 else:
441 update = False
442
443@@ -225,7 +224,7 @@
444 if update:
445 fade_in = clutter.Timeline(20, 60)
446 alpha_in = clutter.Alpha(fade_in, clutter.smoothstep_inc_func)
447- self.behaviour = clutter.BehaviourOpacity(0x00, 0xff, alpha_in)
448+ self.behaviour = clutter.BehaviourOpacity(0, 255, alpha_in)
449 self.behaviour.apply(self.preview)
450 fade_in.start()
451
452@@ -257,25 +256,25 @@
453 if self.menu.active:
454 self.menu.scroll_up()
455 else:
456- self.rss_preview_menu.move(TextMenu.UP)
457+ self.rss_preview_menu.up()
458
459 def _handle_down(self):
460 '''Handle UserEvent.NAVIGATE_DOWN.'''
461 if self.menu.active:
462 self.menu.scroll_down()
463 else:
464- self.rss_preview_menu.move(TextMenu.DOWN)
465+ self.rss_preview_menu.down()
466
467 def _handle_left(self):
468 '''Handle UserEvent.NAVIGATE_LEFT.'''
469 if self._can_move_horizontally():
470- self._preview_activation()
471+ self.rss_preview_menu.active = True
472
473 def _handle_right(self):
474 '''Handle UserEvent.NAVIGATE_RIGHT.'''
475 self.menu.active = True
476
477- def _handle_select(self):
478+ def _handle_select(self, event=None):
479 '''Handle UserEvent.NAVIGATE_SELECT.'''
480 item = self.menu.get_selected()
481
482@@ -297,27 +296,20 @@
483 if self.menu.active:
484 self.callback("rss")
485 else:
486- menu_item = self.rss_preview_menu.get_current_menuitem()
487- kwargs = menu_item.get_userdata()
488+ menu_item = self.rss_preview_menu.selected_item
489+ kwargs = menu_item.userdata
490 self.callback("entry", kwargs)
491
492- def _on_menu_moved(self, actor):
493+ def _on_menu_moved(self, event):
494 '''Update preview area when selected item changed on the menu.'''
495 self._update_preview_area()
496
497- def _on_menu_selected(self, actor):
498- '''Handle a *select command* if an item was selected.'''
499- self._handle_select()
500-
501- def _main_menu_activation(self, actor=None):
502+ def _on_menu_activated(self, event=None):
503 '''Handle the main menu activation.'''
504- self.menu.active = True
505- if self.rss_preview_menu:
506- self.rss_preview_menu.set_active(False)
507+ if self.rss_preview_menu is not None:
508+ self.rss_preview_menu.active = False
509
510- def _preview_activation(self, actor=None):
511- '''Handle the preview activation.'''
512+ def _on_rss_menu_activated(self, event=None):
513+ '''Handle the rss menu activation.'''
514 self.menu.active = False
515- if self.rss_preview_menu:
516- self.rss_preview_menu.set_active(True)
517
518
519=== modified file 'entertainerlib/frontend/gui/screens/movie.py'
520--- entertainerlib/frontend/gui/screens/movie.py 2009-06-05 05:10:21 +0000
521+++ entertainerlib/frontend/gui/screens/movie.py 2009-07-21 22:22:53 +0000
522@@ -13,7 +13,6 @@
523 from entertainerlib.frontend.gui.widgets.scroll_area import ScrollArea
524 from entertainerlib.frontend.gui.widgets.texture import Texture
525 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
526-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
527
528 class Movie(Screen):
529 '''Screen contains information of one movie.'''
530@@ -110,7 +109,7 @@
531 plot.set_line_wrap_mode(pango.WRAP_WORD)
532 plot.set_line_wrap(True)
533 plot.width = 0.5124
534- self.scroll_area = ScrollArea(0.5124, 0.3516, 0.33, 0.38, plot)
535+ self.scroll_area = ScrollArea(0.33, 0.38, 0.5124, 0.3516, plot)
536 self.add(self.scroll_area)
537
538 # Actors
539@@ -156,47 +155,40 @@
540 """
541 Create menu of the screen. This displayed the left side of the screen.
542 """
543- self.menu = TextMenu(self.theme, self.config.show_effects())
544- self.menu.set_row_count(1)
545- #self.track_menu.set_visible_column_count(10)
546- self.menu.set_item_size(self.get_abs_x(0.2196), self.get_abs_y(0.0781))
547- self.menu.set_position(self.get_abs_x(0.07), self.get_abs_y(0.1))
548-
549- item1 = TextMenuItem(0.2196, 0.0781, _("Watch"))
550- item1.set_userdata("watch")
551- self.menu.add_actor(item1)
552-
553- self.menu.set_active(True)
554+ self.menu = TextMenu(0.07, 0.1, 0.2196, 0.0781)
555+ self.menu.add_item(_("Watch"), None, "watch")
556+ self.menu.active = True
557+
558 self.add(self.menu)
559
560 def _handle_up(self):
561 '''Handle UserEvent.NAVIGATE_UP.'''
562- if self.menu.is_active():
563- self.menu.move(self.menu.UP)
564+ if self.menu.active:
565+ self.menu.up()
566 else:
567 self.scroll_area.scroll_up()
568
569 def _handle_down(self):
570 '''Handle UserEvent.NAVIGATE_DOWN.'''
571- if self.menu.is_active():
572- self.menu.move(self.menu.DOWN)
573+ if self.menu.active:
574+ self.menu.down()
575 else:
576 self.scroll_area.scroll_down()
577
578 def _handle_left(self):
579 '''Handle UserEvent.NAVIGATE_LEFT.'''
580- self.menu.set_active(True)
581- self.scroll_area.set_active(False)
582+ self.menu.active = True
583+ self.scroll_area.active = False
584
585 def _handle_right(self):
586 '''Handle UserEvent.NAVIGATE_RIGHT.'''
587- self.menu.set_active(False)
588- self.scroll_area.set_active(True)
589+ self.menu.active = False
590+ self.scroll_area.active = True
591
592- def _handle_select(self):
593+ def _handle_select(self, event=None):
594 '''Handle UserEvent.NAVIGATE_SELECT.'''
595- if self.menu.is_active():
596- item = self.menu.get_current_menuitem().get_userdata()
597+ if self.menu.active:
598+ item = self.menu.selected_userdata
599 if item == "watch":
600 self.media_player.set_media(self.movie)
601 self.media_player.play()
602
603=== modified file 'entertainerlib/frontend/gui/screens/photo.py'
604--- entertainerlib/frontend/gui/screens/photo.py 2009-05-10 06:24:43 +0000
605+++ entertainerlib/frontend/gui/screens/photo.py 2009-05-21 16:12:09 +0000
606@@ -182,7 +182,7 @@
607 else:
608 self.texture.move_by(-self.MOVE_SIZE, 0)
609
610- def _handle_select(self):
611+ def _handle_select(self, event=None):
612 '''Handle UserEvent.NAVIGATE_SELECT.'''
613 # Zoom image. If we zoom then we stop sliding photos.
614 self.stop_slideshow()
615
616=== modified file 'entertainerlib/frontend/gui/screens/photo_albums.py'
617--- entertainerlib/frontend/gui/screens/photo_albums.py 2009-05-10 07:43:27 +0000
618+++ entertainerlib/frontend/gui/screens/photo_albums.py 2009-07-19 19:27:31 +0000
619@@ -13,7 +13,6 @@
620 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
621 from entertainerlib.frontend.gui.widgets.texture import Texture
622 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
623-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
624
625 class PhotoAlbums(Screen):
626 '''Screen contains a list of photo albums and album previews.'''
627@@ -46,10 +45,16 @@
628 self.in_opacity = None
629 self.out_opacity = None
630 self.preview_textures = None
631- self._create_album_menu()
632+ self.menu = self._create_album_menu()
633+ self.add(self.menu)
634 self.li = None
635 self._create_list_indicator()
636
637+ self._update_album_preview(self.menu.selected_userdata)
638+
639+ self.menu.connect('selected', self._handle_select)
640+ self.menu.connect('moved', self._display_selected_album)
641+
642 def _create_no_photos_information(self):
643 """
644 Create textures and labels for information screen. This is displayed
645@@ -80,28 +85,17 @@
646 Create ImageAlbum-menu. This menu contains list of albums. It also
647 displays number of photographs per album.
648 """
649- self.menu = TextMenu(self.theme, self.config.show_effects())
650- self.menu.set_row_count(1)
651- self.menu.set_visible_column_count(7)
652- self.menu.set_item_size(self.get_abs_x(0.4393), self.get_abs_y(0.0781))
653- self.menu.set_position(self.get_abs_x(0.5271), self.get_abs_y(0.3385))
654+ menu = TextMenu(0.5271, 0.3385, 0.4393, 0.0781)
655+ menu.visible_rows = 7
656
657- # Create menu items based on ImageLibrary
658 albums = self.image_library.get_albums()
659- for album in albums:
660- nro_of_photos = album.get_number_of_images()
661- if nro_of_photos == 0: # Skip empty albums
662- continue
663- else:
664- nro_of_photos = str(nro_of_photos)
665- item = TextMenuItem(0.4393, 0.0781, album.get_title(),
666- nro_of_photos)
667- item.set_userdata(album)
668- self.menu.add_actor(item)
669-
670- self.add(self.menu)
671- self._update_album_preview(
672- self.menu.get_current_menuitem().get_userdata())
673+ albums_list = [[album.get_title(), str(album.get_number_of_images()),
674+ album] for album in albums if album.get_number_of_images() != 0]
675+ menu.async_add(albums_list)
676+
677+ menu.active = True
678+
679+ return menu
680
681 def _create_album_preview(self, album):
682 """
683@@ -246,30 +240,23 @@
684 def _create_list_indicator(self):
685 '''Create list indicator for albums list.'''
686 self.li = ListIndicator(0.77, 0.9, 0.2, 0.045, ListIndicator.VERTICAL)
687- self.li.set_maximum(self.menu.get_number_of_items())
688+ albums = self.image_library.get_albums()
689+ albums_list = [[album.get_title(), str(album.get_number_of_images()),
690+ album] for album in albums if album.get_number_of_images() != 0]
691+ self.li.set_maximum(len(albums_list))
692 self.add(self.li)
693
694- def _move_menu(self, menu_direction):
695- '''Move the menu in the given direction.'''
696- self.menu.move(menu_direction)
697- self.li.set_current(self.menu.get_current_position() + 1)
698- # 500ms timeout before preview is updated (fast scrolling)
699- if self.timeout is not None:
700- gobject.source_remove(self.timeout)
701- self.timeout = gobject.timeout_add(500, self._update_album_preview,
702- self.menu.get_current_menuitem().get_userdata())
703-
704 def _handle_up(self):
705 '''Handle UserEvent.NAVIGATE_UP.'''
706- self._move_menu(TextMenu.UP)
707+ self.menu.up()
708
709 def _handle_down(self):
710 '''Handle UserEvent.NAVIGATE_DOWN.'''
711- self._move_menu(TextMenu.DOWN)
712+ self.menu.down()
713
714- def _handle_select(self):
715+ def _handle_select(self, event=None):
716 '''Handle UserEvent.NAVIGATE_SELECT.'''
717- album = self.menu.get_current_menuitem().get_userdata()
718+ album = self.menu.selected_userdata
719 kwargs = { 'title' : album.get_title(), 'images' : album.get_images() }
720 self.callback("photographs", kwargs)
721
722@@ -280,3 +267,12 @@
723 else:
724 Screen.handle_user_event(self, event)
725
726+ def _display_selected_album(self, event=None):
727+ '''Update of the list indicator'''
728+ self.li.set_current(self.menu.selected_index + 1)
729+ # 500ms timeout before preview is updated (fast scrolling)
730+ if self.timeout is not None:
731+ gobject.source_remove(self.timeout)
732+ self.timeout = gobject.timeout_add(500, self._update_album_preview,
733+ self.menu.selected_userdata)
734+
735
736=== modified file 'entertainerlib/frontend/gui/screens/photographs.py'
737--- entertainerlib/frontend/gui/screens/photographs.py 2009-05-10 07:43:27 +0000
738+++ entertainerlib/frontend/gui/screens/photographs.py 2009-07-14 11:06:21 +0000
739@@ -2,13 +2,11 @@
740 '''Photographs - Screen contains a list/grid of photographs'''
741
742 import pango
743-import gobject
744
745 from entertainerlib.frontend.gui.screens.screen import Screen
746 from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
747 from entertainerlib.frontend.gui.widgets.label import Label
748 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
749-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
750 from entertainerlib.frontend.gui.widgets.loading_animation import (
751 LoadingAnimation)
752 from entertainerlib.frontend.gui.widgets.texture import Texture
753@@ -19,7 +17,6 @@
754 def __init__(self, move_to_new_screen_callback, title, images):
755 Screen.__init__(self, 'Photographs', move_to_new_screen_callback)
756
757- self.theme = self.config.theme
758 self.images = images
759
760 # Screen Title (Displayed at the bottom left corner)
761@@ -46,54 +43,32 @@
762 self._create_list_indicator()
763
764 # Create photomenu
765- self.menu = ImageMenu(self.theme)
766- self.menu.set_visible_column_count(6)
767- self.menu.set_position(self.get_abs_x(0.0586), self.get_abs_y(0.1823))
768-
769- if self.config.show_effects():
770- self.menu.set_animate(True)
771-
772+ self.menu = ImageMenu(0.03, 0.08, 0.12, self.y_for_x(0.12))
773+ self.menu.items_per_col = 3
774+ self.menu.visible_rows = 3
775+ self.menu.visible_cols = 8
776+ self.menu.active = True
777 self.add(self.menu)
778
779 photos = self.images
780- self.behaviour = None
781- gobject.timeout_add(25, self._create_photo_menuitems, photos)
782-
783- self._update_image_info(0)
784-
785- def _create_photo_menuitems(self, photographs):
786- """
787- This function is called as async callback. It is also recursive
788- function. This allows us load menu items in the background without
789- blocking the user interface.
790- """
791- # Check if we should stop recursive calls (menu is complete)
792- if len(photographs) > 0:
793- photo = photographs[0]
794- texture = Texture(photo.get_thumbnail_url())
795- item = ImageMenuItem(0.1464, 0.75, texture)
796- item.set_userdata(photo)
797- self.menu.add_actor(item)
798- # Recursive call, remove first artist from the list
799- gobject.timeout_add(15, self._create_photo_menuitems,
800- photographs[1:])
801- return False
802- else:
803- self.throbber.hide()
804- return False
805-
806- def _update_image_info(self, number):
807- """
808- Update image information box.
809- @param number: Number of image. This is index of the self.__images
810- """
811- image = self.images[number]
812+ photos_list = [[Texture(photo.get_thumbnail_url()), photo] \
813+ for photo in photos]
814+ self.menu.async_add(photos_list)
815+
816+ self.menu.connect("selected", self._handle_select)
817+ self.menu.connect('moved', self._update_image_info)
818+ self.menu.connect("filled", self._on_menu_filled)
819+
820+ def _update_image_info(self, event=None):
821+ """Update image information box."""
822+ image = self.images[self.menu.selected_index]
823 name = image.get_title()
824 desc = image.get_description()
825 self.image_title.set_text(name)
826 self.image_title.set_size(0.366, 0.04167)
827 self.image_desc.set_text(desc)
828 self.image_desc.set_size(0.366, 0.0911)
829+ self.li.set_current(self.menu.selected_index + 1)
830
831 def _create_list_indicator(self):
832 '''Create list indicator for albums list.'''
833@@ -102,31 +77,29 @@
834 self.li.set_maximum(len(self.images))
835 self.add(self.li)
836
837- def _move_menu(self, menu_direction):
838- '''Move the menu in the given direction.'''
839- self.menu.move(menu_direction)
840- self._update_image_info(self.menu.get_current_position())
841- self.li.set_current(self.menu.get_current_position() + 1)
842-
843 def _handle_up(self):
844 '''Handle UserEvent.NAVIGATE_UP.'''
845- self._move_menu(ImageMenu.UP)
846+ self.menu.up()
847
848 def _handle_down(self):
849 '''Handle UserEvent.NAVIGATE_DOWN.'''
850- self._move_menu(ImageMenu.DOWN)
851+ self.menu.down()
852
853 def _handle_left(self):
854 '''Handle UserEvent.NAVIGATE_LEFT.'''
855- self._move_menu(ImageMenu.LEFT)
856+ self.menu.left()
857
858 def _handle_right(self):
859 '''Handle UserEvent.NAVIGATE_RIGHT.'''
860- self._move_menu(ImageMenu.RIGHT)
861+ self.menu.right()
862
863- def _handle_select(self):
864+ def _handle_select(self, event=None):
865 '''Handle UserEvent.NAVIGATE_SELECT.'''
866- index = self.menu.get_current_position()
867+ index = self.menu.selected_index
868 kwargs = {'current_photo_index' : index, 'images' : self.images}
869 self.callback("photo", kwargs)
870
871+ def _on_menu_filled(self, event=None):
872+ '''Handles filled event.'''
873+ self.throbber.hide()
874+
875
876=== modified file 'entertainerlib/frontend/gui/screens/question.py'
877--- entertainerlib/frontend/gui/screens/question.py 2009-06-02 01:16:03 +0000
878+++ entertainerlib/frontend/gui/screens/question.py 2009-06-27 09:03:33 +0000
879@@ -4,7 +4,6 @@
880 from entertainerlib.frontend.gui.screens.screen import Screen
881 from entertainerlib.frontend.gui.widgets.label import Label
882 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
883-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
884
885 class Question(Screen):
886 '''Screen is displayed when the application needs to ask a close ended
887@@ -32,18 +31,14 @@
888
889 def display_answers(self):
890 '''Display a menu with answers on the screen.'''
891- self.menu = TextMenu(self.config.theme, self.config.show_effects())
892+ self.menu = TextMenu(0.095, 0.5, 0.4393, 0.0781)
893 self.menu.set_name("questionmenu")
894- self.menu.set_row_count(1)
895- self.menu.set_visible_column_count(7)
896- self.menu.set_item_size(self.get_abs_x(0.4393), self.get_abs_y(0.0781))
897- self.menu.set_position(self.get_abs_x(0.095), self.get_abs_y(0.5))
898+ self.menu.connect('selected', self._handle_select)
899
900 for answer in self.answers:
901- item = TextMenuItem(0.4173, 0.0781, str(answer))
902- item.set_userdata(str(answer))
903- self.menu.add_actor(item)
904+ self.menu.add_item(str(answer), None, str(answer))
905
906+ self.menu.active = True
907 self.add(self.menu)
908
909 def question_callback(self, selected_answer):
910@@ -63,20 +58,16 @@
911 '''Go back to previous screen'''
912 self.move_to_previous_screen_callback()
913
914- def _move_menu(self, menu_direction):
915- '''Move the menu in the given direction.'''
916- self.menu.move(menu_direction)
917-
918 def _handle_up(self):
919 '''Handle UserEvent.NAVIGATE_UP.'''
920- self._move_menu(TextMenu.UP)
921+ self.menu.up()
922
923 def _handle_down(self):
924 '''Handle UserEvent.NAVIGATE_DOWN.'''
925- self._move_menu(TextMenu.DOWN)
926+ self.menu.down()
927
928- def _handle_select(self):
929+ def _handle_select(self, event=None):
930 '''Handle UserEvent.NAVIGATE_SELECT.'''
931- self.question_callback(self.menu.get_current_menuitem().get_userdata())
932+ self.question_callback(self.menu.selected_userdata)
933 self.go_back()
934
935
936=== modified file 'entertainerlib/frontend/gui/screens/rss.py'
937--- entertainerlib/frontend/gui/screens/rss.py 2009-06-25 19:06:28 +0000
938+++ entertainerlib/frontend/gui/screens/rss.py 2009-07-19 19:27:31 +0000
939@@ -5,13 +5,9 @@
940
941 from entertainerlib.frontend.gui.screens.screen import Screen
942 from entertainerlib.frontend.gui.widgets.label import Label
943-from entertainerlib.frontend.gui.widgets.menu import Menu
944-from entertainerlib.frontend.gui.widgets.selector import Selector
945 from entertainerlib.frontend.gui.widgets.texture import Texture
946 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
947-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
948 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
949-from entertainerlib.frontend.medialibrary.feeds import Feed
950 from entertainerlib.utils.feed_utils import FeedEntryParser
951
952 class Rss(Screen):
953@@ -21,9 +17,9 @@
954 Screen.__init__(self, 'Rss', move_to_new_screen_callback)
955
956 self.feed_order = 'UNREAD' # Feeds ordered by number of unread entries
957- self.active_menu = "feed"
958 self.theme = self.config.theme
959 self.library = feed_library
960+ self.parser = FeedEntryParser()
961
962 # Screen Title (Displayed at the bottom left corner)
963 screen_title = Label(0.13, "screentitle", 0, 0.87, _("Headlines"))
964@@ -47,11 +43,12 @@
965 else:
966 # Menus
967 self._create_context_menu()
968- self._create_feed_menu()
969+ self.menu = self._create_feed_menu()
970+ self.add(self.menu)
971 self.li = None
972 self._create_feed_menu_indicator()
973- self.menu.set_active(True)
974- self.context_menu.set_active(False)
975+ self.menu.active = True
976+ self.context_menu.active = False
977
978 # Feeds Title
979 feeds_title = Label(0.04167, "title", 0.3953, 0.2, _("Feeds"))
980@@ -62,45 +59,41 @@
981 self.add(unread_title)
982
983 # Feed description text
984- desc_text = FeedEntryParser().strip_tags(
985- Feed(self.menu.get_current_menuitem().get_userdata()
986- ).description)
987+ desc_text = self.parser.strip_tags(
988+ self.menu.selected_userdata.description)
989+
990 self.description = Label(0.03646, "text", 0.3953, 0.9, desc_text)
991 self.description.set_ellipsize(pango.ELLIPSIZE_END)
992 self.description.set_use_markup(True)
993 self.description.set_size(0.5088, 0.0651)
994 self.add(self.description)
995
996+ self.menu.connect('selected', self._handle_select)
997+ self.menu.connect('moved', self._display_selected_feed)
998+ self.menu.connect('activated', self._menu_activation)
999+ self.context_menu.connect('selected', self._handle_select)
1000+ self.context_menu.connect('activated',
1001+ self._context_menu_activation)
1002+
1003 def _create_feed_menu(self):
1004 """
1005 Create Feed-menu. This menu contains list of Feeds. It also displays
1006 number of unread entries per feed.
1007 """
1008- self.menu = TextMenu(self.theme, self.config.show_effects())
1009- self.menu.set_row_count(1)
1010- self.menu.set_visible_column_count(7)
1011- self.menu.set_item_size(self.get_abs_x(0.5124), self.get_abs_y(0.0781))
1012- self.menu.set_position(self.get_abs_x(0.3807), self.get_abs_y(0.2604))
1013+ menu = TextMenu(0.3807, 0.2604, 0.5124, 0.0781)
1014
1015- # Create menu items based on FeedLibrary
1016 feeds = self.library.get_feeds(self.feed_order)
1017- for feed in feeds:
1018- unread = feed.get_number_of_unread()
1019- if unread == 0:
1020- unread = None
1021- else:
1022- unread = str(unread)
1023- item = TextMenuItem(0.5124, 0.0781,
1024- FeedEntryParser().strip_tags(feed.title), unread)
1025- item.set_userdata(feed.url)
1026- self.menu.add_actor(item)
1027+ feeds_list = [[self.parser.strip_tags(feed.title), \
1028+ str(feed.get_number_of_unread()), feed] \
1029+ for feed in feeds]
1030+ menu.async_add(feeds_list)
1031
1032- self.add(self.menu)
1033+ return menu
1034
1035 def _create_feed_menu_indicator(self):
1036 '''Create a ListIndicator for feed menu.'''
1037 self.li = ListIndicator(0.7, 0.9, 0.2, 0.045, ListIndicator.VERTICAL)
1038- self.li.set_maximum(self.menu.get_number_of_items())
1039+ self.li.set_maximum(len(self.library.get_feeds(self.feed_order)))
1040 self.add(self.li)
1041
1042 def _create_context_menu(self):
1043@@ -108,24 +101,14 @@
1044 Create RSS-feed context menu and add it to the screen.
1045 This menu contains buttons for update and sorting.
1046 """
1047- select = Selector(Selector.LARGE, self.theme)
1048- self.context_menu = Menu()
1049-
1050- self.context_menu.set_loop(True)
1051- item1 = TextMenuItem(0.2196, 0.0781, _("Update feeds"))
1052- self.context_menu.add(item1)
1053- item2 = TextMenuItem(0.2196, 0.0781, _("Mark all as read"))
1054- self.context_menu.add(item2)
1055- item3 = TextMenuItem(0.2196, 0.0781, _("Sort by name"))
1056- self.context_menu.add(item3)
1057- item4 = TextMenuItem(0.2196, 0.0781, _("Sort by unread"))
1058- self.context_menu.add(item4)
1059-
1060- self.context_menu.set_selector(select)
1061- self.context_menu.set_position(self.get_abs_x(0.0732),
1062- self.get_abs_y(0.2604))
1063- self.context_menu.set_active(False)
1064- self.context_menu.show_all()
1065+ self.context_menu = TextMenu(0.0732, 0.2604, 0.2196, 0.0781)
1066+
1067+ self.context_menu.add_item(_("Update feeds"))
1068+ self.context_menu.add_item(_("Mark all as read"))
1069+ self.context_menu.add_item(_("Sort by name"))
1070+ self.context_menu.add_item(_("Sort by unread"))
1071+
1072+ self.context_menu.active = False
1073 self.add(self.context_menu)
1074
1075 def _create_no_feeds_information(self):
1076@@ -159,64 +142,41 @@
1077 Update screen widgets. This is called always when screen is poped from
1078 the screen history.
1079 """
1080- #remove current feed menu, create a new one and set it to active
1081- selected_name = self.menu.get_current_menuitem().label.get_text()
1082-
1083- self.remove(self.menu)
1084- self._create_feed_menu()
1085- self.menu.set_active(True)
1086-
1087- index = self.menu.get_index(selected_name)
1088- if not index == -1:
1089- self.menu.select(index)
1090-
1091- def _move_menu(self, menu_direction):
1092- '''Move the menu in the given direction.'''
1093- if menu_direction == TextMenu.UP:
1094- scroll_direction = self.context_menu.scroll_up
1095- elif menu_direction == TextMenu.DOWN:
1096- scroll_direction = self.context_menu.scroll_down
1097-
1098- if self.active_menu == "feed":
1099- self.menu.move(menu_direction)
1100- self.li.set_current(self.menu.get_current_position() + 1)
1101- feed = Feed(self.menu.get_current_menuitem().get_userdata())
1102- self.description.set_text(FeedEntryParser().strip_tags(
1103- feed.description))
1104- self.description.set_size(0.5088, 0.0651)
1105- else:
1106- scroll_direction()
1107+ feeds = self.library.get_feeds(self.feed_order)
1108+ for idx, item in enumerate(self.menu.items):
1109+ feed = feeds[idx]
1110+ item.userdata = feed
1111+ item.update(self.parser.strip_tags(feed.title),
1112+ str(feed.get_number_of_unread()))
1113
1114 def _handle_up(self):
1115 '''Handle UserEvent.NAVIGATE_UP.'''
1116- self._move_menu(TextMenu.UP)
1117+ if self.menu.active:
1118+ self.menu.up()
1119+ else:
1120+ self.context_menu.up()
1121
1122 def _handle_down(self):
1123 '''Handle UserEvent.NAVIGATE_DOWN.'''
1124- self._move_menu(TextMenu.DOWN)
1125+ if self.menu.active:
1126+ self.menu.down()
1127+ else:
1128+ self.context_menu.down()
1129
1130 def _handle_left(self):
1131 '''Handle UserEvent.NAVIGATE_LEFT.'''
1132- if self.active_menu == "feed":
1133- self.active_menu = "context"
1134- self.li.hide_position()
1135- self.menu.set_active(False)
1136- self.description.hide()
1137- self.context_menu.set_active(True)
1138+ if self.menu.active:
1139+ self._context_menu_activation()
1140
1141 def _handle_right(self):
1142 '''Handle UserEvent.NAVIGATE_RIGHT.'''
1143- if self.active_menu == "context":
1144- self.active_menu = "feed"
1145- self.li.show_position()
1146- self.menu.set_active(True)
1147- self.description.show()
1148- self.context_menu.set_active(False)
1149+ if not self.menu.active:
1150+ self._menu_activation()
1151
1152- def _handle_select(self):
1153+ def _handle_select(self, event=None):
1154 '''Handle UserEvent.NAVIGATE_SELECT.'''
1155- if self.active_menu == "context":
1156- index = self.context_menu.get_current_position()
1157+ if not self.menu.active:
1158+ index = self.context_menu.selected_index
1159 if index == 0:
1160 # Send message to bus and update screen
1161 self.library.request_feed_update()
1162@@ -235,14 +195,34 @@
1163 self.update()
1164 else:
1165 # Feed selected from the menu. Change screen.
1166- feed = Feed(self.menu.get_current_menuitem().get_userdata())
1167+ feed = self.menu.selected_userdata
1168 kwargs = { 'feed' : feed }
1169 self.callback("feed", kwargs)
1170
1171- def handle_user_event(self, event):
1172+ def handle_user_event(self, event=None):
1173 '''Handle screen specific user events unless the library is empty.'''
1174 if self.library.is_empty() or len(self.library.get_feeds()) == 0:
1175 return
1176 else:
1177 Screen.handle_user_event(self, event)
1178
1179+ def _display_selected_feed(self, actor=None):
1180+ '''Update of the list indicator and the description label'''
1181+ self.li.set_current(self.menu.selected_index + 1)
1182+ feed = self.menu.selected_userdata
1183+ self.description.set_text(self.parser.strip_tags(feed.description))
1184+
1185+ def _menu_activation(self, actor=None):
1186+ '''Handle the menu activation'''
1187+ self.menu.active = True
1188+ self.context_menu.active = False
1189+ self.li.show_position()
1190+ self.description.show()
1191+
1192+ def _context_menu_activation(self, actor=None):
1193+ '''Handle the context menu activation'''
1194+ self.menu.active = False
1195+ self.context_menu.active = True
1196+ self.li.hide_position()
1197+ self.description.hide()
1198+
1199
1200=== modified file 'entertainerlib/frontend/gui/screens/screen.py'
1201--- entertainerlib/frontend/gui/screens/screen.py 2009-05-16 16:40:48 +0000
1202+++ entertainerlib/frontend/gui/screens/screen.py 2009-05-28 20:15:47 +0000
1203@@ -112,7 +112,7 @@
1204 '''Handle UserEvent.NAVIGATE_RIGHT.'''
1205 pass
1206
1207- def _handle_select(self):
1208+ def _handle_select(self, event=None):
1209 '''Handle UserEvent.NAVIGATE_SELECT.'''
1210 pass
1211
1212@@ -134,6 +134,7 @@
1213 if not self.has_tabs:
1214 raise ScreenException('This screen does not support tabs.')
1215
1216+ tab.tab_group = self.tab_group
1217 self.tab_group.add_tab(tab)
1218 # XXX: laymansterms - figure this out, why do we need to add directly
1219 # to the screen instead of the tab group, why is the positioning
1220
1221=== modified file 'entertainerlib/frontend/gui/screens/tv_episodes.py'
1222--- entertainerlib/frontend/gui/screens/tv_episodes.py 2009-07-01 11:15:57 +0000
1223+++ entertainerlib/frontend/gui/screens/tv_episodes.py 2009-07-21 22:22:53 +0000
1224@@ -5,14 +5,12 @@
1225 import pango
1226
1227 from entertainerlib.frontend.gui.screens.screen import Screen
1228-from entertainerlib.frontend.gui.user_event import UserEvent
1229 from entertainerlib.frontend.gui.widgets.eyecandy_texture import (
1230 EyeCandyTexture)
1231 from entertainerlib.frontend.gui.widgets.label import Label
1232 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
1233 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
1234 from entertainerlib.frontend.gui.widgets.scroll_area import ScrollArea
1235-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
1236
1237 class TvEpisodes(Screen):
1238 '''Screen contains list of all episodes of one specific season.'''
1239@@ -32,58 +30,43 @@
1240 self.add(screen_title)
1241
1242 self.scroll_area = None
1243- self.episode_menu = None
1244 self.title = None
1245 self.thumb = None
1246- self._create_episode_menu()
1247+ self.menu = self._create_episode_menu()
1248+ self.add(self.menu)
1249 self._create_episode_info_box()
1250
1251 #List indicator
1252 self.li = ListIndicator(0.8, 0.9, 0.2, 0.045, ListIndicator.VERTICAL)
1253- self.li.set_maximum(self.episode_menu.get_number_of_items())
1254+ self.li.set_maximum(
1255+ len(self.tv_series.get_episodes_from_season(self.season_number)))
1256 self.add(self.li)
1257
1258- # Add the additional actions that are needed but not handled by default
1259- self.event_handlers.update({
1260- UserEvent.NAVIGATE_FIRST_PAGE : self._handle_first_page,
1261- UserEvent.NAVIGATE_LAST_PAGE : self._handle_last_page,
1262- UserEvent.NAVIGATE_PREVIOUS_PAGE : self._handle_previous_page,
1263- UserEvent.NAVIGATE_NEXT_PAGE : self._handle_next_page
1264- })
1265+ self.menu.connect("moved", self._update_episode_info)
1266+ self.menu.connect("selected", self._handle_select)
1267+ self.menu.connect("activated", self._on_menu_activated)
1268
1269 def _create_episode_menu(self):
1270- """
1271- Create a list of available seasons
1272- """
1273- self.episode_menu = TextMenu(self.theme, self.config.show_effects())
1274- self.episode_menu.set_row_count(1)
1275- #self.episode_menu.set_visible_column_count(10)
1276- self.episode_menu.set_item_size(self.get_abs_x(0.4393),
1277- self.get_abs_y(0.0781))
1278- self.episode_menu.set_position(self.get_abs_x(0.4978),
1279- self.get_abs_y(0.1563))
1280+ """Create a list of available seasons."""
1281+ menu = TextMenu(0.4978, 0.1563, 0.4393, 0.0781)
1282
1283- # Create menu items based on MusicLibrary
1284 episodes = self.tv_series.get_episodes_from_season(self.season_number)
1285- for episode in episodes:
1286- item = TextMenuItem(0.4393, 0.0781,
1287- str(episode.get_episode_number()) + ". " + episode.get_title())
1288- item.set_userdata(episode)
1289- self.episode_menu.add_actor(item)
1290-
1291- self.episode_menu.set_active(True)
1292- self.add(self.episode_menu)
1293+ episodes_list = [[_("%(num)d. %(title)s") % \
1294+ {'num': episode.get_episode_number(), 'title': episode.get_title()},
1295+ None, episode] for episode in episodes]
1296+ menu.async_add(episodes_list)
1297+
1298+ menu.active = True
1299+
1300+ return menu
1301
1302 def _create_thumbnail_texture(self):
1303- """
1304- Create a thumbnail texture. This is called as menu is scrolled
1305- """
1306+ """Create a thumbnail texture. This is called as menu is scrolled."""
1307 if self.thumb:
1308 self.thumb.hide()
1309
1310 # Thumbnail. Use cover art if thumbnail doesn't exist
1311- menu_item = self.episode_menu.get_current_menuitem()
1312- thumbnail = menu_item.get_userdata().get_thumbnail_url()
1313+ thumbnail = self.menu.selected_userdata.get_thumbnail_url()
1314 if(thumbnail is not None):
1315 pixbuf = gtk.gdk.pixbuf_new_from_file(thumbnail)
1316 thumb_width = 0.2928
1317@@ -115,7 +98,7 @@
1318
1319 # Title
1320 self.title = Label(0.04, "title", 0.05, 0.55,
1321- self.episode_menu.get_current_menuitem().get_userdata().get_title(),
1322+ self.menu.selected_userdata.get_title(),
1323 font_weight="bold")
1324 self.title.set_ellipsize(pango.ELLIPSIZE_END)
1325 self.title.width = 0.4
1326@@ -123,79 +106,62 @@
1327
1328 # Plot
1329 plot = Label(0.029, "subtitle", 0, 0,
1330- self.episode_menu.get_current_menuitem().get_userdata().get_plot())
1331+ self.menu.selected_userdata.get_plot())
1332 plot.width = 0.4
1333
1334- self.scroll_area = ScrollArea(0.4, 0.3516, 0.05, 0.63, plot)
1335+ self.scroll_area = ScrollArea(0.05, 0.63, 0.4, 0.15, plot)
1336+ self.scroll_area.connect("activated", self._on_scroll_area_activated)
1337 self.add(self.scroll_area)
1338
1339- def _update_episode_info_box(self, episode):
1340- """
1341- Update episode infobox
1342- @param episode: Display information from this episode
1343- """
1344+ def _update_episode_info(self, event=None):
1345+ '''Update information from this episode.'''
1346+ self.li.set_current(self.menu.selected_index + 1)
1347+
1348 self._create_thumbnail_texture()
1349- self.title.set_text(episode.get_title())
1350+ self.title.set_text(self.menu.selected_userdata.get_title())
1351 self.title.width = 0.4
1352
1353 plot = Label(0.029, "subtitle", 0, 0,
1354- self.episode_menu.get_current_menuitem().get_userdata().get_plot())
1355+ self.menu.selected_userdata.get_plot())
1356 plot.width = 0.4
1357 self.scroll_area.set_content(plot)
1358
1359- def _move_menu(self, menu_direction):
1360- '''Move the menu in the given direction.'''
1361- if menu_direction == TextMenu.UP:
1362- scroll_direction = self.scroll_area.scroll_up
1363- elif menu_direction == TextMenu.DOWN:
1364- scroll_direction = self.scroll_area.scroll_down
1365-
1366- if self.episode_menu.is_active():
1367- self.episode_menu.move(menu_direction)
1368- self.li.set_current(self.episode_menu.get_current_position() + 1)
1369- self._update_episode_info_box(
1370- self.episode_menu.get_current_menuitem().get_userdata())
1371- else:
1372- scroll_direction()
1373-
1374 def _handle_up(self):
1375 '''Handle UserEvent.NAVIGATE_UP.'''
1376- self._move_menu(TextMenu.UP)
1377+ if self.menu.active:
1378+ self.menu.up()
1379+ else:
1380+ self.scroll_area.scroll_up()
1381
1382 def _handle_down(self):
1383 '''Handle UserEvent.NAVIGATE_DOWN.'''
1384- self._move_menu(TextMenu.DOWN)
1385+ if self.menu.active:
1386+ self.menu.down()
1387+ else:
1388+ self.scroll_area.scroll_down()
1389
1390 def _handle_left(self):
1391 '''Handle UserEvent.NAVIGATE_LEFT.'''
1392- self.episode_menu.set_active(False)
1393- self.scroll_area.set_active(True)
1394+ self.menu.active = False
1395+ self.scroll_area.active = True
1396
1397 def _handle_right(self):
1398 '''Handle UserEvent.NAVIGATE_RIGHT.'''
1399- self.episode_menu.set_active(True)
1400- self.scroll_area.set_active(False)
1401+ self.menu.active = True
1402+ self.scroll_area.active = False
1403
1404- def _handle_select(self):
1405+ def _handle_select(self, event=None):
1406 '''Handle UserEvent.NAVIGATE_SELECT.'''
1407- episode = self.episode_menu.get_current_menuitem().get_userdata()
1408+ episode = self.menu.selected_userdata
1409 self.media_player.set_media(episode)
1410 self.media_player.play()
1411 self.callback("video_osd")
1412
1413- def _handle_first_page(self):
1414- '''Handle UserEvent.NAVIGATE_FIRST_PAGE.'''
1415- self.scroll_area.scroll_to_top()
1416-
1417- def _handle_last_page(self):
1418- '''Handle UserEvent.NAVIGATE_LAST_PAGE.'''
1419- self.scroll_area.scroll_to_bottom()
1420-
1421- def _handle_previous_page(self):
1422- '''Handle UserEvent.NAVIGATE_PREVIOUS_PAGE.'''
1423- self.scroll_area.scroll_page_up()
1424-
1425- def _handle_next_page(self):
1426- '''Handle UserEvent.NAVIGATE_NEXT_PAGE.'''
1427- self.scroll_area.scroll_page_down()
1428+ def _on_menu_activated(self, event=None):
1429+ '''Handle menu activation.'''
1430+ self.scroll_area.active = False
1431+
1432+ def _on_scroll_area_activated(self, event=None):
1433+ '''Handle scroll_area activation.'''
1434+ self.menu.active = False
1435
1436
1437=== modified file 'entertainerlib/frontend/gui/screens/tv_series.py'
1438--- entertainerlib/frontend/gui/screens/tv_series.py 2009-07-01 11:15:57 +0000
1439+++ entertainerlib/frontend/gui/screens/tv_series.py 2009-07-12 07:57:05 +0000
1440@@ -9,7 +9,6 @@
1441 from entertainerlib.frontend.gui.widgets.label import Label
1442 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
1443 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
1444-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
1445
1446 class TvSeries(Screen):
1447 '''Screen that contains all seasons of one TV series.'''
1448@@ -26,37 +25,35 @@
1449 self.add(screen_title)
1450
1451 self.art = None
1452- self.season_menu = None
1453+ self.menu = None
1454 self._create_series_cover_texture()
1455- self._create_season_menu()
1456+ self.menu = self._create_season_menu()
1457+ self.add(self.menu)
1458
1459 #List indicator
1460 self.li = ListIndicator(0.8, 0.9, 0.2, 0.045, ListIndicator.VERTICAL)
1461- self.li.set_maximum(self.season_menu.get_number_of_items())
1462+ self.li.set_maximum(len(self.tv_series.get_seasons()))
1463 self.add(self.li)
1464
1465+ self.menu.connect("moved", self._update_season_info)
1466+ self.menu.connect("selected", self._handle_select)
1467+
1468 def _create_season_menu(self):
1469 """
1470 Create a list of available seasons
1471 """
1472- self.season_menu = TextMenu(self.theme, self.config.show_effects())
1473- self.season_menu.set_row_count(1)
1474- self.season_menu.set_item_size(self.get_abs_x(0.4393),
1475- self.get_abs_y(0.0781))
1476- self.season_menu.set_position(self.get_abs_x(0.4978),
1477- self.get_abs_y(0.1563))
1478+ menu = TextMenu(0.4978, 0.1563, 0.4393, 0.0781)
1479
1480- # Create menu items based on MusicLibrary
1481 seasons = self.tv_series.get_seasons()
1482 seasons.sort()
1483- for season in seasons:
1484- item = TextMenuItem(0.4393, 0.0781,
1485- _("Season %(num)s") % {'num': season})
1486- item.set_userdata(season)
1487- self.season_menu.add_actor(item)
1488-
1489- self.season_menu.set_active(True)
1490- self.add(self.season_menu)
1491+
1492+ seasons_list = [[_("Season %(num)s") % {'num': season}, None, season] \
1493+ for season in seasons]
1494+ menu.async_add(seasons_list)
1495+
1496+ menu.active = True
1497+
1498+ return menu
1499
1500 def _create_series_cover_texture(self):
1501 """
1502@@ -72,22 +69,21 @@
1503 self.art = EyeCandyTexture(0.16, 0.15, 0.2196, 0.5859, pixbuf)
1504 self.add(self.art)
1505
1506- def _move_menu(self, menu_direction):
1507- '''Move the menu in the given direction.'''
1508- self.season_menu.move(menu_direction)
1509- self.li.set_current(self.season_menu.get_current_position() + 1)
1510+ def _update_season_info(self, event=None):
1511+ '''Update the ListIndicator.'''
1512+ self.li.set_current(self.menu.selected_index + 1)
1513
1514 def _handle_up(self):
1515 '''Handle UserEvent.NAVIGATE_UP.'''
1516- self._move_menu(TextMenu.UP)
1517+ self.menu.up()
1518
1519 def _handle_down(self):
1520 '''Handle UserEvent.NAVIGATE_DOWN.'''
1521- self._move_menu(TextMenu.DOWN)
1522+ self.menu.down()
1523
1524- def _handle_select(self):
1525+ def _handle_select(self, event=None):
1526 '''Handle UserEvent.NAVIGATE_SELECT.'''
1527- season = self.season_menu.get_current_menuitem().get_userdata()
1528+ season = self.menu.selected_userdata
1529 kwargs = { 'tv_series' : self.tv_series, 'season_number' : season }
1530 self.callback("tv_episodes", kwargs)
1531
1532
1533=== modified file 'entertainerlib/frontend/gui/tabs/albums_tab.py'
1534--- entertainerlib/frontend/gui/tabs/albums_tab.py 2009-05-14 03:22:31 +0000
1535+++ entertainerlib/frontend/gui/tabs/albums_tab.py 2009-07-14 11:06:21 +0000
1536@@ -2,18 +2,14 @@
1537 '''Albums tab which displays albums and allows users to select them'''
1538 # pylint: disable-msg=W1001
1539
1540-import clutter
1541-import gobject
1542 import pango
1543
1544 from entertainerlib.frontend.gui.tabs.tab import Tab
1545 from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
1546-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
1547 from entertainerlib.frontend.gui.widgets.label import Label
1548 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
1549 from entertainerlib.frontend.gui.widgets.loading_animation import (
1550 LoadingAnimation)
1551-from entertainerlib.frontend.gui.widgets.texture import Texture
1552
1553 class AlbumsTab(Tab):
1554 '''Tab to show album listings'''
1555@@ -28,48 +24,28 @@
1556 self.add(self.throbber)
1557
1558 if len(albums) < 4:
1559- # XXX: laymansterms - This is "less than elegant." I'm not fixing
1560- # ImageMenu right now so we can't get rid of tex_size_x and y yet.
1561- # We really need to eliminate the concept tex_size_y, but it will
1562- # have to wait for now.
1563 x_percent = 0.2928
1564- tex_size_x = self.get_abs_x(x_percent)
1565- tex_size_y = self.get_abs_y(0.5208)
1566- row_count = 1
1567- visible = 3
1568+ visible_rows = 1
1569+ visible_cols = 3
1570 elif len(albums) < 13:
1571 x_percent = 0.1464
1572- tex_size_x = self.get_abs_x(x_percent)
1573- tex_size_y = self.get_abs_y(0.2604)
1574- row_count = 2
1575- visible = 6
1576+ visible_rows = 2
1577+ visible_cols = 6
1578 else:
1579 x_percent = 0.1098
1580- tex_size_x = self.get_abs_x(x_percent)
1581- tex_size_y = self.get_abs_y(0.1953)
1582- row_count = 3
1583- visible = 8
1584+ visible_rows = 3
1585+ visible_cols = 8
1586
1587 # Create albums menu
1588- self.menu = ImageMenu(self.theme)
1589- self.menu.set_row_count(row_count)
1590- self.menu.set_visible_column_count(visible)
1591- self.menu.set_item_size(tex_size_x, tex_size_y)
1592-
1593- # Default texture
1594- self.default_cover = Texture(self.theme.getImage("default_album_art"))
1595-
1596- self._async_menu_build(albums, x_percent)
1597- self._selected_album = self.menu.get_current_menuitem().get_userdata()
1598-
1599- self.menu.set_position(self.get_abs_x(0.0586), self.get_abs_y(0.1823))
1600- self.menu.show()
1601-
1602- if self.config.show_effects():
1603- self.menu.set_animate(True)
1604-
1605+ self.menu = ImageMenu(0.07, 0.16, x_percent, self.y_for_x(x_percent))
1606+ self.menu.visible_rows = visible_rows
1607+ self.menu.visible_cols = visible_cols
1608+ self.menu.items_per_col = self.menu.visible_rows
1609 self.add(self.menu)
1610
1611+ albums_list = [[album.get_album_art_url(), album] for album in albums]
1612+ self.menu.async_add_albums(albums_list)
1613+
1614 self.li = ListIndicator(0.77, 0.8, 0.18, 0.045,
1615 ListIndicator.HORIZONTAL)
1616 self.li.set_maximum(len(albums))
1617@@ -92,102 +68,79 @@
1618
1619 self.connect('activated', self._on_activated)
1620 self.connect('deactivated', self._on_deactivated)
1621-
1622- def _async_menu_build(self, albums, item_size_percent):
1623- """
1624- This function is called as an async callback. It is also a recursive
1625- function. This allows us to load menu items in the background without
1626- blocking the user interface.
1627- """
1628-
1629- # Check if we should stop recursive calls (menu is complete)
1630- if len(albums) > 0:
1631- album = albums[0]
1632- if album.has_album_art():
1633- texture = Texture(album.get_album_art_url())
1634- else:
1635- texture = clutter.CloneTexture(self.default_cover)
1636-
1637- item = ImageMenuItem(item_size_percent, 1, texture)
1638- item.set_userdata(album)
1639- self.menu.add_actor(item)
1640- # Recursive call, remove first album from the list
1641- gobject.timeout_add(25, self._async_menu_build, albums[1:],
1642- item_size_percent)
1643- return False
1644- else:
1645- self.throbber.hide()
1646- # Done loading the albums, notify gobject to stop calling menu build
1647- return False
1648-
1649- def _get_selected_album(self):
1650- '''Get the currently selected album from the menu.'''
1651- return self.menu.get_current_menuitem().get_userdata()
1652-
1653- selected_album = property(_get_selected_album)
1654+ self.menu.connect("moved", self._update_album_info)
1655+ self.menu.connect("selected", self._handle_select)
1656+ self.menu.connect("activated", self._on_activated)
1657+ self.menu.connect("filled", self._on_menu_filled)
1658
1659 def can_activate(self):
1660 '''Albums tab will always be created from an existing artist with at
1661 least one album.'''
1662 return True
1663
1664- def _update_album_info(self):
1665+ def _update_album_info(self, event=None):
1666 '''Update the album information labels.'''
1667 if self.active:
1668- album = self.menu.get_current_menuitem().get_userdata()
1669+ album = self.menu.selected_userdata
1670 self.album_title.set_text(album.get_title())
1671 self.album_artist.set_text(album.get_artist())
1672 self.album_tracks.set_text(_("%(total)s tracks") % \
1673 {'total': album.get_number_of_tracks()})
1674+ self.li.show()
1675+ self.li.set_current(self.menu.selected_index + 1)
1676 else:
1677 self.album_title.set_text("")
1678 self.album_artist.set_text("")
1679 self.album_tracks.set_text("")
1680-
1681- def _update_menu(self, direction):
1682- '''Update the menu based on the provided menu direction.'''
1683- self.menu.move(direction)
1684- self._update_album_info()
1685- self.li.set_current(self.menu.get_current_position() + 1)
1686- return False
1687+ self.li.hide()
1688
1689 def _handle_up(self):
1690 '''Handle the up user event.'''
1691- if self.menu.get_cursor_position()[1] == 0:
1692+ if self.menu.on_top:
1693 return True # Move control back to tab bar
1694 else:
1695- return self._update_menu(self.menu.UP)
1696+ self.menu.up()
1697+ return False
1698
1699 def _handle_down(self):
1700 '''Handle the down user event.'''
1701- return self._update_menu(self.menu.DOWN)
1702+ self.menu.down()
1703+ return False
1704
1705 def _handle_left(self):
1706 '''Handle the left user event.'''
1707- return self._update_menu(self.menu.LEFT)
1708+ self.menu.left()
1709+ return False
1710
1711 def _handle_right(self):
1712 '''Handle the right user event.'''
1713- return self._update_menu(self.menu.RIGHT)
1714+ self.menu.right()
1715+ return False
1716
1717- def _handle_select(self):
1718+ def _handle_select(self, event=None):
1719 '''Handle the select user event.'''
1720- album = self.menu.get_current_menuitem().get_userdata()
1721+ album = self.menu.selected_userdata
1722 kwargs = { 'album' : album }
1723 self.callback("album", kwargs)
1724 return False
1725
1726 def _on_activated(self, event=None):
1727 '''Tab activated.'''
1728+ if self.tab_group is not None:
1729+ self.tab_group.active = False
1730+ self.menu.active = True
1731+ self.active = True
1732 self._update_album_info()
1733- self.menu.set_active(True)
1734- self.menu.set_opacity(255)
1735 return False
1736
1737 def _on_deactivated(self, event=None):
1738 '''Tab deactivated.'''
1739+ self.active = False
1740+ self.menu.active = False
1741 self._update_album_info()
1742- self.menu.set_active(False)
1743- self.menu.set_opacity(128)
1744 return False
1745
1746+ def _on_menu_filled(self, event=None):
1747+ '''Handles filled event.'''
1748+ self.throbber.hide()
1749+
1750
1751=== modified file 'entertainerlib/frontend/gui/tabs/artists_tab.py'
1752--- entertainerlib/frontend/gui/tabs/artists_tab.py 2009-05-14 03:22:31 +0000
1753+++ entertainerlib/frontend/gui/tabs/artists_tab.py 2009-07-16 20:31:22 +0000
1754@@ -2,8 +2,6 @@
1755 '''Artists tab for the music screen'''
1756 # pylint: disable-msg=W1001
1757
1758-import clutter
1759-import gobject
1760 import pango
1761
1762 from entertainerlib.frontend.gui.tabs.tab import Tab
1763@@ -12,7 +10,6 @@
1764 from entertainerlib.frontend.gui.widgets.loading_animation import (
1765 LoadingAnimation)
1766 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
1767-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
1768
1769 class ArtistsTab(Tab):
1770 '''Tab for the music screen to show artist listings'''
1771@@ -27,19 +24,17 @@
1772 self.throbber.show()
1773 self.add(self.throbber)
1774
1775- self.menu = TextMenu(self.config.theme, self.config.show_effects())
1776- self.menu.set_item_size(self.get_abs_x(0.293), self.get_abs_y(0.078))
1777- cursor = clutter.Rectangle()
1778- cursor.set_color((0, 0, 0, 0))
1779- self.menu.set_cursor(cursor)
1780- self.menu.set_orientation(TextMenu.HORIZONTAL)
1781- self.menu.set_row_count(7)
1782- self.menu.set_visible_column_count(3)
1783- self.menu.set_position(self.get_abs_x(0.057), self.get_abs_y(0.208))
1784- if self.config.show_effects():
1785- self.menu.set_animate(True)
1786+ self.menu = TextMenu(0.057, 0.208, 0.293, 0.078)
1787+ self.menu.items_per_row = 3
1788+ self.menu.visible_rows = 7
1789+ self.menu.visible_cols = 3
1790+ self.menu.active = False
1791+ self.menu.cursor = None
1792 self.add(self.menu)
1793
1794+ artists_list = [[artist, None, artist] for artist in artists]
1795+ self.menu.async_add_artists(artists_list)
1796+
1797 # Create artist label
1798 self.artist_title = Label(0.0416, "title", 0.22, 0.794, "")
1799 self.artist_title.set_ellipsize(pango.ELLIPSIZE_END)
1800@@ -54,107 +49,85 @@
1801
1802 # Create artis menu list indicator
1803 self.li = ListIndicator(0.77, 0.8, 0.18, 0.045,
1804- ListIndicator.HORIZONTAL)
1805+ ListIndicator.VERTICAL)
1806 self.li.set_maximum(len(artists))
1807 self.add(self.li)
1808
1809- self._async_menu_build(artists)
1810- self._selected_artist = self.menu.get_current_menuitem().get_userdata()
1811-
1812 self.connect('activated', self._on_activated)
1813 self.connect('deactivated', self._on_deactivated)
1814-
1815- def _async_menu_build(self, artists):
1816- """
1817- This function is called as an async callback. It is also a recursive
1818- function. This allows us to load menu items in the background without
1819- blocking the user interface.
1820- """
1821-
1822- # Check if we should stop recursive calls (menu is complete)
1823- if len(artists) > 0:
1824- artist = artists[0]
1825- item = TextMenuItem(0.293, 0.104, artist)
1826- item.justified = True
1827- item.set_userdata(artist)
1828- self.menu.add_actor(item)
1829- # Recursive call, remove first artist from the list
1830- gobject.timeout_add(15, self._async_menu_build, artists[1:])
1831- return False
1832- else:
1833- self.throbber.hide()
1834- # Done loading artists, notify gobject to stop calling menu build
1835- return False
1836-
1837- def _get_selected_artist(self):
1838- '''Get the currently selected artist from the menu.'''
1839- return self.menu.get_current_menuitem().get_userdata()
1840-
1841- selected_artist = property(_get_selected_artist)
1842+ self.menu.connect("moved", self._update_artist_info)
1843+ self.menu.connect("selected", self._handle_select)
1844+ self.menu.connect("activated", self._on_activated)
1845+ self.menu.connect("filled", self._on_menu_filled)
1846
1847 def can_activate(self):
1848 '''Albums tab will always be created from an existing artist with at
1849 least one album.'''
1850 return True
1851
1852- def _update_artist_info(self):
1853+ def _update_artist_info(self, event=None):
1854 '''Update the artist information labels'''
1855 if self.active:
1856- artist = self.menu.get_current_menuitem().get_userdata()
1857+ artist = self.menu.selected_userdata
1858 self.artist_title.set_text(artist)
1859 self.artist_albums.set_text(_("%(albums)d albums") %
1860 {'albums': self.library.number_of_albums_by_artist(artist)})
1861 self.artist_tracks.set_text(_("%(tracks)d tracks") %
1862 {'tracks': self.library.number_of_tracks_by_artist(artist)})
1863+ self.li.show()
1864+ self.li.set_current(self.menu.selected_index + 1)
1865 else:
1866 self.artist_title.set_text("")
1867 self.artist_albums.set_text("")
1868 self.artist_tracks.set_text("")
1869-
1870- def _update_menu(self, direction):
1871- '''Update the menu based on the provided menu direction.'''
1872- self.menu.move(direction)
1873- self._update_artist_info()
1874- self.li.set_current(self.menu.get_current_position() + 1)
1875- return False
1876+ self.li.hide()
1877
1878 def _handle_up(self):
1879 '''Handle the up user event.'''
1880- if self.menu.get_cursor_position()[1] == 0:
1881+ if self.menu.on_top:
1882 return True # Move control back to tab bar
1883 else:
1884- return self._update_menu(self.menu.UP)
1885+ self.menu.up()
1886+ return False
1887
1888 def _handle_down(self):
1889 '''Handle the down user event.'''
1890- return self._update_menu(self.menu.DOWN)
1891+ self.menu.down()
1892+ return False
1893
1894 def _handle_left(self):
1895 '''Handle the left user event.'''
1896- return self._update_menu(self.menu.LEFT)
1897+ self.menu.left()
1898+ return False
1899
1900 def _handle_right(self):
1901 '''Handle the right user event.'''
1902- return self._update_menu(self.menu.RIGHT)
1903+ self.menu.right()
1904+ return False
1905
1906- def _handle_select(self):
1907+ def _handle_select(self, event=None):
1908 '''Handle the select user event.'''
1909- artist = self.menu.get_current_menuitem().get_userdata()
1910+ artist = self.menu.selected_userdata
1911 kwargs = { 'artist' : artist }
1912 self.callback("artist", kwargs)
1913 return False
1914
1915 def _on_activated(self, event=None):
1916 '''Tab activated.'''
1917+ if self.tab_group is not None:
1918+ self.tab_group.active = False
1919+ self.menu.active = True
1920+ self.active = True
1921 self._update_artist_info()
1922- self.menu.set_active(True)
1923- self.menu.set_opacity(255)
1924 return False
1925
1926 def _on_deactivated(self, event=None):
1927 '''Tab deactivated.'''
1928+ self.active = False
1929+ self.menu.active = False
1930 self._update_artist_info()
1931- self.menu.set_active(False)
1932- self.menu.set_opacity(128)
1933- return False
1934+
1935+ def _on_menu_filled(self, event=None):
1936+ '''Handles filled event.'''
1937+ self.throbber.hide()
1938
1939
1940=== modified file 'entertainerlib/frontend/gui/tabs/lyrics_tab.py'
1941--- entertainerlib/frontend/gui/tabs/lyrics_tab.py 2009-05-14 03:22:31 +0000
1942+++ entertainerlib/frontend/gui/tabs/lyrics_tab.py 2009-07-22 20:46:29 +0000
1943@@ -1,6 +1,7 @@
1944 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
1945 '''Lyrics tab for the audio play screen'''
1946
1947+import clutter
1948 import pango
1949
1950 from entertainerlib.frontend.gui.tabs.tab import Tab
1951@@ -26,7 +27,8 @@
1952 lyrics.set_line_wrap_mode(pango.WRAP_WORD)
1953 lyrics.width = 0.366
1954
1955- self.lyrics_area = ScrollArea(0.4, 0.57, 0.5, 0.23, lyrics)
1956+ self.lyrics_area = ScrollArea(0.5, 0.23, 0.4, 0.57, lyrics)
1957+ self.lyrics_area.connect("activated", self._on_activated)
1958 self.add(self.lyrics_area)
1959
1960 self.connect('activated', self._on_activated)
1961@@ -39,24 +41,24 @@
1962 self.track.fetch_lyrics(self._lyrics_search_callback)
1963
1964 def _lyrics_search_callback(self, lyrics_text):
1965- '''
1966- This function is called when lyrics search is over.
1967- '''
1968+ '''This function is called when lyrics search is over.'''
1969 self.throbber.hide()
1970
1971- # Save the results to help determine if the tab can activate
1972+ # Save the results to help determine if the tab can activate.
1973 self.lyrics_text = lyrics_text
1974
1975- if lyrics_text is None:
1976- no_lyrics = Label(0.037, "title", 0.5, 0.5,
1977+ if lyrics_text == "":
1978+ no_lyrics = Label(0.037, "title", 0.7, 0.5,
1979 _("No lyrics found for this track"))
1980+ no_lyrics.set_anchor_point_from_gravity(clutter.GRAVITY_CENTER)
1981 self.add(no_lyrics)
1982 else:
1983 lyrics = Label(0.037, "subtitle", 0, 0, lyrics_text)
1984 lyrics.set_line_wrap_mode(pango.WRAP_WORD)
1985 lyrics.width = 0.366
1986
1987- self.lyrics_area = ScrollArea(0.4, 0.57, 0.5, 0.23, lyrics)
1988+ self.lyrics_area = ScrollArea(0.5, 0.23, 0.4, 0.57, lyrics)
1989+ self.lyrics_area.connect("activated", self._on_activated)
1990 self.add(self.lyrics_area)
1991 self.library.save_lyrics(self.track, lyrics_text)
1992
1993@@ -71,7 +73,7 @@
1994
1995 def _handle_up(self):
1996 '''Handle the up user event.'''
1997- if self.lyrics_area.get_offset() == 0:
1998+ if self.lyrics_area.on_top:
1999 return True # Move control back to tab bar
2000 else:
2001 return self.lyrics_area.scroll_up()
2002@@ -82,13 +84,15 @@
2003
2004 def _on_activated(self, event=None):
2005 '''Tab activated.'''
2006- self.lyrics_area.set_active(True)
2007- self.lyrics_area.set_opacity(255)
2008+ if self.tab_group is not None:
2009+ self.tab_group.active = False
2010+ self.lyrics_area.active = True
2011+ self.active = True
2012 return False
2013
2014 def _on_deactivated(self, event=None):
2015 '''Tab deactivated.'''
2016- self.lyrics_area.set_active(False)
2017- self.lyrics_area.set_opacity(128)
2018+ self.active = False
2019+ self.lyrics_area.active = False
2020 return False
2021
2022
2023=== modified file 'entertainerlib/frontend/gui/tabs/movies_tab.py'
2024--- entertainerlib/frontend/gui/tabs/movies_tab.py 2009-05-14 03:22:31 +0000
2025+++ entertainerlib/frontend/gui/tabs/movies_tab.py 2009-07-14 11:06:21 +0000
2026@@ -1,17 +1,14 @@
2027 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
2028 '''MoviesTab - MoviesTab is group of objects that are displayed on movie tab'''
2029
2030-import clutter
2031-import gtk
2032 import pango
2033
2034 from entertainerlib.frontend.gui.tabs.tab import Tab
2035 from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
2036-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
2037 from entertainerlib.frontend.gui.widgets.label import Label
2038 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2039-from entertainerlib.frontend.gui.widgets.rounded_texture import RoundedTexture
2040-from entertainerlib.frontend.gui.widgets.texture import Texture
2041+from entertainerlib.frontend.gui.widgets.loading_animation import (
2042+ LoadingAnimation)
2043
2044 class MoviesTab(Tab):
2045 """
2046@@ -24,6 +21,7 @@
2047 def __init__(self, video_library, move_to_new_screen_callback,
2048 name="movies", tab_title=_("Movies")):
2049 Tab.__init__(self, name, tab_title, move_to_new_screen_callback)
2050+
2051 self.video_library = video_library
2052 self.theme = self.config.theme
2053 self.list_indicator = None
2054@@ -35,7 +33,17 @@
2055 if self.video_library.get_number_of_movies() == 0:
2056 self._create_empty_library_notice()
2057 else:
2058- self._create_menu()
2059+ # Start the loading animation while the menu is loading
2060+ self.throbber = LoadingAnimation(0.1, 0.1)
2061+ self.throbber.show()
2062+ self.add(self.throbber)
2063+
2064+ self.menu = self._create_menu()
2065+ self.add(self.menu)
2066+ self.menu.connect("moved", self._update_movie_info)
2067+ self.menu.connect("selected", self._handle_select)
2068+ self.menu.connect("activated", self._on_activated)
2069+ self.menu.connect("filled", self._on_menu_filled)
2070
2071 self.connect('activated', self._on_activated)
2072 self.connect('deactivated', self._on_deactivated)
2073@@ -67,40 +75,19 @@
2074 the video library.
2075 """
2076 #Create movie menu
2077- self.menu = ImageMenu(self.theme)
2078- self.menu.set_item_size(self.get_abs_x(0.1), self.get_abs_y(0.25))
2079- self.menu.set_item_spacing(self.get_abs_y(0.025))
2080- self.menu.set_visible_column_count(2)
2081- self.menu.set_row_count(7)
2082- self.menu.set_orientation(self.menu.VERTICAL)
2083-
2084- # Default texture
2085- default_cover = Texture(self.theme.getImage("default_movie_art"))
2086+ menu = ImageMenu(0.06, 0.18, 0.12, 0.25)
2087+ menu.items_per_col = 2
2088+ menu.visible_rows = 2
2089+ menu.visible_cols = 7
2090
2091 movies = self.video_library.get_movies()
2092- for movie in movies:
2093- if movie.has_cover_art():
2094- pix_buffer = gtk.gdk.pixbuf_new_from_file(
2095- movie.get_cover_art_url())
2096- texture = RoundedTexture(0.0, 0.0, 0.1, 0.25, pix_buffer)
2097- else:
2098- texture = clutter.CloneTexture(default_cover)
2099- item = ImageMenuItem(0.1, 1.41, texture)
2100- item.set_userdata(movie)
2101- self.menu.add_actor(item)
2102-
2103- self.menu.set_position(self.get_abs_x(0.11), self.get_abs_y(0.2))
2104- self.menu.show()
2105-
2106- if self.config.show_effects():
2107- self.menu.set_animate(True)
2108-
2109- self.add(self.menu)
2110+ movies_list = [[movie.get_cover_art_url(), movie] for movie in movies]
2111+ menu.async_add_videos(movies_list)
2112
2113 # Create list indicator
2114 self.list_indicator = ListIndicator(0.75, 0.76, 0.2, 0.045,
2115- ListIndicator.VERTICAL)
2116- self.list_indicator.set_maximum(self.menu.get_number_of_items())
2117+ ListIndicator.HORIZONTAL)
2118+ self.list_indicator.set_maximum(len(movies))
2119 self.show()
2120 self.add(self.list_indicator)
2121
2122@@ -120,10 +107,12 @@
2123 self.movie_plot.width = 0.7
2124 self.add(self.movie_plot)
2125
2126- def _update_movie_info(self):
2127+ return menu
2128+
2129+ def _update_movie_info(self, event=None):
2130 '''Update the movie information labels.'''
2131 if self.active:
2132- movie = self.menu.get_current_menuitem().get_userdata()
2133+ movie = self.menu.selected_userdata
2134 genres = movie.get_genres()
2135 if len(genres) > 1:
2136 genre = genres[0] + "/" + genres[1]
2137@@ -135,55 +124,61 @@
2138 self.movie_info.set_text(_("%(min)d min, (%(genre)s)") % \
2139 {'min': movie.get_runtime(), 'genre': genre})
2140 self.movie_plot.set_text(movie.get_short_plot())
2141+ self.list_indicator.show()
2142+ self.list_indicator.set_current(self.menu.selected_index + 1)
2143 else:
2144 self.movie_title.set_text("")
2145 self.movie_info.set_text("")
2146 self.movie_plot.set_text("")
2147-
2148- def _update_menu(self, direction):
2149- '''Update the menu based on the provided menu direction.'''
2150- self.menu.move(direction)
2151- self._update_movie_info()
2152- self.list_indicator.set_current(self.menu.get_current_position() + 1)
2153- return False
2154+ self.list_indicator.hide()
2155
2156 def _handle_up(self):
2157 '''Handle the up user event.'''
2158- if self.menu.get_current_position() <= 6:
2159+ if self.menu.on_top:
2160 return True # Move control back to tab bar
2161 else:
2162- return self._update_menu(self.menu.UP)
2163+ self.menu.up()
2164+ return False
2165
2166 def _handle_down(self):
2167 '''Handle the down user event.'''
2168- return self._update_menu(self.menu.DOWN)
2169+ self.menu.down()
2170+ return False
2171
2172 def _handle_left(self):
2173 '''Handle the left user event.'''
2174- return self._update_menu(self.menu.LEFT)
2175+ self.menu.left()
2176+ return False
2177
2178 def _handle_right(self):
2179 '''Handle the right user event.'''
2180- return self._update_menu(self.menu.RIGHT)
2181+ self.menu.right()
2182+ return False
2183
2184- def _handle_select(self):
2185+ def _handle_select(self, event=None):
2186 '''Handle the select user event.'''
2187- movie = self.menu.get_current_menuitem().get_userdata()
2188+ movie = self.menu.selected_userdata
2189 kwargs = { 'movie' : movie }
2190 self.callback("movie", kwargs)
2191 return False
2192
2193 def _on_activated(self, event=None):
2194 '''Tab activated.'''
2195+ if self.tab_group is not None:
2196+ self.tab_group.active = False
2197+ self.menu.active = True
2198+ self.active = True
2199 self._update_movie_info()
2200- self.menu.set_active(True)
2201- self.menu.set_opacity(255)
2202 return False
2203
2204 def _on_deactivated(self, event=None):
2205 '''Tab deactivated.'''
2206+ self.active = False
2207+ self.menu.active = False
2208 self._update_movie_info()
2209- self.menu.set_active(False)
2210- self.menu.set_opacity(128)
2211 return False
2212
2213+ def _on_menu_filled(self, event=None):
2214+ '''Handles filled event.'''
2215+ self.throbber.hide()
2216+
2217
2218=== modified file 'entertainerlib/frontend/gui/tabs/series_tab.py'
2219--- entertainerlib/frontend/gui/tabs/series_tab.py 2009-05-14 03:22:31 +0000
2220+++ entertainerlib/frontend/gui/tabs/series_tab.py 2009-07-14 11:06:21 +0000
2221@@ -1,17 +1,14 @@
2222 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
2223 '''SeriesTab - Group of objects that are displayed on tv-series tab'''
2224
2225-import clutter
2226-import gtk
2227 import pango
2228
2229 from entertainerlib.frontend.gui.tabs.tab import Tab
2230 from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
2231-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
2232 from entertainerlib.frontend.gui.widgets.label import Label
2233 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2234-from entertainerlib.frontend.gui.widgets.rounded_texture import RoundedTexture
2235-from entertainerlib.frontend.gui.widgets.texture import Texture
2236+from entertainerlib.frontend.gui.widgets.loading_animation import (
2237+ LoadingAnimation)
2238
2239 class SeriesTab(Tab):
2240 """
2241@@ -24,6 +21,7 @@
2242 def __init__(self, video_library, move_to_new_screen_callback,
2243 name="series", tab_title=_("TV-Series")):
2244 Tab.__init__(self, name, tab_title, move_to_new_screen_callback)
2245+
2246 self.video_library = video_library
2247 self.theme = self.config.theme
2248 self.list_indicator = None
2249@@ -34,7 +32,17 @@
2250 if self.video_library.get_number_of_tv_series() == 0:
2251 self._create_empty_library_notice()
2252 else:
2253- self._create_menu()
2254+ # Start the loading animation while the menu is loading
2255+ self.throbber = LoadingAnimation(0.4, 0.1)
2256+ self.throbber.show()
2257+ self.add(self.throbber)
2258+
2259+ self.menu = self._create_menu()
2260+ self.add(self.menu)
2261+ self.menu.connect("moved", self._update_serie_info)
2262+ self.menu.connect("selected", self._handle_select)
2263+ self.menu.connect("activated", self._on_activated)
2264+ self.menu.connect("filled", self._on_menu_filled)
2265
2266 self.connect('activated', self._on_activated)
2267 self.connect('deactivated', self._on_deactivated)
2268@@ -66,42 +74,19 @@
2269 the video library.
2270 """
2271 #Create TV-series menu
2272- self.menu = ImageMenu(self.theme)
2273- self.menu.set_item_size(self.get_abs_x(0.1), self.get_abs_y(0.25))
2274- self.menu.set_item_spacing(self.get_abs_y(0.025))
2275- self.menu.set_visible_column_count(2)
2276- self.menu.set_row_count(7)
2277- self.menu.set_orientation(self.menu.VERTICAL)
2278-
2279- # Default texture
2280- default_cover = Texture(self.theme.getImage("default_movie_art"))
2281-
2282- all_series = self.video_library.get_tv_series()
2283- for series in all_series:
2284- if series.has_cover_art():
2285- pix_buffer = gtk.gdk.pixbuf_new_from_file(
2286- series.get_cover_art_url())
2287- texture = RoundedTexture(0.0, 0.0, 0.1, 0.25, pix_buffer)
2288- else:
2289- texture = clutter.CloneTexture(default_cover)
2290-
2291- item = ImageMenuItem(0.1, 1.41, texture)
2292- item.set_userdata(series)
2293- self.menu.add_actor(item)
2294-
2295- self.menu.set_position(self.get_abs_x(0.11), self.get_abs_y(0.2))
2296- self.menu.set_active(False)
2297- self.menu.show()
2298-
2299- if self.config.show_effects():
2300- self.menu.set_animate(True)
2301-
2302- self.add(self.menu)
2303+ menu = ImageMenu(0.06, 0.18, 0.12, 0.25)
2304+ menu.items_per_col = 2
2305+ menu.visible_rows = 2
2306+ menu.visible_cols = 7
2307+
2308+ series = self.video_library.get_tv_series()
2309+ series_list = [[serie.get_cover_art_url(), serie] for serie in series]
2310+ menu.async_add_videos(series_list)
2311
2312 # Create list indicator
2313 self.list_indicator = ListIndicator(0.75, 0.76, 0.2, 0.045,
2314- ListIndicator.VERTICAL)
2315- self.list_indicator.set_maximum(self.menu.get_number_of_items())
2316+ ListIndicator.HORIZONTAL)
2317+ self.list_indicator.set_maximum(len(series))
2318 self.list_indicator.show()
2319 self.add(self.list_indicator)
2320
2321@@ -115,64 +100,72 @@
2322 self.series_info = Label(0.034, "subtitle", 0.2, 0.82, "")
2323 self.add(self.series_info)
2324
2325- def _update_series_info(self):
2326+ return menu
2327+
2328+ def _update_serie_info(self, event=None):
2329 '''Update the series information labels.'''
2330 if self.active:
2331- series = self.menu.get_current_menuitem().get_userdata()
2332+ series = self.menu.selected_userdata
2333 info = _("%(season)d Seasons\n%(episode)d Episodes") % {'season':
2334 series.get_number_of_seasons(), 'episode':
2335 series.get_number_of_episodes()}
2336
2337 self.series_info.set_text(info)
2338 self.series_title.set_text(series.get_title())
2339+ self.list_indicator.show()
2340+ self.list_indicator.set_current(self.menu.selected_index + 1)
2341 else:
2342 self.series_info.set_text("")
2343 self.series_title.set_text("")
2344-
2345- def _update_menu(self, direction):
2346- '''Update the menu based on the provided menu direction.'''
2347- self.menu.move(direction)
2348- self._update_series_info()
2349- self.list_indicator.set_current(self.menu.get_current_position() + 1)
2350- return False
2351+ self.list_indicator.hide()
2352
2353 def _handle_up(self):
2354 '''Handle the up user event.'''
2355- if self.menu.get_current_position() <= 6:
2356+ if self.menu.on_top:
2357 return True # Move control back to tab bar
2358 else:
2359- return self._update_menu(self.menu.UP)
2360+ self.menu.up()
2361+ return False
2362
2363 def _handle_down(self):
2364 '''Handle the down user event.'''
2365- return self._update_menu(self.menu.DOWN)
2366+ self.menu.down()
2367+ return False
2368
2369 def _handle_left(self):
2370 '''Handle the left user event.'''
2371- return self._update_menu(self.menu.LEFT)
2372+ self.menu.left()
2373+ return False
2374
2375 def _handle_right(self):
2376 '''Handle the right user event.'''
2377- return self._update_menu(self.menu.RIGHT)
2378+ self.menu.right()
2379+ return False
2380
2381- def _handle_select(self):
2382+ def _handle_select(self, event=None):
2383 '''Handle the select user event.'''
2384- series = self.menu.get_current_menuitem().get_userdata()
2385+ series = self.menu.selected_userdata
2386 kwargs = { 'tv_series' : series }
2387 self.callback("tv_series", kwargs)
2388 return False
2389
2390 def _on_activated(self, event=None):
2391 '''Tab activated.'''
2392- self._update_series_info()
2393- self.menu.set_active(True)
2394- self.menu.set_opacity(255)
2395+ if self.tab_group is not None:
2396+ self.tab_group.active = False
2397+ self.menu.active = True
2398+ self.active = True
2399+ self._update_serie_info()
2400 return False
2401
2402 def _on_deactivated(self, event=None):
2403 '''Tab deactivated.'''
2404- self._update_series_info()
2405- self.menu.set_active(False)
2406- self.menu.set_opacity(128)
2407+ self.active = False
2408+ self.menu.active = False
2409+ self._update_serie_info()
2410 return False
2411
2412+ def _on_menu_filled(self, event=None):
2413+ '''Handles filled event.'''
2414+ self.throbber.hide()
2415+
2416
2417=== modified file 'entertainerlib/frontend/gui/tabs/tab.py'
2418--- entertainerlib/frontend/gui/tabs/tab.py 2009-05-12 17:18:10 +0000
2419+++ entertainerlib/frontend/gui/tabs/tab.py 2009-05-21 16:12:09 +0000
2420@@ -40,6 +40,7 @@
2421
2422 # show/hide animation on the Tab
2423 self.timeline = clutter.Timeline(30, 60)
2424+ self.timeline.connect('completed', self._on_timeline_completed)
2425 self.alpha = clutter.Alpha(self.timeline, clutter.smoothstep_inc_func)
2426 self.behaviour = clutter.BehaviourOpacity(0, 255, self.alpha)
2427 self.behaviour.apply(self)
2428@@ -48,6 +49,7 @@
2429 self._active = None
2430 self._visible = False
2431 self.set_opacity(0)
2432+ self.hide()
2433
2434 def _get_active(self):
2435 '''active property getter.'''
2436@@ -81,6 +83,7 @@
2437 self._visible = boolean
2438
2439 if boolean:
2440+ self.show()
2441 self.timeline.set_direction(clutter.TIMELINE_FORWARD)
2442 self.timeline.rewind()
2443 self.timeline.start()
2444@@ -90,6 +93,11 @@
2445
2446 visible = property(_get_visible, _set_visible)
2447
2448+ def _on_timeline_completed(self, timeline):
2449+ """Hides the Tab on the end of the fade out animation."""
2450+ if timeline.get_direction() == clutter.TIMELINE_BACKWARD:
2451+ self.hide()
2452+
2453 def can_activate(self):
2454 '''
2455 Returns a boolean whether or not this tab can be activated. If tab
2456@@ -135,7 +143,7 @@
2457 '''Dummy method for unimplemented handlers in subclass.'''
2458 return False
2459
2460- def _handle_select(self):
2461+ def _handle_select(self, event=None):
2462 '''Dummy method for unimplemented handlers in subclass.'''
2463 return False
2464
2465
2466=== modified file 'entertainerlib/frontend/gui/tabs/tracks_tab.py'
2467--- entertainerlib/frontend/gui/tabs/tracks_tab.py 2009-05-14 03:22:31 +0000
2468+++ entertainerlib/frontend/gui/tabs/tracks_tab.py 2009-07-16 20:31:22 +0000
2469@@ -1,14 +1,14 @@
2470 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
2471 '''Tracks tab for the artist screen'''
2472
2473-import clutter
2474 import pango
2475
2476 from entertainerlib.frontend.gui.tabs.tab import Tab
2477 from entertainerlib.frontend.gui.widgets.label import Label
2478 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2479+from entertainerlib.frontend.gui.widgets.loading_animation import (
2480+ LoadingAnimation)
2481 from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2482-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2483
2484 class TracksTab(Tab):
2485 '''Tab for the artist screen to show track listings'''
2486@@ -17,31 +17,22 @@
2487 tab_title=_("Tracks")):
2488 Tab.__init__(self, name, tab_title, move_to_new_screen_callback)
2489
2490- self.menu = TextMenu(self.theme, self.config.show_effects())
2491- self.menu.set_item_size(self.get_abs_x(0.2928), self.get_abs_y(0.0781))
2492-
2493- cursor = clutter.Rectangle()
2494- cursor.set_color((0, 0, 0, 0))
2495- self.menu.set_cursor(cursor)
2496-
2497- for track in tracks:
2498- item = TextMenuItem(0.2928, 0.104, track.get_title())
2499- item.justified = True
2500- item.set_userdata(track)
2501- self.menu.add_actor(item)
2502-
2503- self.menu.set_orientation(TextMenu.HORIZONTAL)
2504- self.menu.set_row_count(7)
2505- self.menu.set_visible_column_count(3)
2506- self.menu.set_position(self.get_abs_x(0.0586), self.get_abs_y(0.2083))
2507- self.menu.set_active(False)
2508- self.menu.show()
2509-
2510- if self.config.show_effects():
2511- self.menu.set_animate(True)
2512-
2513+ # Start the loading animation while the menu is loading
2514+ self.throbber = LoadingAnimation(0.1, 0.1)
2515+ self.throbber.show()
2516+ self.add(self.throbber)
2517+
2518+ self.menu = TextMenu(0.0586, 0.2083, 0.2928, 0.0781)
2519+ self.menu.items_per_row = 3
2520+ self.menu.visible_rows = 7
2521+ self.menu.visible_cols = 3
2522+ self.menu.active = False
2523+ self.menu.cursor = None
2524 self.add(self.menu)
2525
2526+ tracks_list = [[track.get_title(), None, track] for track in tracks]
2527+ self.menu.async_add_artists(tracks_list)
2528+
2529 self.track_title = Label(0.045, "title", 0.22, 0.79, "")
2530 self.track_title.set_ellipsize(pango.ELLIPSIZE_END)
2531 self.track_title.width = 0.366
2532@@ -56,78 +47,88 @@
2533 self.add(self.track_length)
2534
2535 self.li = ListIndicator(0.77, 0.8, 0.18, 0.045,
2536- ListIndicator.HORIZONTAL)
2537- self.li.set_maximum(self.menu.get_number_of_items())
2538+ ListIndicator.VERTICAL)
2539+ self.li.set_maximum(len(tracks))
2540 self.li.show()
2541 self.add(self.li)
2542
2543 self.connect('activated', self._on_activated)
2544 self.connect('deactivated', self._on_deactivated)
2545+ self.menu.connect("moved", self._update_track_info)
2546+ self.menu.connect("selected", self._handle_select)
2547+ self.menu.connect("activated", self._on_activated)
2548+ self.menu.connect("filled", self._on_menu_filled)
2549
2550 def can_activate(self):
2551 '''Tracks tab will always be created from an existing artist with at
2552 least one track.'''
2553 return True
2554
2555- def _update_track_info(self):
2556+ def _update_track_info(self, event=None):
2557 '''Update the track information labels'''
2558 if self.active:
2559- track = self.menu.get_current_menuitem().get_userdata()
2560+ track = self.menu.selected_userdata
2561 self.track_title.set_text(track.get_title())
2562 self.track_length.set_text(str(track.get_length() / 60) + ":" + \
2563 str(track.get_length() % 60).zfill(2))
2564 self.track_number.set_text(_("Track %(track)s from %(album)s") % \
2565 {'track': track.get_tracknumber(),
2566 'album': track.get_album().get_title()})
2567+ self.li.show()
2568+ self.li.set_current(self.menu.selected_index + 1)
2569 else:
2570 self.track_title.set_text("")
2571 self.track_length.set_text("")
2572 self.track_number.set_text("")
2573-
2574- def _update_menu(self, direction):
2575- '''Update the menu based on the provided menu direction.'''
2576- self.menu.move(direction)
2577- self._update_track_info()
2578- self.li.set_current(self.menu.get_current_position() + 1)
2579- return False
2580+ self.li.hide()
2581
2582 def _handle_up(self):
2583 '''Handle the up user event.'''
2584- if self.menu.get_cursor_position()[1] == 0:
2585+ if self.menu.on_top:
2586 return True # Move control back to tab bar
2587 else:
2588- return self._update_menu(self.menu.UP)
2589+ self.menu.up()
2590+ return False
2591
2592 def _handle_down(self):
2593 '''Handle the down user event.'''
2594- return self._update_menu(self.menu.DOWN)
2595+ self.menu.down()
2596+ return False
2597
2598 def _handle_left(self):
2599 '''Handle the left user event.'''
2600- return self._update_menu(self.menu.LEFT)
2601+ self.menu.left()
2602+ return False
2603
2604 def _handle_right(self):
2605 '''Handle the right user event.'''
2606- return self._update_menu(self.menu.RIGHT)
2607+ self.menu.right()
2608+ return False
2609
2610- def _handle_select(self):
2611+ def _handle_select(self, event=None):
2612 '''Handle the select user event.'''
2613- track = self.menu.get_current_menuitem().get_userdata()
2614+ track = self.menu.selected_userdata
2615 kwargs = { 'track' : track }
2616 self.callback("audio_play", kwargs)
2617 return False
2618
2619 def _on_activated(self, event=None):
2620 '''Tab activated.'''
2621+ if self.tab_group is not None:
2622+ self.tab_group.active = False
2623+ self.menu.active = True
2624+ self.active = True
2625 self._update_track_info()
2626- self.menu.set_active(True)
2627- self.menu.set_opacity(255)
2628 return False
2629
2630 def _on_deactivated(self, event=None):
2631 '''Tab deactivated.'''
2632+ self.active = False
2633+ self.menu.active = False
2634 self._update_track_info()
2635- self.menu.set_active(False)
2636- self.menu.set_opacity(128)
2637 return False
2638
2639+ def _on_menu_filled(self, event=None):
2640+ '''Handles filled event.'''
2641+ self.throbber.hide()
2642+
2643
2644=== modified file 'entertainerlib/frontend/gui/tabs/video_clips_tab.py'
2645--- entertainerlib/frontend/gui/tabs/video_clips_tab.py 2009-05-14 03:22:31 +0000
2646+++ entertainerlib/frontend/gui/tabs/video_clips_tab.py 2009-07-14 11:06:21 +0000
2647@@ -3,15 +3,14 @@
2648
2649 import os
2650
2651-import gtk
2652 import pango
2653
2654 from entertainerlib.frontend.gui.tabs.tab import Tab
2655 from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
2656-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
2657 from entertainerlib.frontend.gui.widgets.label import Label
2658 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2659-from entertainerlib.frontend.gui.widgets.rounded_texture import RoundedTexture
2660+from entertainerlib.frontend.gui.widgets.loading_animation import (
2661+ LoadingAnimation)
2662
2663 class VideoClipsTab(Tab):
2664 """
2665@@ -35,7 +34,17 @@
2666 if self.video_library.get_number_of_video_clips() == 0:
2667 self._create_empty_library_notice()
2668 else:
2669- self._create_menu()
2670+ # Start the loading animation while the menu is loading
2671+ self.throbber = LoadingAnimation(0.7, 0.1)
2672+ self.throbber.show()
2673+ self.add(self.throbber)
2674+
2675+ self.menu = self._create_menu()
2676+ self.add(self.menu)
2677+ self.menu.connect("moved", self._update_clip_info)
2678+ self.menu.connect("selected", self._handle_select)
2679+ self.menu.connect("activated", self._on_activated)
2680+ self.menu.connect("filled", self._on_menu_filled)
2681
2682 self.connect('activated', self._on_activated)
2683 self.connect('deactivated', self._on_deactivated)
2684@@ -65,33 +74,19 @@
2685 Create a view that is displayed when there are indexed clips in
2686 the video library.
2687 """
2688- self.menu = ImageMenu(self.theme)
2689- self.menu.set_item_size(self.get_abs_x(0.233), self.get_abs_y(0.25))
2690- self.menu.set_item_spacing(self.get_abs_y(0.025))
2691- self.menu.set_visible_column_count(2)
2692- self.menu.set_row_count(3)
2693- self.menu.set_orientation(self.menu.VERTICAL)
2694+ menu = ImageMenu(0.04, 0.16, 0.23, self.y_for_x(0.23) * 0.7)
2695+ menu.items_per_col = 2
2696+ menu.visible_rows = 2
2697+ menu.visible_cols = 4
2698
2699 clips = self.video_library.get_video_clips()
2700- for clip in clips:
2701- pix_buffer = gtk.gdk.pixbuf_new_from_file(clip.get_thumbnail_url())
2702- texture = RoundedTexture(0.0, 0.0, 0.233, 0.25, pix_buffer)
2703- item = ImageMenuItem(0.233, 0.5625, texture)
2704- item.set_userdata(clip)
2705- self.menu.add_actor(item)
2706-
2707- self.menu.set_position(self.get_abs_x(0.15), self.get_abs_y(0.2))
2708- self.menu.set_active(False)
2709-
2710- if self.config.show_effects():
2711- self.menu.set_animate(True)
2712-
2713- self.add(self.menu)
2714+ clips_list = [[clip.get_thumbnail_url(), clip] for clip in clips]
2715+ menu.async_add_clips(clips_list)
2716
2717 # Create list indicator
2718 self.list_indicator = ListIndicator(0.7, 0.8, 0.2, 0.045,
2719- ListIndicator.VERTICAL)
2720- self.list_indicator.set_maximum(self.menu.get_number_of_items())
2721+ ListIndicator.HORIZONTAL)
2722+ self.list_indicator.set_maximum(len(clips))
2723 self.list_indicator.show()
2724 self.add(self.list_indicator)
2725
2726@@ -107,46 +102,48 @@
2727 self.clip_info.width = 0.5
2728 self.add(self.clip_info)
2729
2730- def _update_clip_info(self):
2731+ return menu
2732+
2733+ def _update_clip_info(self, event=None):
2734 '''Update the VideoClip information labels.'''
2735 if self.active:
2736- clip = self.menu.get_current_menuitem().get_userdata()
2737+ clip = self.menu.selected_userdata
2738 (folder, filename) = os.path.split(clip.get_filename())
2739 self.clip_title.set_text(filename)
2740 self.clip_info.set_text(folder)
2741+ self.list_indicator.show()
2742+ self.list_indicator.set_current(self.menu.selected_index + 1)
2743 else:
2744 self.clip_title.set_text("")
2745 self.clip_info.set_text("")
2746-
2747- def _update_menu(self, direction):
2748- '''Update the menu based on the provided menu direction.'''
2749- self.menu.move(direction)
2750- self._update_clip_info()
2751- self.list_indicator.set_current(self.menu.get_current_position() + 1)
2752- return False
2753+ self.list_indicator.hide()
2754
2755 def _handle_up(self):
2756 '''Handle the up user event.'''
2757- if self.menu.get_current_position() <= 2:
2758+ if self.menu.on_top:
2759 return True # Move control back to tab bar
2760 else:
2761- return self._update_menu(self.menu.UP)
2762+ self.menu.up()
2763+ return False
2764
2765 def _handle_down(self):
2766 '''Handle the down user event.'''
2767- return self._update_menu(self.menu.DOWN)
2768+ self.menu.down()
2769+ return False
2770
2771 def _handle_left(self):
2772 '''Handle the left user event.'''
2773- return self._update_menu(self.menu.LEFT)
2774+ self.menu.left()
2775+ return False
2776
2777 def _handle_right(self):
2778 '''Handle the right user event.'''
2779- return self._update_menu(self.menu.RIGHT)
2780+ self.menu.right()
2781+ return False
2782
2783- def _handle_select(self):
2784+ def _handle_select(self, event=None):
2785 '''Handle the select user event.'''
2786- clip = self.menu.get_current_menuitem().get_userdata()
2787+ clip = self.menu.selected_userdata
2788 self.media_player.set_media(clip)
2789 self.media_player.play()
2790 self.callback("video_osd")
2791@@ -154,15 +151,21 @@
2792
2793 def _on_activated(self, event=None):
2794 '''Tab activated.'''
2795+ if self.tab_group is not None:
2796+ self.tab_group.active = False
2797+ self.menu.active = True
2798+ self.active = True
2799 self._update_clip_info()
2800- self.menu.set_active(True)
2801- self.menu.set_opacity(255)
2802 return False
2803
2804 def _on_deactivated(self, event=None):
2805 '''Tab deactivated.'''
2806+ self.active = False
2807+ self.menu.active = False
2808 self._update_clip_info()
2809- self.menu.set_active(False)
2810- self.menu.set_opacity(128)
2811 return False
2812
2813+ def _on_menu_filled(self, event=None):
2814+ '''Handles filled event.'''
2815+ self.throbber.hide()
2816+
2817
2818=== modified file 'entertainerlib/frontend/gui/widgets/base.py'
2819--- entertainerlib/frontend/gui/widgets/base.py 2009-04-28 22:30:06 +0000
2820+++ entertainerlib/frontend/gui/widgets/base.py 2009-07-15 21:22:17 +0000
2821@@ -21,3 +21,12 @@
2822 result = int(self.config.get_stage_height() * percentage)
2823 return result
2824
2825+ def y_for_x(self, percentage):
2826+ """
2827+ Returns the percentage of stage height giving the same number of pixels
2828+ equivalent to `percentage` applied to the stage width.
2829+ """
2830+ stage_ratio = float(self.config.get_stage_width())
2831+ stage_ratio /= self.config.get_stage_height()
2832+ return stage_ratio * percentage
2833+
2834
2835=== modified file 'entertainerlib/frontend/gui/widgets/grid_menu.py'
2836--- entertainerlib/frontend/gui/widgets/grid_menu.py 2009-05-10 06:24:43 +0000
2837+++ entertainerlib/frontend/gui/widgets/grid_menu.py 2009-07-19 19:27:31 +0000
2838@@ -1,672 +1,591 @@
2839 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
2840-'''GridMenu contains a grid of textures and allows user to select one'''
2841+'''GridMenu contains a grid of MenuItem based widgets.'''
2842+
2843+import math
2844
2845 import clutter
2846-
2847-class GridMenu(clutter.Group):
2848- """
2849- GridMenu widget
2850-
2851- This is a flexible menu widget that supports horizontal, vertical,
2852- scrollable, multirow menus. Real menus should inherit this class
2853- and override few methods.
2854- """
2855-
2856- # Orientation constants
2857- HORIZONTAL = 0
2858- VERTICAL = 1
2859-
2860- # Direction constants
2861- UP = 2
2862- DOWN = 3
2863- LEFT = 4
2864- RIGHT = 5
2865-
2866- # Cursor placement (Is cursor drawn above or below of all menuitems)
2867- BELOW = 6
2868- ABOVE = 7
2869-
2870- def __init__(self, orientation = 1): # Default orientation is VERTICAL
2871- """
2872- Initialize GridMenu widget
2873- @param orientation: Orientation of the menu. Use class constatants.
2874- """
2875+import gobject
2876+
2877+from entertainerlib.frontend.gui.widgets.base import Base
2878+from entertainerlib.frontend.gui.widgets.motion_buffer import MotionBuffer
2879+
2880+class GridMenu(Base, clutter.Group):
2881+ """
2882+ GridMenu widget.
2883+
2884+ A core widget to handle MenuItem in a grid with a cursor.
2885+ This widget provides all the necessary logic to move items and the cursor.
2886+ """
2887+ __gsignals__ = {
2888+ 'activated' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
2889+ 'moved' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
2890+ 'selected' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
2891+ }
2892+
2893+ MODE_NONE = 0
2894+ MODE_SELECT = 1
2895+ MODE_SEEK = 2
2896+
2897+ def __init__(self, x=0, y=0, item_width=0.2, item_height=0.1):
2898+ Base.__init__(self)
2899 clutter.Group.__init__(self)
2900
2901- self.itemgroup = clutter.Group() # Group for menuitems
2902- self.items = [] # List of Menuitems
2903- self.orientation = orientation # Menu orientation
2904- self.row_count = 1 # Items per row
2905- self.visible_cols = 8 # Number of visible columns
2906- self.cell_height = 100 # Menuitem height
2907- self.cell_width = 100 # Menuitem width
2908- self.cell_spacing = 0 # Space between items
2909- self.logical_position = 0 # Logical position (in item list)
2910- self.cursor_position = (0, 0) # Cursor position (on screen)
2911- self.content_position = (0, 0) # Content position
2912- self.animate = False # Should we animate this widget
2913- self.cursor = None # Cursor (clutter.Actor)
2914- self.cursor_placement = 7 # Cursor placement (Default: Above)
2915- self.active = True # Is this menu active
2916- self.content_timeline = None # Content scroll timeline
2917- self.cursor_timeline = None # Cursor move timeline
2918-
2919- self.add(self.itemgroup)
2920-
2921- # Default red transparent cursor
2922- c = clutter.Rectangle()
2923- c.set_color((255, 0, 0, 128))
2924- self.set_cursor(c)
2925-
2926- def set_cursor(self, cursor):
2927- """
2928- Set cursor actor. This is usually a Texture or clutter.Rectangle.
2929- @param cursor: clutter.Actor - Cursor graphics
2930- """
2931- # Remove old if exists
2932- if self.cursor is not None:
2933- self.remove(self.cursor)
2934-
2935- # Set new cursor
2936- self.cursor = cursor
2937-
2938- # If widget is currently active display cursor
2939- if self.active:
2940- self.cursor.show()
2941-
2942- self.add(self.cursor)
2943- self.cursor.set_size(self.cell_width, self.cell_height)
2944- self._scale_cursor()
2945-
2946- # Set cursor placement
2947- if self.cursor_placement == self.BELOW:
2948- self.cursor.lower_bottom()
2949- elif self.cursor_placement == self.ABOVE:
2950- self.cursor.raise_top()
2951-
2952- def set_cursor_placement(self, placement):
2953- """
2954- Set cursor placement. Is cursor drawn below or above of all menuitems.
2955- @param placement: GridMenu.BELOW or GridMenu.ABOVE
2956- """
2957- self.cursor_placement = placement
2958- if self.cursor_placement == self.BELOW:
2959- self.cursor.lower_bottom()
2960- elif self.cursor_placement == self.ABOVE:
2961- self.cursor.raise_top()
2962-
2963- def set_active(self, boolean):
2964- """
2965- Set menu widget to active / disabled. Please see API references of
2966- the self._animate_items_on_state_change() method. It handels all actor
2967- changes when menu widget state changes.
2968- """
2969- self.active = boolean
2970- self._animate_items_on_state_change()
2971-
2972- def is_active(self):
2973- """
2974- Is this menu active?
2975- @return: True if menu is active, otherwise False
2976- """
2977- return self.active
2978-
2979- def set_animate(self, boolean):
2980- """
2981- Enable/disable widget animation.
2982- @param boolean: True if widget should be animated, otherwise False
2983- """
2984- self.animate = boolean
2985-
2986- def add_actor(self, item):
2987- """
2988- Add menuitem to this menu.
2989- @param item: clutter.Actor object
2990- """
2991- #item.set_size(self.cell_width, self.cell_height)
2992- self._scale_menuitem(item) # Scale item
2993- self.items.append(item) # Add to logical list
2994- self.itemgroup.add(item) # Add to menuitem group (visible list)
2995- self._update_menuitem_position(item, len(self.items) - 1)
2996-
2997- # Update cursor
2998- if self.cursor_placement == self.BELOW:
2999- self.cursor.lower_bottom()
3000- elif self.cursor_placement == self.ABOVE:
3001- self.cursor.raise_top()
3002-
3003- # If this is a first item in the menu.
3004- if len(self.items) == 1:
3005- self._update_selected_item(0)
3006- self.set_visible_column_count(self.visible_cols)
3007-
3008- def remove_actor(self, item):
3009- """
3010- Remove menuitem from this menu.
3011- @param item: clutter.Actor object
3012- """
3013- try:
3014- self.items.remove(item) # Remove from logical list
3015- self.itemgroup.remove(item) # Remove from visible list group
3016- for i in range(len(self.items)):
3017- self._update_menuitem_position(self.items[i], i)
3018- except:
3019- raise Exception("Couldn't remove menuitem. Item doesn't exist.")
3020-
3021- def remove_all(self):
3022- """
3023- Remove all menuitems from this menu.
3024- """
3025+ self.motion_duration = 100 # Default duration of animations ms.
3026+ self.cursor_below = True # Is the cursor below items?
3027+ self._active = None
3028+ self._is_vertical = True
3029 self.items = []
3030- self.itemgroup.remove_all()
3031-
3032- def select(self, logical_position):
3033- '''Selects the item at the passed logical_position.'''
3034- # pylint: disable-msg=W0612
3035- target = ( int(logical_position / self.row_count),
3036- int(logical_position % self.row_count))
3037-
3038- move_y = self.cursor_position[0] - target[0]
3039- move_x = self.cursor_position[1] - target[1]
3040-
3041- #Move Vertically
3042- if move_y > 0:
3043- for i in xrange(move_y):
3044- self._move_cursor(self.UP)
3045- else:
3046- for i in xrange(-move_y):
3047- self._move_cursor(self.DOWN)
3048-
3049- #Move Horizontally
3050- if move_x > 0:
3051- for i in xrange(move_x):
3052- self._move_cursor(self.RIGHT)
3053- else:
3054- for i in xrange(-move_x):
3055- self._move_cursor(self.LEFT)
3056-
3057- def get_n_children(self):
3058- """
3059- Override clutter.Group method. Returns number of menuitems.
3060- """
3061- return len(self.items)
3062-
3063- def get_nth_child(self, index):
3064- """
3065- Override clutter.Group method. Returns one menuitem.
3066- """
3067- return self.items[index]
3068-
3069- def get_number_of_items(self):
3070- """
3071- Get number of menuitems.
3072- @return: Integer
3073- """
3074- return len(self.items)
3075-
3076- def get_current_position(self):
3077- """
3078- Get logical position of the menu cursor.
3079- """
3080- return self.logical_position
3081-
3082- def get_current_menuitem(self):
3083- """
3084- Get current menuitem. (Menuitem under menu cursor)
3085- @return: clutter.Actor
3086- """
3087- return self.items[self.logical_position]
3088-
3089- def get_cursor_position(self):
3090- """
3091- Get cursor position. This is a (x,y) coordinate of the cursor. Cursor
3092- position is always in displayble area.
3093- @return: (x,y) tuple
3094- """
3095- return self.cursor_position
3096-
3097- def set_orientation(self, orientation):
3098- """
3099- Set widget orientation. This defines which way widget is scrolled when
3100- all items don't fit into the widget area at the same time.
3101- @param orientation: GridMenu.HORIZONTAL or GridMenu.VERTICAL
3102- """
3103- if orientation < 0 or orientation > 1:
3104- raise Exception("Invalid orientation")
3105- self.orientation = orientation
3106- for i in range(len(self.items)):
3107- self._update_menuitem_position(self.items[i], i)
3108-
3109- def get_orientation(self):
3110- """
3111- Get menu orientation.
3112- @return: GridMenu.VERTICAL or GridMenu.HORIZONTAL (Integer constant)
3113- """
3114- return self.orientation
3115-
3116- def set_row_count(self, count):
3117- """
3118- Set how many items there should be on one row. Notice that row's
3119- direction depends on widget's orientation. For example, 'normal menu'
3120- would be VERTICAL with a row count 1.
3121- @param count: Number of items per row
3122- """
3123- if count < 1:
3124- raise Exception("Row count must be a positive value.")
3125- self.row_count = count
3126- for i in range(len(self.items)):
3127- self._update_menuitem_position(self.items[i], i)
3128-
3129- def get_row_count(self):
3130- """
3131- Get row count.
3132- @return: Integer
3133- """
3134- return self.row_count
3135-
3136- def set_visible_column_count(self, count):
3137- """
3138- Set how many columns are allowed to be visible at the same time. Column
3139- direction depends on widget orientation. For example, if menu is
3140- VERTICAL, row_count is 1 and visible columns is 8, then there are at
3141- most 8 visible items which are scrolled vertically.
3142- """
3143- if count < 1:
3144- raise Exception("Only positive values allowed!")
3145- self.visible_cols = count
3146-
3147- if self.orientation == self.VERTICAL:
3148- w = (self.cell_width + self.cell_spacing) * self.row_count
3149- h = count * (self.cell_height + self.cell_spacing)
3150- else:
3151- w = count * (self.cell_width + self.cell_spacing)
3152- h = (self.cell_height + self.cell_spacing) * self.row_count
3153-
3154- self.remove_clip()
3155- #print "clip area: (width: " + str(w) + " and height: " + str(h)
3156- self.set_clip(0, 0, w, h)
3157-
3158- def get_visible_column_count(self):
3159- """
3160- Get the number of visible columns.
3161- @return: Integer
3162- """
3163- return self.visible_cols
3164-
3165- def set_item_spacing(self, space):
3166- """
3167- Set how many pixels should be left between menu items.
3168- @param space: Item spacing in pixels (0 or higher)
3169- """
3170- if space < 0:
3171- raise Exception("Spacing must be a positive integer.")
3172- self.cell_spacing = space
3173- for i in range(len(self.items)):
3174- self._update_menuitem_position(self.items[i], i)
3175-
3176- def set_item_height(self, height):
3177- """
3178- Set the height of the one cell in the grid.
3179- @param height: Height in pixels (positive value)
3180- """
3181-
3182- if height < 1:
3183- raise Exception("Height must be a positive value")
3184-
3185- self.cell_height = height
3186-
3187- # Update cursor
3188- self._scale_cursor()
3189-
3190- # Update menuitems
3191- for item in self.items:
3192- self._scale_menuitem(item)
3193- for i in range(len(self.items)):
3194- self._update_menuitem_position(self.items[i], i)
3195-
3196- def set_item_width(self, width):
3197- """
3198- Set the width of the one cell in the grid.
3199- @param height: Width in pixels (positive value)
3200- """
3201- if width < 1:
3202- raise Exception("Width must be a positive value")
3203-
3204- self.cell_width = width
3205-
3206- # Update cursor
3207- self._scale_cursor()
3208-
3209- # Update menuitems
3210- for item in self.items:
3211- self._scale_menuitem(item)
3212- for i in range(len(self.items)):
3213- self._update_menuitem_position(self.items[i], i)
3214-
3215- def set_item_size(self, width, height):
3216- """
3217- Set the width of the one cell in the grid.
3218- @param height: Width in pixels (positive value)
3219- """
3220- if width < 1 or height < 1 :
3221- raise Exception("Width and height must be positive values!")
3222-
3223- self.cell_width = width
3224- self.cell_height = height
3225-
3226- # Update clipping
3227- #self.set_visible_column_count(self.visible_cols)
3228-
3229- # Update cursor
3230- self._scale_cursor()
3231-
3232- # Update menuitems
3233- for item in self.items:
3234- self._scale_menuitem(item)
3235- for i in range(len(self.items)):
3236- self._update_menuitem_position(self.items[i], i)
3237-
3238- def move(self, direction):
3239- """
3240- Move to given direction. Cursor is moved or grid content is scrolled if
3241- needed.
3242- @param direction: GridMenu class variables (UP, DOWN, LEFT, RIGHT)
3243- """
3244- if not self._is_move_legal(direction):
3245- return
3246-
3247- # Current position of the content and cursor
3248- cur_x, cur_y = self.cursor_position
3249- con_x, con_y = self.content_position
3250-
3251- # Define limit value for cursor and content move
3252- max_row = len(self.items) / self.row_count
3253- if (len(self.items) % self.row_count) != 0:
3254- max_row = max_row + 1
3255-
3256- content_scroll_max = max_row - self.visible_cols
3257-
3258- if self.visible_cols > max_row:
3259- # There are less rows than allowed to be visible at the same time
3260- cursor_scroll_max = max_row - 1
3261- else:
3262- cursor_scroll_max = self.visible_cols - 1
3263-
3264- # Menu has a vertical orientation
3265- if self.orientation == self.VERTICAL:
3266- if direction == self.UP:
3267- if cur_y == 0:
3268- if con_y > 0:
3269- self._move_content(1, self.DOWN)
3270- else:
3271- self._move_cursor(self.UP)
3272- elif direction == self.DOWN:
3273- if cur_y < cursor_scroll_max:
3274- self._move_cursor(self.DOWN)
3275- else:
3276- if con_y < content_scroll_max:
3277- self._move_content(1, self.UP)
3278- elif direction == self.LEFT:
3279- if cur_x > 0:
3280- self._move_cursor(self.LEFT)
3281- elif direction == self.RIGHT:
3282- if cur_x < self.row_count - 1:
3283- self._move_cursor(self.RIGHT)
3284-
3285- # Menu has a horizontal orientation
3286- else:
3287- if direction == self.UP:
3288- if cur_y > 0:
3289- self._move_cursor(self.UP)
3290- elif direction == self.DOWN:
3291- if cur_y < self.row_count - 1:
3292- self._move_cursor(self.DOWN)
3293- elif direction == self.LEFT:
3294- if cur_x == 0:
3295- if con_x > 0:
3296- self._move_content(1, self.RIGHT)
3297- else:
3298- self._move_cursor(self.LEFT)
3299- elif direction == self.RIGHT:
3300- if cur_x < cursor_scroll_max:
3301- self._move_cursor(self.RIGHT)
3302- else:
3303- if con_x < content_scroll_max:
3304- self._move_content(1, self.LEFT)
3305-
3306- old_position = self.logical_position
3307-
3308- # Calculate logical position
3309- if self.orientation == self.VERTICAL:
3310- tmp = (self.cursor_position[1] +
3311- self.content_position[1]) * self.row_count
3312- self.logical_position = tmp + self.cursor_position[0]
3313- else:
3314- tmp = (self.cursor_position[0] +
3315- self.content_position[0]) * self.row_count
3316- self.logical_position = tmp + self.cursor_position[1]
3317-
3318- self._update_selected_item(self.logical_position, old_position)
3319-
3320- def _is_move_legal(self, direction):
3321- """
3322- Check if move is legal. This prevents cursor from leaving menu items.
3323- This could happen when grid's last row is not "full".
3324- @param direction: GridMenu class variables (UP, DOWN, LEFT, RIGHT)
3325- @return: True if move can be executed, otherwise False
3326- """
3327- # If last row is "full" we can't go wrong
3328- if len(self.items) % self.row_count == 0:
3329- return True
3330-
3331- # -1 because rows start from 0
3332- last_full_row = len(self.items) / self.row_count -1
3333- items_on_last_row = len(self.items) % self.row_count
3334-
3335- # Checking for vertical menu
3336- if self.orientation == self.VERTICAL:
3337- current_row = self.cursor_position[1] + self.content_position[1]
3338- if current_row == last_full_row and direction == self.DOWN:
3339- if self.cursor_position[0] + 1 > items_on_last_row:
3340- return False
3341- if current_row == last_full_row + 1 and direction == self.RIGHT:
3342- if self.logical_position == len(self.items) - 1:
3343- return False
3344- # Checking for horizontal menu
3345- else:
3346- current_row = self.cursor_position[0] + self.content_position[0]
3347- if current_row == last_full_row and direction == self.RIGHT:
3348- if self.cursor_position[1] + 1 > items_on_last_row:
3349- return False
3350- if current_row == last_full_row + 1 and direction == self.DOWN:
3351- if self.logical_position == len(self.items) - 1:
3352- return False
3353- return True
3354-
3355- def _move_cursor(self, direction):
3356- """
3357- Move cursor on grid.
3358- """
3359- # Change logical position
3360- x, y = self.cursor_position
3361- if direction == self.UP:
3362- self.cursor_position = (x, y-1)
3363- elif direction == self.DOWN:
3364- self.cursor_position = (x, y+1)
3365- elif direction == self.LEFT:
3366- self.cursor_position = (x-1, y)
3367- elif direction == self.RIGHT:
3368- self.cursor_position = (x+1, y)
3369-
3370- # Change cursor texture position
3371- x_offset = self.cell_width + self.cell_spacing
3372- y_offset = self.cell_height + self.cell_spacing
3373-
3374- # BELOW IS ANIMATION CODE FOR MENU CURSOR. SHOULD CURSOR BE ANIMATED?
3375-# if self.animate:
3376-# # Finish previous animation before new
3377-# if self.cursor_timeline is not None and (
3378-# self.cursor_timeline.is_playing():
3379-# self.cursor_timeline.pause()
3380-# self.cursor_timeline.advance(4)
3381-#
3382-# self.cursor_timeline = clutter.Timeline(4, 26)
3383-# alpha = clutter.Alpha(self.cursor_timeline, clutter.ramp_inc_func)
3384-#
3385-# x = self.cursor.get_x()
3386-# y = self.cursor.get_y()
3387-# if direction == self.UP:
3388-# behaviour = clutter.BehaviourPath(alpha,
3389-# ((x,y),
3390-# (x,y - y_offset)))
3391-# elif direction == self.DOWN:
3392-# behaviour = clutter.BehaviourPath(alpha,
3393-# ((x,y),
3394-# (x,y + y_offset)))
3395-# elif direction == self.RIGHT:
3396-# behaviour = clutter.BehaviourPath(alpha,
3397-# ((x,y),
3398-# (x + x_offset,y)))
3399-# elif direction == self.LEFT:
3400-# behaviour = clutter.BehaviourPath(alpha,
3401-# ((x,y),
3402-# (x - x_offset,y)))
3403-# behaviour.apply(self.cursor)
3404-# self.cursor_timeline.start()
3405-# else:
3406- if direction == self.UP:
3407- self.cursor.move_by(0, -y_offset)
3408- elif direction == self.DOWN:
3409- self.cursor.move_by(0, y_offset)
3410- elif direction == self.LEFT:
3411- self.cursor.move_by(-x_offset, 0)
3412- elif direction == self.RIGHT:
3413- self.cursor.move_by(x_offset, 0)
3414-
3415- def _move_content(self, count, direction):
3416- """
3417- Move menuitems to given direction.
3418- @param count: How much we scroll (number of menuitems)
3419- @param direction: Which direction we scroll (See class constants)
3420- """
3421- # Change logical position
3422- x, y = self.content_position
3423- if direction == self.UP:
3424- self.content_position = (x, y+1)
3425- elif direction == self.DOWN:
3426- self.content_position = (x, y-1)
3427- elif direction == self.LEFT:
3428- self.content_position = (x+1, y)
3429- elif direction == self.RIGHT:
3430- self.content_position = (x-1, y)
3431-
3432- x_offset = count * (self.cell_width + self.cell_spacing)
3433- y_offset = count * (self.cell_height + self.cell_spacing)
3434-# if self.animate:
3435-# x = self.itemgroup.get_x()
3436-# y = self.itemgroup.get_y()
3437-#
3438-# # Finish previous animation before new
3439-# if self.content_timeline is not None and (
3440-# self.content_timeline.is_playing():
3441-# self.content_timeline.pause()
3442-# self.content_timeline.advance(4)
3443-#
3444-# self.content_timeline = clutter.Timeline(4, 26)
3445-# alpha = clutter.Alpha(self.content_timeline,
3446-# clutter.ramp_inc_func)
3447-# if direction == self.UP:
3448-# behaviour = clutter.BehaviourPath(alpha,
3449-# ((x,y),
3450-# (x,y - y_offset)))
3451-# elif direction == self.DOWN:
3452-# behaviour = clutter.BehaviourPath(alpha,
3453-# ((x,y),
3454-# (x,y + y_offset)))
3455-# elif direction == self.RIGHT:
3456-# behaviour = clutter.BehaviourPath(alpha,
3457-# ((x,y),
3458-# (x + x_offset,y)))
3459-# elif direction == self.LEFT:
3460-# behaviour = clutter.BehaviourPath(alpha,
3461-# ((x,y),
3462-# (x - x_offset,y)))
3463-# behaviour.apply(self.itemgroup)
3464-# self.content_timeline.start()
3465-# else:
3466- if direction == self.UP:
3467- self.itemgroup.move_by(0, -y_offset)
3468- elif direction == self.DOWN:
3469- self.itemgroup.move_by(0, y_offset)
3470- elif direction == self.RIGHT:
3471- self.itemgroup.move_by(x_offset, 0)
3472- elif direction == self.LEFT:
3473- self.itemgroup.move_by(-x_offset, 0)
3474-
3475- def _scale_menuitem(self, menuitem):
3476- """
3477- Scale menuitem to fit into one cell. Preserves actor's aspect ratio.
3478- @param menuitem: clutter.Actor
3479- """
3480- w, h = menuitem.get_size()
3481-
3482- if w == self.cell_width and h == self.cell_height:
3483- return # Already correct size, no scale needed
3484-
3485- x_ratio = self.cell_width / float(w)
3486- y_ratio = self.cell_height / float(h)
3487- if x_ratio > y_ratio:
3488- menuitem.set_scale(self.cell_height /float(h),
3489- self.cell_height / float(h))
3490- else:
3491- menuitem.set_scale(self.cell_width / float(w),
3492- self.cell_width / float(w))
3493-
3494- def _scale_cursor(self):
3495- """
3496- Scale cursor. Override this method if you want to use cursor that is
3497- smaller or larger than menuitem. This default implementation scales
3498- cursor into the size of cell of the grid. Aspect ratio is not
3499- preserved!
3500- """
3501- x_ratio = self.cell_width / float(self.cursor.get_width())
3502- y_ratio = self.cell_height / float(self.cursor.get_height())
3503- self.cursor.set_scale(x_ratio, y_ratio)
3504-
3505- def _update_menuitem_position(self, menuitem, logical_position):
3506- """
3507- Recalculate menuitem's position.
3508- @param menuitem: Menuitem that is repositioned (clutter.Actor)
3509- @param logical_position: Logical position of the menuitem
3510- """
3511- if logical_position == 0:
3512- x = 0
3513- y = 0
3514- else:
3515- row = (logical_position / self.row_count) # Item's row
3516- col = logical_position % self.row_count # Item's column
3517- if self.orientation == self.VERTICAL:
3518- x = col * (self.cell_width + self.cell_spacing)
3519- y = row * (self.cell_height + self.cell_spacing)
3520- elif self.orientation == self.HORIZONTAL:
3521- y = col * (self.cell_height + self.cell_spacing)
3522- x = row * (self.cell_width + self.cell_spacing)
3523- menuitem.set_position(x, y)
3524-
3525- def _update_selected_item(self, new_location, old_location=None):
3526- """
3527- This method allows to modify currently selected item. This can be
3528- overriden in inherited widgets.
3529- @param new_location: Logical location of current menuitem
3530- @param old_location: Logical location of last menuitem
3531- """
3532- pass
3533-
3534- def _animate_items_on_state_change(self):
3535- """
3536- This method is called when widget is set active or inactive. The method
3537- allows widget to react this action. Method should be overriden in
3538- inheriting classes. Default implementation doesn't do anything.
3539- """
3540- pass
3541+
3542+ # Items dimensions variable: relative, absolute, center
3543+ self._item_width = item_width
3544+ self._item_height = item_height
3545+ self._item_width_abs = self.get_abs_x(item_width)
3546+ self._item_height_abs = self.get_abs_y(item_height)
3547+ self._dx = int(self._item_width_abs / 2)
3548+ self._dy = int(self._item_height_abs / 2)
3549+
3550+ # Default cursor's index.
3551+ self._selected_index = 0
3552+
3553+ # Grid dimensions: real, visible.
3554+ self.items_per_row = 10
3555+ self.items_per_col = 10
3556+ self._visible_rows = 3
3557+ self._visible_cols = 5
3558+
3559+ # The moving_group is a Clutter group containing all the items.
3560+ self._moving_group_x = 0
3561+ self._moving_group_y = 0
3562+ self._moving_group = clutter.Group()
3563+ self.add(self._moving_group)
3564+
3565+ # The moving_group is translated using a `BehaviourPath`.
3566+ self._moving_group_timeline = clutter.Timeline(fps=200, duration=200)
3567+ moving_group_alpha = clutter.Alpha(self._moving_group_timeline,
3568+ clutter.smoothstep_inc_func)
3569+ self._moving_group_behaviour = clutter.BehaviourPath(moving_group_alpha)
3570+ self._moving_group_behaviour.apply(self._moving_group)
3571+
3572+ # The cursor is an Actor that can be added and moved on the menu.
3573+ # The cusor is always located in the visible (clipped) area of the menu.
3574+ self._cursor_x = 0
3575+ self._cursor_y = 0
3576+ self._cursor = None
3577+ self._cursor_timeline = clutter.Timeline(fps=200, duration=200)
3578+ cursor_alpha = clutter.Alpha(self._cursor_timeline,
3579+ clutter.sine_inc_func)
3580+ self._cursor_behaviour = clutter.BehaviourPath(cursor_alpha)
3581+
3582+ # A MotionBuffer is used to compute useful information about the
3583+ # cursor's motion. It's used when moving the cursor with a pointer.
3584+ self._motion_buffer = MotionBuffer()
3585+ self._event_mode = self.MODE_NONE
3586+ self._motion_handler = 0
3587+ self._seek_step_x = 0
3588+ self._seek_step_y = 0
3589+ gobject.timeout_add(200, self._internal_timer_callback)
3590+
3591+ #XXX: Samuel Buffet
3592+ # This rectangle is used to grab events as it turns out that their
3593+ # might be a bug in clutter 0.8 or python-clutter 0.8.
3594+ # It may be avoided with next release of clutter.
3595+ self._event_rect = clutter.Rectangle()
3596+ self._event_rect.set_opacity(0)
3597+ self.add(self._event_rect)
3598+ self._event_rect.set_reactive(True)
3599+ self._event_rect.connect('button-press-event',
3600+ self._on_button_press_event)
3601+ self._event_rect.connect('button-release-event',
3602+ self._on_button_release_event)
3603+ self._event_rect.connect('scroll-event', self._on_scroll_event)
3604+
3605+ self.set_position(self.get_abs_x(x), self.get_abs_y(y))
3606+
3607+ @property
3608+ def count(self):
3609+ """Return the number of items."""
3610+ return len(self.items)
3611+
3612+ @property
3613+ def on_top(self):
3614+ """Return True if the selected item is currently on the top."""
3615+ selected_row = self._index_to_xy(self._selected_index)[1]
3616+ if selected_row == 0:
3617+ return True
3618+ else:
3619+ return False
3620+
3621+ @property
3622+ def on_bottom(self):
3623+ """Return True if the selected item is currently on the bottom."""
3624+ selected_row = self._index_to_xy(self._selected_index)[1]
3625+ if self._is_vertical:
3626+ end_row = self._index_to_xy(self.count - 1)[1]
3627+ if selected_row == end_row:
3628+ return True
3629+ else:
3630+ return False
3631+ else:
3632+ if selected_row == self.items_per_col - 1:
3633+ return True
3634+ else:
3635+ return False
3636+
3637+ @property
3638+ def on_left(self):
3639+ """Return True if the selected item is currently on the left."""
3640+ selected_col = self._index_to_xy(self._selected_index)[0]
3641+ if selected_col == 0:
3642+ return True
3643+ else:
3644+ return False
3645+
3646+ @property
3647+ def on_right(self):
3648+ """Return True if the selected item is currently on the right."""
3649+ selected_col = self._index_to_xy(self._selected_index)[0]
3650+ if not self._is_vertical:
3651+ end_col = self._index_to_xy(self.count - 1)[0]
3652+ if selected_col == end_col:
3653+ return True
3654+ else:
3655+ return False
3656+ else:
3657+ if selected_col == self.items_per_row - 1:
3658+ return True
3659+ else:
3660+ return False
3661+
3662+ @property
3663+ def selected_item(self):
3664+ """Return the selected MenuItem."""
3665+ if self.count == 0:
3666+ return None
3667+ else:
3668+ return self.items[self._selected_index]
3669+
3670+ @property
3671+ def selected_userdata(self):
3672+ """Return userdata of the MenuItem."""
3673+ item = self.selected_item
3674+ if item is None:
3675+ return None
3676+ else:
3677+ return item.userdata
3678+
3679+ def _get_active(self):
3680+ """Active property getter."""
3681+ return self._active
3682+
3683+ def _set_active(self, boolean):
3684+ """Active property setter."""
3685+ if self._active == boolean:
3686+ return
3687+
3688+ self._active = boolean
3689+ if boolean:
3690+ if self._cursor is not None:
3691+ self._cursor.show()
3692+ if self.selected_item is not None:
3693+ self.selected_item.animate_in()
3694+ self.emit('activated')
3695+ self.set_opacity(255)
3696+ else:
3697+ if self._cursor is not None:
3698+ self._cursor.hide()
3699+ if self.selected_item is not None:
3700+ self.selected_item.animate_out()
3701+ self.set_opacity(128)
3702+
3703+ active = property(_get_active, _set_active)
3704+
3705+ def _get_horizontal(self):
3706+ """horizontal property getter."""
3707+ return not self._is_vertical
3708+
3709+ def _set_horizontal(self, boolean):
3710+ """horizontal property setter."""
3711+ self._is_vertical = not boolean
3712+
3713+ horizontal = property(_get_horizontal, _set_horizontal)
3714+
3715+ def _get_vertical(self):
3716+ """vertical property getter."""
3717+ return self._is_vertical
3718+
3719+ def _set_vertical(self, boolean):
3720+ """vertical property setter."""
3721+ self._is_vertical = boolean
3722+
3723+ vertical = property(_get_vertical, _set_vertical)
3724+
3725+ def _get_selected_index(self):
3726+ """selected_index property getter."""
3727+ return self._selected_index
3728+
3729+ def _set_selected_index(self, index, duration=None):
3730+ """selected_index property setter."""
3731+ # Xc, Yc : coordinates of the menu's cursor on the array of items.
3732+ # xc, yc : coordinates of the menu's cursor relative to the menu.
3733+ # xm, ym : coordinates of the moving_group relative to the menu.
3734+ # Xc = xc - xm
3735+ # Yc = yc - ym
3736+
3737+ if self._selected_index == index or \
3738+ index < 0 or \
3739+ index > self.count - 1 or \
3740+ self._moving_group_timeline.is_playing() or \
3741+ self._cursor_timeline.is_playing():
3742+ return
3743+
3744+ # Start select/unselect animations on both items.
3745+ self.items[self._selected_index].animate_out()
3746+ self.items[index].animate_in()
3747+
3748+ # Get the cursor's coordinate on the array.
3749+ # /!\ Those coordinates are NOT pixels but refer to the array of items.
3750+ (Xc, Yc) = self._index_to_xy(index)
3751+
3752+ xm = self._moving_group_x
3753+ ym = self._moving_group_y
3754+
3755+ xc = Xc + xm
3756+ yc = Yc + ym
3757+
3758+ # If the targeted cursor's position is on the last visible column then
3759+ # the moving_group is translated by -1 on the x axis and the translation
3760+ # of the cursor is reduce by 1 to stay on the column before the last
3761+ # one. This is not done if the last column has been selected.
3762+ if xc == self.visible_cols - 1 and \
3763+ xm > self.visible_cols -self.items_per_row:
3764+ xc -= 1
3765+ xm -= 1
3766+
3767+ # If the targeted cursor's position is on the first visible column then
3768+ # the moving_group is translated by +1 on the x axis and the translation
3769+ # of the cursor is raised by 1 to stay on the column after the first
3770+ # one. This is not done if the first column has been selected.
3771+ if xc == 0 and xm < 0:
3772+ xc += 1
3773+ xm += 1
3774+
3775+ # If the targeted cursor's position is on the last visible row then
3776+ # the moving_group is translated by -1 on the y axis and the translation
3777+ # of the cursor is reduce by 1 to stay on the row before the last
3778+ # one. This is not done if the last row has been selected.
3779+ if yc == self.visible_rows - 1 and \
3780+ ym > self.visible_rows -self.items_per_col:
3781+ yc -= 1
3782+ ym -= 1
3783+
3784+ # If the targeted cursor's position is on the first visible row then
3785+ # the moving_group is translated by +1 on the y axis and the translation
3786+ # of the cursor is raised by 1 to stay on the row after the first
3787+ # one. This is not done if the last row has been selected.
3788+ if yc == 0 and ym < 0:
3789+ yc += 1
3790+ ym += 1
3791+
3792+ if duration is None:
3793+ duration = self.motion_duration
3794+
3795+ self._move_cursor(xc, yc, duration)
3796+ self._move_moving_group(xm, ym, duration)
3797+ self._selected_index = index
3798+
3799+ self.emit('moved')
3800+
3801+ selected_index = property(_get_selected_index, _set_selected_index)
3802+
3803+ def _get_visible_rows(self):
3804+ """visible_rows property getter."""
3805+ return self._visible_rows
3806+
3807+ def _set_visible_rows(self, visible_rows):
3808+ """visible_rows property setter."""
3809+ self._visible_rows = visible_rows
3810+ self._clip()
3811+
3812+ visible_rows = property(_get_visible_rows, _set_visible_rows)
3813+
3814+ def _get_visible_cols(self):
3815+ """visible_cols property getter."""
3816+ return self._visible_cols
3817+
3818+ def _set_visible_cols(self, visible_cols):
3819+ """visible_cols property setter."""
3820+ self._visible_cols = visible_cols
3821+ self._clip()
3822+
3823+ visible_cols = property(_get_visible_cols, _set_visible_cols)
3824+
3825+ def _get_cursor(self):
3826+ """cursor property getter."""
3827+ return self._cursor
3828+
3829+ def _set_cursor(self, cursor):
3830+ """cursor property setter."""
3831+ if self._cursor is not None:
3832+ self.remove(self._cursor)
3833+
3834+ self._cursor = cursor
3835+
3836+ if self._cursor is not None:
3837+ self.add(self._cursor)
3838+ if self._active:
3839+ self._cursor.show()
3840+ else:
3841+ self._cursor.hide()
3842+
3843+ if self.cursor_below:
3844+ self._cursor.lower_bottom()
3845+ else:
3846+ self._cursor.raise_top()
3847+
3848+ self._cursor.set_size(int(self._item_width_abs),
3849+ int(self._item_height_abs))
3850+ self._cursor.set_anchor_point(self._dx, self._dy)
3851+ self._cursor.set_position(self._dx, self._dy)
3852+
3853+ self._cursor_behaviour.apply(self._cursor)
3854+
3855+ cursor = property(_get_cursor, _set_cursor)
3856+
3857+ def _clip(self):
3858+ """Updates the clipping region."""
3859+ self.set_clip(0, 0, self._visible_cols * self._item_width_abs,
3860+ self._visible_rows * self._item_height_abs)
3861+
3862+ self._event_rect.set_size(self._visible_cols * self._item_width_abs,
3863+ self._visible_rows * self._item_height_abs)
3864+
3865+ def stop_animation(self):
3866+ """Stops the timelines driving menu animation."""
3867+ self._moving_group_timeline.stop()
3868+ self._cursor_timeline.stop()
3869+
3870+ def raw_add_item(self, item):
3871+ """A method to add an item in the menu."""
3872+ self._moving_group.add(item)
3873+ self.items.append(item)
3874+
3875+ (x, y) = self._index_to_xy(self.count - 1)
3876+
3877+ item.move_anchor_point(self._dx, self._dy)
3878+ item.set_position(x * self._item_width_abs + self._dx,
3879+ y * self._item_height_abs + self._dy)
3880+
3881+ if self._is_vertical:
3882+ self.items_per_col = y + 1
3883+ else:
3884+ self.items_per_row = x + 1
3885+
3886+ if self.cursor_below:
3887+ item.raise_top()
3888+ else:
3889+ item.lower_bottom()
3890+
3891+ def _index_to_xy(self, index):
3892+ """Return the coordinates of an element associated to its index."""
3893+ if self._is_vertical:
3894+ r = index / float(self.items_per_row)
3895+ y = int(math.modf(r)[1])
3896+ x = int(index - y * self.items_per_row)
3897+ else:
3898+ r = index / float(self.items_per_col)
3899+ x = int(math.modf(r)[1])
3900+ y = int(index - x * self.items_per_col)
3901+
3902+ return (x, y)
3903+
3904+ def _move_moving_group(self, x, y, duration):
3905+ """Moves the moving_group to x, y coordinates."""
3906+ if (x, y) == (self._moving_group_x, self._moving_group_y):
3907+ return
3908+
3909+ self._moving_group_behaviour.clear()
3910+
3911+ start_knot = (self._moving_group_x * self._item_width_abs,
3912+ self._moving_group_y * self._item_height_abs)
3913+ stop_knot = (x * self._item_width_abs,
3914+ y * self._item_height_abs)
3915+
3916+ self._moving_group_behaviour.append_knots(start_knot, stop_knot)
3917+
3918+ self._moving_group_x, self._moving_group_y = x, y
3919+ self._moving_group_timeline.set_duration(duration)
3920+ self._moving_group_timeline.start()
3921+
3922+ def _move_cursor(self, x, y, duration):
3923+ """
3924+ Moves the cursor to x, y coordinates.
3925+ The motion is applied to the center of the cursor.
3926+ """
3927+ if (x, y) == (self._cursor_x, self._cursor_y):
3928+ return
3929+
3930+ self._cursor_behaviour.clear()
3931+
3932+ start_knot = (self._cursor_x * self._item_width_abs + self._dx,
3933+ self._cursor_y * self._item_height_abs + self._dy)
3934+ stop_knot = (x * self._item_width_abs + self._dx,
3935+ y * self._item_height_abs + self._dy)
3936+
3937+ self._cursor_behaviour.append_knots(start_knot, stop_knot)
3938+
3939+ self._cursor_x, self._cursor_y = x, y
3940+ self._cursor_timeline.set_duration(duration)
3941+ self._cursor_timeline.start()
3942+
3943+ def up(self):
3944+ """Move the menu's cursor up changing the selected_index property."""
3945+ if not self.on_top:
3946+ if self._is_vertical:
3947+ self.selected_index -= self.items_per_row
3948+ else:
3949+ self.selected_index -= 1
3950+
3951+ def down(self):
3952+ """Move the menu's cursor down changing the selected_index property."""
3953+ if not self.on_bottom:
3954+ if self._is_vertical:
3955+ self.selected_index += self.items_per_row
3956+ else:
3957+ self.selected_index += 1
3958+
3959+ def right(self):
3960+ """Move the menu's cursor right changing the selected_index property."""
3961+ if not self.on_right:
3962+ if self._is_vertical:
3963+ self.selected_index += 1
3964+ else:
3965+ self.selected_index += self.items_per_col
3966+
3967+ def left(self):
3968+ """Move the menu's cursor left changing the selected_index property."""
3969+ if not self.on_left:
3970+ if self._is_vertical:
3971+ self.selected_index -= 1
3972+ else:
3973+ self.selected_index -= self.items_per_col
3974+
3975+ def _internal_timer_callback(self):
3976+ """
3977+ This callback is used to move the cursor if the SEEK mode is activated.
3978+ """
3979+ if self._event_mode == self.MODE_SEEK:
3980+ if self._seek_step_x == 1:
3981+ self.right()
3982+ if self._seek_step_x == -1:
3983+ self.left()
3984+ if self._seek_step_y == 1:
3985+ self.down()
3986+ if self._seek_step_y == -1:
3987+ self.up()
3988+
3989+ return True
3990+
3991+ def _on_button_press_event(self, actor, event):
3992+ """button-press-event handler."""
3993+ clutter.grab_pointer(self._event_rect)
3994+ if not self._event_rect.handler_is_connected(self._motion_handler):
3995+ self._motion_handler = self._event_rect.connect('motion-event',
3996+ self._on_motion_event)
3997+
3998+ (x_menu, y_menu) = self.get_transformed_position()
3999+ (x_moving_group, y_moving_group) = self._moving_group.get_position()
4000+
4001+ # Events coordinates are relative to the stage.
4002+ # So they need to be computed relatively to the moving group.
4003+ x = event.x - x_menu - x_moving_group
4004+ y = event.y - y_menu - y_moving_group
4005+
4006+ x_grid = int(x / self._item_width_abs)
4007+ y_grid = int(y / self._item_height_abs)
4008+
4009+ if self._is_vertical:
4010+ new_index = y_grid * self.items_per_row + x_grid
4011+ else:
4012+ new_index = x_grid * self.items_per_col + y_grid
4013+
4014+ (delta_x, delta_y) = self._index_to_xy(self._selected_index)
4015+
4016+ delta_x -= x_grid
4017+ delta_y -= y_grid
4018+
4019+ # Correction factor due to the fact that items are not necessary square,
4020+ # but most probably rectangles. So the distance in the grid coordinates
4021+ # must be corrected by a factor to have a real distance in pixels on the
4022+ # screen.
4023+ correction = float(self._item_width_abs) / float(self._item_height_abs)
4024+ correction *= correction
4025+ distance = math.sqrt(delta_x ** 2 * correction + delta_y ** 2)
4026+
4027+ # Computation of the duration of animations, scaling grid steps to ms.
4028+ duration = int(distance * 50)
4029+
4030+ if self.selected_index == new_index and \
4031+ self.active and \
4032+ not self._cursor_timeline.is_playing() and \
4033+ not self._moving_group_timeline.is_playing():
4034+ self._event_mode = self.MODE_SELECT
4035+ else:
4036+ self.active = True
4037+ self._event_mode = self.MODE_NONE
4038+
4039+ self._set_selected_index(new_index, duration)
4040+
4041+ self._motion_buffer.start(event)
4042+
4043+ return False
4044+
4045+ def _on_button_release_event(self, actor, event):
4046+ """button-release-event handler."""
4047+ clutter.ungrab_pointer()
4048+ if self._event_rect.handler_is_connected(self._motion_handler):
4049+ self._event_rect.disconnect_by_func(self._on_motion_event)
4050+
4051+ if self._event_mode == self.MODE_SELECT:
4052+ self.emit('selected')
4053+
4054+ self._event_mode = self.MODE_NONE
4055+
4056+ return True
4057+
4058+ def _on_motion_event(self, actor, event):
4059+ """motion-event handler"""
4060+ # threshold in pixels = the minimum distance we have to move before we
4061+ # consider a motion has started
4062+ motion_threshold = 20
4063+
4064+ self._seek_step_x = 0
4065+ self._seek_step_y = 0
4066+ self._motion_buffer.compute_from_start(event)
4067+ self._motion_buffer.compute_from_last_motion_event(event)
4068+
4069+ if self._motion_buffer.distance_from_start > motion_threshold:
4070+ self._event_mode = self.MODE_SEEK
4071+ self._motion_buffer.take_new_motion_event(event)
4072+ dx = self._motion_buffer.dx_from_last_motion_event
4073+ dy = self._motion_buffer.dy_from_last_motion_event
4074+
4075+ if math.fabs(dx) > math.fabs(dy):
4076+ self._seek_step_x = dx > 0 and 1 or -1
4077+ else:
4078+ self._seek_step_y = dy > 0 and 1 or -1
4079+
4080+ return False
4081+
4082+ def _on_scroll_event(self, actor, event):
4083+ """scroll-event handler (mouse's wheel)."""
4084+ if not self.active:
4085+ self.active = True
4086+ return
4087+
4088+ if event.direction == clutter.SCROLL_DOWN:
4089+ self.down()
4090+ else:
4091+ self.up()
4092+
4093+ return False
4094
4095
4096=== modified file 'entertainerlib/frontend/gui/widgets/image_menu.py'
4097--- entertainerlib/frontend/gui/widgets/image_menu.py 2009-05-06 03:40:22 +0000
4098+++ entertainerlib/frontend/gui/widgets/image_menu.py 2009-07-19 19:27:31 +0000
4099@@ -2,88 +2,177 @@
4100 '''Implements a grid menu that contains images'''
4101
4102 import clutter
4103+import gtk
4104+import gobject
4105
4106 from entertainerlib.frontend.gui.widgets.grid_menu import GridMenu
4107-from entertainerlib.utils.configuration import Configuration
4108+from entertainerlib.frontend.gui.widgets.menu_item import MenuItem
4109+from entertainerlib.frontend.gui.widgets.rounded_texture import RoundedTexture
4110+from entertainerlib.frontend.gui.widgets.texture import Texture
4111+
4112+class ImageMenuItem(MenuItem):
4113+ """A menuitem widget that contains a Texture."""
4114+
4115+ def __init__(self, width, height, texture):
4116+ MenuItem.__init__(self)
4117+
4118+ item_width = self.get_abs_x(width)
4119+ item_height = self.get_abs_y(height)
4120+
4121+ self.original_ratio = float(texture.get_width())
4122+ self.original_ratio /= texture.get_height()
4123+ item_ratio = float(item_width) / float(item_height)
4124+
4125+ margin = 0.95
4126+
4127+ if item_ratio >= self.original_ratio:
4128+ texture_width = self.original_ratio * item_height
4129+ texture_height = item_height
4130+ texture_width *= margin
4131+ texture_height *= margin
4132+ delta_width = item_width - texture_width
4133+ delta_height = item_height - texture_height
4134+ else:
4135+ texture_width = item_width
4136+ texture_height = item_width / self.original_ratio
4137+ texture_width *= margin
4138+ texture_height *= margin
4139+ delta_width = item_width - texture_width
4140+ delta_height = item_height - texture_height
4141+
4142+ item_x = delta_width / 2.0
4143+ item_y = delta_height / 2.0
4144+
4145+ texture.set_position(int(item_x), int(item_y))
4146+ texture.set_size(int(texture_width), int(texture_height))
4147+
4148+ self.add(texture)
4149+
4150
4151 class ImageMenu(GridMenu):
4152- """
4153- ImageMenu widget
4154-
4155- This widget inherits genral GridMenu widget and implements simple
4156- imagemenu.
4157- """
4158-
4159- def __init__(self, theme):
4160- """Initialize widget"""
4161- GridMenu.__init__(self, self.HORIZONTAL)
4162-
4163- #XXX: Joshua: This is to make sure the spaces in an image menu are the
4164- #right size when the screen is not the default size of 1366x768
4165- #This should eventually be replaced by proportional resizing.
4166- config = Configuration()
4167-
4168- width_ratio = config.get_stage_width() / 1366.0
4169- height_ratio = config.get_stage_height() / 768.0
4170-
4171- self.set_item_size(int(200 * width_ratio), int(150 * height_ratio))
4172-
4173- #End of temporary code
4174-
4175- self.set_row_count(3)
4176+ """A grid menu that contains images."""
4177+ __gsignals__ = {
4178+ 'filled' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
4179+ }
4180+
4181+ def __init__(self, x=0, y=0, item_width=0.2, item_height=0.1):
4182+ GridMenu.__init__(self, x, y, item_width, item_height)
4183+
4184+ self.cursor_below = False
4185+ self.horizontal = True
4186+ self.items_per_col = 4
4187+ self.visible_rows = 3
4188+ self.visible_cols = 5
4189
4190 c = clutter.Rectangle()
4191+ c.set_size(100, 100)
4192 c.set_color((255, 255, 255, 128))
4193- self.set_cursor(c)
4194-
4195- def set_animate(self, boolean):
4196- """
4197- Enable/disable widget animation.
4198- @param boolean: True if widget should be animated, otherwise False
4199- """
4200- self.animate = boolean
4201- #self.cursor.animate(boolean)
4202-
4203- def _update_selected_item(self, new_location, old_location=None):
4204- """
4205- Overridden method! See GridMenu API reference.
4206- @param new_location: Logical location of current menuitem
4207- @param old_location: Logical location of last menuitem
4208- """
4209- if old_location is not None:
4210- self.items[old_location].set_active(False)
4211- self.items[new_location].set_active(True)
4212-
4213- def _animate_items_on_state_change(self):
4214- """
4215- This method is called when widget is set active or inactive. This
4216- method allows widget to react this action.
4217- """
4218- if len(self.items):
4219- if self.active:
4220- self.cursor.show()
4221- self.items[self.logical_position].set_active(True)
4222- self.set_opacity(255)
4223- else:
4224- self.cursor.hide()
4225- self.items[self.logical_position].set_active(False)
4226- self.set_opacity(128)
4227-
4228- def _scale_menuitem(self, menuitem):
4229- """
4230- Scale menuitem to fit into one cell. Preserves actor's aspect ratio.
4231- @param menuitem: clutter.Actor
4232- """
4233-# w, h = menuitem.get_size()
4234-#
4235-# if w == self.cell_width and h == self.cell_height:
4236-# return # Already correct size, no scale needed
4237-#
4238-# x_ratio = self.cell_width / float(w)
4239-# y_ratio = self.cell_height / float(h)
4240-# if x_ratio > y_ratio:
4241-# menuitem.set_scale(self.cell_height /float(h),
4242-# self.cell_height / float(h))
4243-# else:
4244-# menuitem.set_scale(self.cell_width / float(w),
4245-# self.cell_width / float(w))
4246+ self.cursor = c
4247+
4248+ pix_buffer = gtk.gdk.pixbuf_new_from_file(
4249+ self.config.theme.getImage("default_movie_art"))
4250+ self.movie_default = RoundedTexture(0.0, 0.0, 0.1, 0.25, pix_buffer)
4251+
4252+ self.album_default = Texture(
4253+ self.config.theme.getImage("default_album_art"))
4254+
4255+ def add_item(self, texture, data):
4256+ """Add a ImageMenuItem from a Texture."""
4257+ item = ImageMenuItem(self._item_width, self._item_height, texture)
4258+ item.userdata = data
4259+
4260+ self.raw_add_item(item)
4261+
4262+ def async_add(self, items):
4263+ """
4264+ Add asynchronously ImageMenuItem using a list.
4265+
4266+ The list should be : [[texture1, data1], [texture2, data2], etc]
4267+
4268+ texture1, texture2 : are Texture objects.
4269+ data1, data2: are the data that will be accessible from the menu item.
4270+ (see MenuItem Class)
4271+ """
4272+ if len(items) > 0:
4273+ item = items[0]
4274+ self.add_item(item[0], item[1])
4275+
4276+ # Recursive call, remove first element from the list
4277+ gobject.timeout_add(15, self.async_add, items[1:])
4278+ else:
4279+ self.emit("filled")
4280+
4281+ return False
4282+
4283+ # XXX: This needs to be changed. An ImageMenu should know nothing about
4284+ # special video items.
4285+ def async_add_videos(self, items):
4286+ """
4287+ Add asynchronously ImageMenuItem using a list.
4288+ The created ImageMenuItem fits movies and series requirements.
4289+ See also async_add comments.
4290+ """
4291+ if len(items) > 0:
4292+ item = items[0]
4293+ if item[1].has_cover_art():
4294+ pix_buffer = gtk.gdk.pixbuf_new_from_file(item[0])
4295+ texture = RoundedTexture(0.0, 0.0, 0.1, 0.25, pix_buffer)
4296+ else:
4297+ texture = clutter.CloneTexture(self.movie_default)
4298+
4299+ self.add_item(texture, item[1])
4300+
4301+ # Recursive call, remove first element from the list
4302+ gobject.timeout_add(10, self.async_add_videos, items[1:])
4303+ else:
4304+ self.emit("filled")
4305+
4306+ return False
4307+
4308+ # XXX: This needs to be changed. An ImageMenu should know nothing about
4309+ # special album items.
4310+ def async_add_albums(self, items):
4311+ """
4312+ Add asynchronously ImageMenuItem using a list.
4313+ The created ImageMenuItem fits albums requirements.
4314+ See also async_add comments.
4315+ """
4316+ if len(items) > 0:
4317+ item = items[0]
4318+ if item[1].has_album_art():
4319+ texture = Texture(item[0])
4320+ else:
4321+ texture = clutter.CloneTexture(self.album_default)
4322+
4323+ self.add_item(texture, item[1])
4324+
4325+ # Recursive call, remove first element from the list
4326+ gobject.timeout_add(10, self.async_add_albums, items[1:])
4327+ else:
4328+ self.emit("filled")
4329+
4330+ return False
4331+
4332+ # XXX: This needs to be changed. An ImageMenu should know nothing about
4333+ # special clip items.
4334+ def async_add_clips(self, items):
4335+ """
4336+ Add asynchronously ImageMenuItem using a list.
4337+ The created ImageMenuItem fits clips requirements.
4338+ See also async_add comments.
4339+ """
4340+ if len(items) > 0:
4341+ item = items[0]
4342+ pix_buffer = gtk.gdk.pixbuf_new_from_file(item[0])
4343+ texture = RoundedTexture(0.0, 0.0, 0.23, self.y_for_x(0.23) * 0.7,
4344+ pix_buffer)
4345+
4346+ self.add_item(texture, item[1])
4347+
4348+ # Recursive call, remove first element from the list
4349+ gobject.timeout_add(10, self.async_add_clips, items[1:])
4350+ else:
4351+ self.emit("filled")
4352+
4353+ return False
4354+
4355
4356=== removed file 'entertainerlib/frontend/gui/widgets/image_menu_item.py'
4357--- entertainerlib/frontend/gui/widgets/image_menu_item.py 2009-05-06 02:58:08 +0000
4358+++ entertainerlib/frontend/gui/widgets/image_menu_item.py 1970-01-01 00:00:00 +0000
4359@@ -1,34 +0,0 @@
4360-# Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
4361-'''Menuitem that contains an image'''
4362-
4363-from entertainerlib.frontend.gui.widgets.menu_item import MenuItem
4364-
4365-class ImageMenuItem(MenuItem):
4366- """
4367- Simple Image MenuItem
4368-
4369- This menu item contains one Texture that is scaled in such a way that the
4370- texture fills the whole menu item area. Texture aspect ratio is not
4371- preserved. ImageMenuItem is defined in terms of its width with a ratio
4372- determining the height relative to width. This is so the image will scale
4373- predictably on monitors of different aspect ratios.
4374- """
4375-
4376- def __init__(self, x_percent, y_to_x_ratio, texture):
4377- """Initialize menuitem"""
4378-
4379- MenuItem.__init__(self)
4380-
4381- width = self.get_abs_x(x_percent)
4382- height = self._calculate_height(width, y_to_x_ratio)
4383-
4384- texture.set_size(width, height)
4385-
4386- self.add(texture)
4387-
4388- def _calculate_height(self, width, y_to_x_ratio):
4389- """Calculate pixel height from the ratio of y to x"""
4390- height = int(width * y_to_x_ratio)
4391-
4392- return height
4393-
4394
4395=== modified file 'entertainerlib/frontend/gui/widgets/label.py'
4396--- entertainerlib/frontend/gui/widgets/label.py 2009-04-28 22:30:06 +0000
4397+++ entertainerlib/frontend/gui/widgets/label.py 2009-05-31 15:00:01 +0000
4398@@ -22,7 +22,7 @@
4399 self._font_size = font_size
4400 self._set_font_size(font_size)
4401
4402- self.set_color(self.theme.get_color(color_name))
4403+ self.color = color_name
4404
4405 self._position = None
4406 self._set_position((x_pos_percent, y_pos_percent))
4407@@ -32,33 +32,33 @@
4408 if name:
4409 self.set_name(name)
4410
4411- self._width = self._init_width()
4412- self._height = self._init_height()
4413-
4414 self.set_line_wrap(True)
4415
4416+ def _get_color(self):
4417+ """color property getter."""
4418+ return self._color
4419+
4420+ def _set_color(self, color_name):
4421+ """color property setter."""
4422+ self._color = color_name
4423+ self.set_color(self.theme.get_color(self._color))
4424+
4425+ color = property(_get_color, _set_color)
4426+
4427 def set_size(self, x_percent, y_percent):
4428 """Override clutter label set_size to calculate absolute size from a
4429 percentage"""
4430- self._width = x_percent
4431- self._height = y_percent
4432 clutter.Label.set_size(self,
4433 self.get_abs_x(x_percent),
4434 self.get_abs_y(y_percent))
4435
4436- def _init_width(self):
4437- """Generate the width of the label initially before the width property
4438- is set to ensure width has a value"""
4439- return float(self.get_width()) / self.config.get_stage_width()
4440-
4441 def _get_width(self):
4442 """Return the width as a percent of the stage size"""
4443- return self._width
4444+ return float(self.get_width()) / self.config.get_stage_width()
4445
4446 def _set_width(self, x_percent):
4447 """Set the width based on a hozirontal percent of the stage size.
4448 Provide clutter label set_width the absolute size from the percentage"""
4449- self._width = x_percent
4450 clutter.Label.set_width(self, self.get_abs_x(x_percent))
4451
4452 width = property(_get_width, _set_width)
4453@@ -80,20 +80,14 @@
4454
4455 position = property(_get_position, _set_position)
4456
4457- def _init_height(self):
4458- """Generate the height of the label initially before the height property
4459- is set to ensure height has a value"""
4460- return float(self.get_height()) / self.config.get_stage_height()
4461-
4462 def _get_height(self):
4463 """Return the height as a percent of the stage size"""
4464- return self._height
4465+ return float(self.get_height()) / self.config.get_stage_height()
4466
4467 def _set_height(self, y_percent):
4468 """Set the height based on a vertical percent of the stage size.
4469 Provide clutter label set_height the absolute size from the percentage
4470 """
4471- self._height = y_percent
4472 clutter.Label.set_height(self, self.get_abs_y(y_percent))
4473
4474 height = property(_get_height, _set_height)
4475
4476=== removed file 'entertainerlib/frontend/gui/widgets/menu.py'
4477--- entertainerlib/frontend/gui/widgets/menu.py 2009-05-06 03:40:22 +0000
4478+++ entertainerlib/frontend/gui/widgets/menu.py 1970-01-01 00:00:00 +0000
4479@@ -1,252 +0,0 @@
4480-# Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
4481-'''Menu has a list of menuitems and it allows user to select one of them'''
4482-
4483-import clutter
4484-
4485-class Menu(clutter.Group):
4486- """
4487- Menu widget.
4488-
4489- Blaa blaa...
4490- """
4491-
4492- def __init__(self):
4493- """
4494- Initialize menu widget.
4495- """
4496- clutter.Group.__init__(self)
4497- self.__name = None # Menu name
4498- self.__active = False # Is this menu active
4499- self.__page_zize = 8 # How many items are displayed at the same
4500- # time
4501- self.__loop = False # Should menu loop from last to first
4502- self.__selector = None # Selector widget
4503- self.__items = [] # List of MenuItems
4504- self.__current = 0 # Current position of the menu
4505- self.__item_width = 0 # Menuitem width
4506- self.__item_height = 0 # Menuitem height
4507-
4508- def is_active(self):
4509- """
4510- Is this menu active.
4511- @return boolean, True if menu is currently active, otherwise False
4512- """
4513- return self.__active
4514-
4515- def set_active(self, boolean):
4516- """
4517- Set this widget active or inactive
4518- @param boolean: True to set widget active, False to set inactive
4519- """
4520- self.__active = boolean
4521- if boolean:
4522- if self.__selector is not None:
4523- self.__selector.show()
4524- self.get_current_menuitem().set_active(True)
4525- self.set_opacity(255)
4526- else:
4527- if self.__selector is not None:
4528- self.__selector.hide()
4529- self.get_current_menuitem().set_active(False)
4530- self.set_opacity(128)
4531-
4532- def add(self, menuitem):
4533- """
4534- Add new menuitem to the menu.
4535- @param menuitem: Add this menuitem (Menitem Widget)
4536- """
4537- # First menuitem determines the size of all menuitems (is it good?)
4538- if len(self.__items) == 0:
4539- self.__item_width = menuitem.get_width()
4540- self.__item_height = menuitem.get_height()
4541-
4542- menuitem.set_position(0, len(self.__items) * menuitem.get_height())
4543- self.__items.append(menuitem)
4544- clutter.Group.add(self, menuitem)
4545- self.update_clip()
4546-
4547- def remove(self, menuitem):
4548- """
4549- Remove menuitem from the menu.
4550- @param menuitem: Remove this menuitem (Menitem Widget)
4551- """
4552- clutter.Group.remove(self, menuitem)
4553- for item in self.__items:
4554- if item is menuitem:
4555- del item
4556-
4557- def remove_all(self):
4558- """
4559- Remove all menuitems from the menu.
4560- @param menuitem: Remove this menuitem (Clutter.Actor)
4561- """
4562- clutter.Group.remove_all(self)
4563- self.__current = 0
4564- del self.__selector
4565- self.__selector = None
4566- del self.__items
4567- self.__items = []
4568-
4569- def set_loop(self, boolean):
4570- """
4571- Set loop for menu. If loop is true then first menuitem comes after the
4572- last one.
4573- @param boolean: True if menu should loop, otherwise False
4574- """
4575- self.__loop = boolean
4576-
4577- def is_loop(self):
4578- """
4579- Is menu in loop mode.
4580- @return: Boolean value
4581- """
4582- return self.__loop
4583-
4584- def set_menuitem_background(self, texture):
4585- """
4586- Set texture that is displayed under every menuitem. This is optional.
4587- @param texture: Menuitem background texture. (clutter.Actor)
4588- """
4589- for index in range(len(self.__items)):
4590- bg = clutter.CloneTexture(texture)
4591- bg.set_size(self.get_width(), self.__item_height)
4592- bg.set_position(0, index * self.__item_height)
4593- bg.set_opacity(128)
4594- bg.set_name("background")
4595- clutter.Group.add(self, bg)
4596- for item in self.__items:
4597- clutter.Group.lower(self, bg, item)
4598-
4599- def set_selector(self, selector):
4600- """
4601- Set selector texture. Selector is moved over menuitems when user
4602- browses the menu.
4603- @param selector: Selector texture (Clutter.Actor)
4604- """
4605- selector.set_position(0, 0)
4606- x_scale = self.__item_width / float(selector.get_width())
4607- y_scale = self.__item_height / float(selector.get_height())
4608- selector.set_size(self.__item_width, self.__item_height)
4609- selector.set_scale(x_scale, y_scale)
4610- selector.set_name("selector")
4611- self.__selector = selector
4612- clutter.Group.add(self, self.__selector)
4613- for item in self.__items:
4614- if item.get_name() != "background":
4615- clutter.Group.lower(self, selector, item)
4616-
4617- def scroll_up(self):
4618- """
4619- Scroll menu up (one menuitem).
4620- """
4621- if self.__current > 0:
4622- self.__items[self.__current].set_active(False)
4623- self.__current = self.__current - 1
4624- self.__items[self.__current].set_active(True)
4625- if self.__selector is not None:
4626- self.__selector.set_position(0,
4627- self.__current * self.__item_height)
4628-
4629- # Loop from top to bottom
4630- elif self.__current == 0 and self.__loop:
4631- self.__items[self.__current].set_active(False)
4632- self.__current = len(self.__items) - 1
4633- self.__items[self.__current].set_active(True)
4634- if self.__selector is not None:
4635- self.__selector.set_position(0,
4636- self.__current * self.__item_height)
4637-
4638- def scroll_down(self):
4639- """
4640- Scroll menu down (one menuitem).
4641- """
4642- if self.__current < len(self.__items) - 1:
4643- self.__items[self.__current].set_active(False)
4644- self.__current = self.__current + 1
4645- self.__items[self.__current].set_active(True)
4646- if self.__selector is not None:
4647- self.__selector.set_position(0,
4648- self.__current * self.__item_height)
4649-
4650- # Loop from bottom to top
4651- elif self.__current == len(self.__items) - 1 and self.__loop:
4652- self.__items[self.__current].set_active(False)
4653- self.__current = 0
4654- self.__items[self.__current].set_active(True)
4655- if self.__selector is not None:
4656- self.__selector.set_position(0,
4657- self.__current * self.__item_height)
4658-
4659- def scroll_up_page(self):
4660- """
4661- Scroll menu up one page. One page is a number of visible items.
4662- """
4663-
4664- def scroll_down_page(self):
4665- """
4666- Scroll menu down one page. One page is a number of visible items.
4667- """
4668- # Set current menuitem
4669- self.__items[self.__current].set_active(False)
4670- self.__current = self.__current + 8
4671- self.__items[self.__current].set_active(True)
4672-
4673- # Set selector position
4674- if self.__selector is not None:
4675- self.__selector.set_position(0,
4676- self.__current * self.__items[0].get_height())
4677-
4678- # Clip to current position of the menu
4679- #self.set_clip(0,)
4680-
4681- def update_clip(self):
4682- """
4683- Update menu clipping. This method decides which partion of menu is
4684- actually displayed on screen. If you have a long menu, not all items
4685- fit into the window. This function checks what is the current position
4686- of the menulist and then clips the widget in such a way that current
4687- position is on screen.
4688- """
4689- if len(self.__items) <= self.__page_zize:
4690- self.remove_clip()
4691- else:
4692- self.set_clip(0,
4693- self.__current * self.__item_height,
4694- self.__item_width,
4695- self.__item_height * self.__page_zize)
4696- self.set_height(self.__item_height * self.__page_zize)
4697-
4698- def get_current_position(self):
4699- """
4700- Get current position of the list. This position is under selector.
4701- """
4702- return self.__current
4703-
4704- def get_current_menuitem(self):
4705- """
4706- Get Clutter.Actor object from the current postition.
4707- """
4708- if len(self.__items) == 0:
4709- return None
4710- return self.__items[self.__current]
4711-
4712- def get_number_of_items(self):
4713- """
4714- Get number of menuitems in menu.
4715- """
4716- return len(self.__items)
4717-
4718- def set_name(self, name):
4719- """
4720- Set menu name
4721- @param name: Name of the menu (used to identify menu from others)
4722- """
4723- self.__name = name
4724-
4725- def get_name(self):
4726- """
4727- Get name of the menu.
4728- @return name as String
4729- """
4730- return self.__name
4731-
4732
4733=== modified file 'entertainerlib/frontend/gui/widgets/menu_item.py'
4734--- entertainerlib/frontend/gui/widgets/menu_item.py 2009-05-06 03:40:22 +0000
4735+++ entertainerlib/frontend/gui/widgets/menu_item.py 2009-05-28 19:28:56 +0000
4736@@ -1,51 +1,34 @@
4737 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
4738-'''Simple Menuitem'''
4739+"""Simple Menuitem."""
4740
4741 import clutter
4742
4743 from entertainerlib.frontend.gui.widgets.base import Base
4744
4745 class MenuItem(Base, clutter.Group):
4746- """
4747- Simple menuitem widget.
4748-
4749- This is a base class for all menuitem types. MenuItems are supposed to be
4750- used in Menus which inherit GridMenu class.
4751- """
4752+ """Simple menuitem widget."""
4753
4754 def __init__(self):
4755- """Initialize menuitem."""
4756-
4757 Base.__init__(self)
4758 clutter.Group.__init__(self)
4759- self.active = False
4760- self.data = None
4761-
4762- def set_active(self, boolean):
4763- """
4764- Set menuitem active / inactive.
4765- @param boolean: use True to set active and False to set inactive
4766- """
4767- self.active = boolean
4768-
4769- def is_active(self):
4770- """
4771- Get widget status.
4772- @return True if widget is active, otherwise False
4773- """
4774- return self.active
4775-
4776- def set_userdata(self, data):
4777- """
4778- Set user data for this menuitem.
4779- @param id: ID string
4780- """
4781- self.data = data
4782-
4783- def get_userdata(self):
4784- """
4785- Get user data
4786- @return: string
4787- """
4788- return self.data
4789+
4790+ self._userdata = None
4791+
4792+ def _get_userdata(self):
4793+ """userdata property getter."""
4794+ return self._userdata
4795+
4796+ def _set_userdata(self, userdata):
4797+ """userdata property getter."""
4798+ self._userdata = userdata
4799+
4800+ userdata = property (_get_userdata, _set_userdata)
4801+
4802+ def animate_in(self):
4803+ """Animation to be done when an item gets selected."""
4804+ pass
4805+
4806+ def animate_out(self):
4807+ """Animation to be done when an item gets unselected."""
4808+ pass
4809
4810
4811=== modified file 'entertainerlib/frontend/gui/widgets/scroll_area.py'
4812--- entertainerlib/frontend/gui/widgets/scroll_area.py 2009-07-11 19:34:42 +0000
4813+++ entertainerlib/frontend/gui/widgets/scroll_area.py 2009-07-22 20:46:29 +0000
4814@@ -1,202 +1,270 @@
4815 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
4816-'''Container that has scrollbars and allows content to be scrolled'''
4817+'''Container that has scrollbars and allows content to be scrolled.'''
4818
4819 import clutter
4820+import gobject
4821
4822 from entertainerlib.frontend.gui.widgets.base import Base
4823 from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
4824+from entertainerlib.frontend.gui.widgets.motion_buffer import MotionBuffer
4825+from entertainerlib.frontend.gui.widgets.special_behaviours import \
4826+ LoopedPathBehaviour
4827
4828 class ScrollArea(Base, clutter.Group):
4829 """Wrapper of a clutter Group that allows for scrolling. ScrollArea
4830 modifies the width of the content and it assumes that the content uses
4831 percent modification (read: not default clutter objects)."""
4832-
4833- def __init__(self, x_size_percent, y_size_percent, x_pos_percent,
4834- y_pos_percent, group, step_size_percent=0.04, active=False):
4835+ __gsignals__ = {
4836+ 'activated' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
4837+ 'moving' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
4838+ }
4839+
4840+ MODE_SELECTION = 0
4841+ MODE_MOTION = 1
4842+ MODE_STOP = 2
4843+ STEP_SIZE_PERCENT = 0.04
4844+
4845+ def __init__(self, x, y, width, height, content):
4846 Base.__init__(self)
4847 clutter.Group.__init__(self)
4848
4849- self.offset = 0 # Determines current position of the view
4850- self.step_size = self.get_abs_y(step_size_percent)
4851- self.area_width = self.get_abs_x(x_size_percent)
4852- self.area_height = self.get_abs_y(y_size_percent)
4853- self.active = active
4854- self.content_group = None
4855-
4856- self.set_position(
4857- self.get_abs_x(x_pos_percent),
4858- self.get_abs_y(y_pos_percent))
4859+ self._motion_buffer = MotionBuffer()
4860+ self._offset = 0 # Drives the content motion.
4861+ self._offset_max = 0 # Maximum value of offset (equal on bottom).
4862+ self._old_offset = 0 # Stores the old value of offset on motions.
4863+ self._motion_handler = 0
4864+ self._active = None
4865+
4866+ self.step_size = self.get_abs_y(self.STEP_SIZE_PERCENT)
4867+
4868+ # Allowed area for the widget's scrolling content.
4869+ self.area_width = self.get_abs_x(width)
4870+ self.area_height = self.get_abs_y(height)
4871
4872 # Create content position indicator
4873- self.indicator = ListIndicator(3 * x_size_percent / 4, y_size_percent,
4874- 0.2, 0.045, ListIndicator.VERTICAL)
4875+ self.indicator = ListIndicator(3 * width / 4, height, 0.2, 0.045,
4876+ ListIndicator.VERTICAL)
4877 self.indicator.hide_position()
4878+ self.indicator.set_maximum(2)
4879 self.add(self.indicator)
4880
4881- if active:
4882- self.set_active(active)
4883-
4884- self.set_content(group)
4885-
4886- def set_active(self, boolean):
4887- """
4888- Set this widget active / inactive. When inactive indicator is hidden.
4889- """
4890- self.active = boolean
4891+ # A clipped Group to receive the content.
4892+ self._fixed_group = clutter.Group()
4893+ self._fixed_group.set_clip(0, 0, self.area_width, self.area_height)
4894+ self.add(self._fixed_group)
4895+ self.content = None
4896+
4897+ self._motion_timeline = clutter.Timeline(fps=200, duration=500)
4898+ self._motion_timeline.connect('completed',
4899+ self._motion_timeline_callback, None)
4900+ self._motion_alpha = clutter.Alpha(self._motion_timeline,
4901+ clutter.sine_inc_func)
4902+ self._motion_behaviour = LoopedPathBehaviour(self._motion_alpha)
4903+
4904+ self.set_content(content)
4905+
4906+ self.active = None
4907+
4908+ # Preparation to pointer events handling.
4909+ self.set_reactive(True)
4910+ self.connect('button-press-event', self._on_button_press_event)
4911+ self.connect('button-release-event', self._on_button_release_event)
4912+ self.connect('scroll-event', self._on_scroll_event)
4913+
4914+ self.set_position(self.get_abs_x(x), self.get_abs_y(y))
4915+
4916+ @property
4917+ def on_top(self):
4918+ """True if we're on top."""
4919+ return self._offset == 0
4920+
4921+ @property
4922+ def on_bottom(self):
4923+ """True if we're on bottom."""
4924+ return self._offset == self._offset_max
4925+
4926+ def _get_active(self):
4927+ """Active property getter."""
4928+ return self._active
4929+
4930+ def _set_active(self, boolean):
4931+ """Active property setter."""
4932+ if self._active == boolean:
4933+ return
4934+
4935+ self._active = boolean
4936 if boolean:
4937- self.indicator.show()
4938+ # Show indicator if there is need for scrolling.
4939+ if self._offset_max >= 0:
4940+ self.indicator.show()
4941+
4942+ self.set_opacity(255)
4943+ self.emit('activated')
4944 else:
4945 self.indicator.hide()
4946-
4947- def is_active(self):
4948- """
4949- Is this widget active.
4950- """
4951- return self.active
4952-
4953- def set_content(self, group):
4954- """
4955- Set content into scroll area.
4956- @param group: Group of clutter Actors (clutter.Group)
4957- """
4958- if self.content_group is not None:
4959- self.remove(self.content_group)
4960-
4961- self.content_group = group
4962- self.add(group)
4963- self.content_group.set_clip(0, 0, self.area_width, self.area_height)
4964- tmp = (self.content_group.get_height() -
4965- self.area_height) / self.step_size
4966- self.indicator.set_maximum(tmp + 2)
4967-
4968- # Do not show indicator if there is no need for scrolling
4969- if self.content_group.get_height() < self.area_height:
4970- self.indicator.hide()
4971-
4972- def set_step_size(self, size_percent):
4973- """Step size determines how much content is scrolled once."""
4974-
4975- self.step_size = self.get_abs_y(size_percent)
4976- if self.content_group is not None:
4977- tmp = (self.content_group.get_height() -
4978- self.area_height) / self.step_size
4979- self.indicator.set_maximum(tmp + 2)
4980-
4981- def get_step_size(self):
4982- """
4983- Get step size
4984- @requires: Step size (Integer)
4985- """
4986- return self.step_size
4987-
4988- def get_offset(self):
4989- """
4990- Get current offset value. This value determines how many pixels current
4991- view is from the absolute top.
4992- @return: Integer
4993- """
4994- return self.offset
4995+ self.set_opacity(128)
4996+
4997+ active = property(_get_active, _set_active)
4998+
4999+ def _get_offset(self):
5000+ """Get current offset value."""
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches