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

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

Commit message

All menus are now reactive to mouse pointer events.

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

Devs,

Finally the day has come.

I think it's now time for me to propose this work around menus.

The goal was first to make menu pointer reactive, but it turned out that I've done a GridMenu refactoring at the same time. For me it was just none sense to change the current GridMenu.

As menus are everywhere, the branch is quite big (the diff is around 5kline).
I know that this branch is far from being perfect, but if I wait longer then it will become very hard to land that work.

As it is not perfect, I'm prepared for a long and hard review with probably a lot of discussions.

But definitely, the work is enough mature to be proposed. A lot of other things can be done but it can be done later imho.

Good review.

Samuel-

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

Cripes!! This is going to take quite a long time to review. :)

Do you have suggestions of where to start? What's the core change? Some widget? GridMenu? I typically like to look at the core change, then examine everything that it affected. That might just be the entire frontend in this case, but I thought I'd ask in case there was hope for me. ;)

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

> Cripes!! This is going to take quite a long time to review. :)
>
> Do you have suggestions of where to start? What's the core change? Some
> widget? GridMenu? I typically like to look at the core change, then examine
> everything that it affected. That might just be the entire frontend in this
> case, but I thought I'd ask in case there was hope for me. ;)

Well, everything starts from the GridMenu which has been greatly refactored.
Then, the classes inheriting from it: ImageMenu, TextMenu.
Then maybe the relative menuitems.
And to finish the screens.

That might be a good order.

The GridMenu looks difficult but is quite simple in fact. There's only a couple of methods that will need a bit of abstraction (those linked to the cursor's motion).

Keep in mind that the GridMenu is a group containing a moving group which is translated on x,y and a cursor also translatable on x,y.

The rest of the code is really classic and easy to read.

Samuel-

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

I forgot to mention that there's also a new specif behaviour.

We now have 2, LoopedPathBehaviour and the new FontSizeBehaviour used to do a font size effect.

When clutter 1.0 will be released, we'll have quite a lot of work. Not only to change our code to the new API but also to see if there's no better way to do things with it.

Samuel-

Revision history for this message
Matt Layman (mblayman) wrote :
Download full text (3.7 KiB)

Well, here we go. Please keep in mind that these comments are basically everything that I could think of. I understand if not all comments are landed in this branch. This is just comments for the two files listed below (because it's all I'm going to review tonight). More comments will come later, but this is at least something to chew on.

base.py:
 * y_for_x docstring isn't very clear (gramatically). The word "than" just doesn't work well in that context. I think you could replace "than" with "equivalent to".
 * Could you add a test for this method?

grid_menu.py:
 * Is this widget testable? I've got to believe that somewhere in those 561 lines is something that can be tested. :)
 * import order. math is a Python standard library module.
 * Please add something meaningful to the class docstring.
 * I'm fairly lost by the init. There are so many instance variables. You seem to have some groupings, but could you add some comments to help us understand what those groupings are?
 * init seems to have some default width and heights. The values seem like they are pixels and not percentages. Why is that? Could they be percentages instead?
 * self.motion_duration in init: what is the unit of time for it? 100 is just a magic number right now.
 * _item_width_abs for the default value of item_width=200 will be enormous. Way larger than the stage.
 * event rect color of Orange. I like it. :D
 * When the branch lands, please file a bug about your XXX in the init. I'm okay with that XXX comment landing.
 * on_bottom & on_right: you could move the end_row calculation into the if statement of _is_vertical to slightly improve performance of horizontal menus. (Remember, these comments are *everything* I could think of :D).
 * _set_selected_index: "relatively" should be "relative"
 * _set_selected_index comment needs cleanup (start with "#start select...").
 * What is the cursor in _set_selected_index comments? Is that the mouse cursor? Is it some other cursor that is tracked by the GridMenu? I'm having a hard time figuring it out. Maybe there could be a comment by cursor property definition.
 * I got to the direction methods (up, down, left, right) and said to myself "WTF!? Why isn't there code to move something?" I then realized that the _set_selected_index method triggers the actual movement. I think it would be wise to mention this in the docstring.
 * Xc and Yc seems like the coordinates of "index" to me (line 234) and not like "coordinates of the cursor relatively to the moving group". Am I missing something?
 * Could you add some comments to the series of if statements in _set_selected_index? Mapping variable names in my head (because xm, Xc, and xc aren't very descriptive) and determining boolean logic at the same time is difficult to do. I'm sure the rules make sense, but it's hard as a reader to immediately understand what the code is trying to accomplish in those lines.
 * What is items_anchor_west? Everywhere that it is used in the code, it is set to False. And only one part of GridMenu uses it (raw_add_item). Therefore, the if portion of "if self.items_anchor_west" will never execute.
 * Could you expand the raw_add_item docstring? For example, wh...

Read more...

review: Needs Fixing
508. By Samuel Buffet

Fixes after first set of comments from Matt (part 1).

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

Evening Matt,

See my first set of fixes.

The fixes are for this part of your comments.
To be continued.

Samuel-

> base.py:
> * y_for_x docstring isn't very clear (gramatically). The word "than" just
> doesn't work well in that context. I think you could replace "than" with
> "equivalent to".
> * Could you add a test for this method?
>
> grid_menu.py:
> * Is this widget testable? I've got to believe that somewhere in those 561
> lines is something that can be tested. :)
> * import order. math is a Python standard library module.
> * Please add something meaningful to the class docstring.
> * I'm fairly lost by the init. There are so many instance variables. You seem
> to have some groupings, but could you add some comments to help us understand
> what those groupings are?
> * init seems to have some default width and heights. The values seem like
> they are pixels and not percentages. Why is that? Could they be percentages
> instead?
> * self.motion_duration in init: what is the unit of time for it? 100 is just
> a magic number right now.
> * _item_width_abs for the default value of item_width=200 will be enormous.
> Way larger than the stage.
> * event rect color of Orange. I like it. :D
> * When the branch lands, please file a bug about your XXX in the init. I'm
> okay with that XXX comment landing.
> * on_bottom & on_right: you could move the end_row calculation into the if
> statement of _is_vertical to slightly improve performance of horizontal menus.
> (Remember, these comments are *everything* I could think of :D).
> * _set_selected_index: "relatively" should be "relative"
> * _set_selected_index comment needs cleanup (start with "#start select...").
> * What is the cursor in _set_selected_index comments? Is that the mouse
> cursor? Is it some other cursor that is tracked by the GridMenu? I'm having a
> hard time figuring it out. Maybe there could be a comment by cursor property
> definition.
> * I got to the direction methods (up, down, left, right) and said to myself
> "WTF!? Why isn't there code to move something?" I then realized that the
> _set_selected_index method triggers the actual movement. I think it would be
> wise to mention this in the docstring.
> * Xc and Yc seems like the coordinates of "index" to me (line 234) and not
> like "coordinates of the cursor relatively to the moving group". Am I missing
> something?
> * Could you add some comments to the series of if statements in
> _set_selected_index? Mapping variable names in my head (because xm, Xc, and xc
> aren't very descriptive) and determining boolean logic at the same time is
> difficult to do. I'm sure the rules make sense, but it's hard as a reader to
> immediately understand what the code is trying to accomplish in those lines.

Revision history for this message
Samuel Buffet (samuel-buffet) wrote :
Download full text (17.4 KiB)

incremental diff:

=== modified file 'entertainerlib/frontend/gui/widgets/base.py'
--- entertainerlib/frontend/gui/widgets/base.py 2009-05-24 14:28:19 +0000
+++ entertainerlib/frontend/gui/widgets/base.py 2009-07-15 21:22:17 +0000
@@ -24,7 +24,7 @@
     def y_for_x(self, percentage):
         """
         Returns the percentage of stage height giving the same number of pixels
- than `percentage` applied to the stage width.
+ equivalent to `percentage` applied to the stage width.
         """
         stage_ratio = float(self.config.get_stage_width())
         stage_ratio /= self.config.get_stage_height()

=== modified file 'entertainerlib/frontend/gui/widgets/grid_menu.py'
--- entertainerlib/frontend/gui/widgets/grid_menu.py 2009-05-28 19:12:05 +0000
+++ entertainerlib/frontend/gui/widgets/grid_menu.py 2009-07-15 21:22:17 +0000
@@ -1,16 +1,20 @@
 # Copyright (c) 2009 Entertainer Developers - See COPYING - GPLv2
 '''GridMenu contains a grid of MenuItem based widgets.'''

+import math
+
 import clutter
 import gobject
-import math

 from entertainerlib.frontend.gui.widgets.base import Base
 from entertainerlib.frontend.gui.widgets.motion_buffer import MotionBuffer

 class GridMenu(Base, clutter.Group):
     """
- GridMenu widget
+ GridMenu widget.
+
+ A core widget to handle MenuItem in a grid with a cursor.
+ This widget provides all the necessary logic to move items and the cursor.
     """
     __gsignals__ = {
         'activated' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
@@ -22,17 +26,18 @@
     MODE_SELECT = 1
     MODE_SEEK = 2

- def __init__(self, x=0, y=0, item_width=200, item_height=100):
+ def __init__(self, x=0, y=0, item_width=0.2, item_height=0.1):
         Base.__init__(self)
         clutter.Group.__init__(self)

- self.motion_duration = 100
- self.cursor_below = True
+ self.motion_duration = 100 # Default duration for animations.
+ self.cursor_below = True # Is the cursor below items?
         self._active = None
         self._is_vertical = True
- self.items_anchor_west = False
+ self.items_anchor_west = False # Special anchor mode for items.
         self.items = []

+ # Items dimensions variable: relative, absolute, center
         self._item_width = item_width
         self._item_height = item_height
         self._item_width_abs = self.get_abs_x(item_width)
@@ -40,23 +45,30 @@
         self._dx = int(self._item_width_abs / 2)
         self._dy = int(self._item_height_abs / 2)

+ # Default cursor's index.
         self._selected_index = 0
+
+ # Grid dimensions: real, visible
         self.items_per_row = 10
         self.items_per_col = 10
         self._visible_rows = 3
         self._visible_cols = 5

+ # The moving_group is a Clutter group containing all the items.
         self._moving_group_x = 0
         self._moving_group_y = 0
         self._moving_group = clutter.Group()
         self.add(self._moving_group)

+ # The moving_group is translated using a `BehaviourPath`.
         self._moving_group_timeline = clutter.Timeline(fps=200, duration=200)
         moving_...

509. By Samuel Buffet

Fixed a stupid regression.

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

Review comment of the day:

image_menu.py:
 * I need to raise a point that will be contentious. I have to disagree with putting specific logic for videos/albums/clips embedded into a widget. IMO, the only async_add method should be async_add, not async_add_*. I know that this puts more burden on the screen objects, but I strongly believe that classes should serve only the purpose that they were intended for. In the case of the ImageMenu, I think it should only know how to display images that are provided to it. I think that the easy solution would be to move the asyn specific method back into the screen/tab that it came from.

I have a decent idea for how to keep the async_add functionality in ImageMenu, but keep the specific logic out. We can talk about it on IRC. It involves concrete factories as input parameters to async_add (if that makes any sense to you right now, then our conversation on IRC will be a short one).

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

Hi Matt,

> image_menu.py:
> * I need to raise a point that will be contentious. I have to disagree with
> putting specific logic for videos/albums/clips embedded into a widget. IMO,
> the only async_add method should be async_add, not async_add_*. I know that
> this puts more burden on the screen objects, but I strongly believe that
> classes should serve only the purpose that they were intended for. In the case
> of the ImageMenu, I think it should only know how to display images that are
> provided to it. I think that the easy solution would be to move the asyn
> specific method back into the screen/tab that it came from.

I have to agree with you, this was also my concern doing that. I was pretty happy to be able to add items asynchronously but definitely not with adding specific methods for specific items.

Now questions are, what to do instead, where (I'd prefer to do that in another branch if it's okay for you).

> I have a decent idea for how to keep the async_add functionality in ImageMenu,
> but keep the specific logic out. We can talk about it on IRC. It involves
> concrete factories as input parameters to async_add (if that makes any sense
> to you right now, then our conversation on IRC will be a short one).

Okay, let's talk of tha on IRC.

Samuel-

(missing tests for text and image menus are on the road.)

510. By Samuel Buffet

Fixes after first set of comments from Matt (part 2).

Revision history for this message
Samuel Buffet (samuel-buffet) wrote :
Download full text (4.9 KiB)

Matt,

I've pushed my second part of fixes.

> base.py:
> * y_for_x docstring isn't very clear (gramatically). The word "than" just
> doesn't work well in that context. I think you could replace "than" with
> "equivalent to".
> * Could you add a test for this method?

Fixed.

> grid_menu.py:
> * Is this widget testable? I've got to believe that somewhere in those 561
> lines is something that can be tested. :)

Yeah of course. Added. (other menus will come).

> * import order. math is a Python standard library module.
> * Please add something meaningful to the class docstring.

Fixed.

> * I'm fairly lost by the init. There are so many instance variables. You seem
> to have some groupings, but could you add some comments to help us understand
> what those groupings are?

I've added more comments. It should be better.

> * init seems to have some default width and heights. The values seem like
> they are pixels and not percentages. Why is that? Could they be percentages
> instead?

Yeah it was a mistake. That's fixed.

> * self.motion_duration in init: what is the unit of time for it? 100 is just
> a magic number right now.

Here it's a regular timeline duration in ms.

> * _item_width_abs for the default value of item_width=200 will be enormous.
> Way larger than the stage.
> * event rect color of Orange. I like it. :D

:D Saddly it was a debug fossil so I've removed that line.

> * When the branch lands, please file a bug about your XXX in the init. I'm
> okay with that XXX comment landing.

New that. That was my intention.

> * on_bottom & on_right: you could move the end_row calculation into the if
> statement of _is_vertical to slightly improve performance of horizontal menus.
> (Remember, these comments are *everything* I could think of :D).

Changed.

> * _set_selected_index: "relatively" should be "relative"

Also fixed.

> * _set_selected_index comment needs cleanup (start with "#start select...").

Fixed.

> * What is the cursor in _set_selected_index comments? Is that the mouse
> cursor? Is it some other cursor that is tracked by the GridMenu? I'm having a
> hard time figuring it out. Maybe there could be a comment by cursor property
> definition.

I've change the docstring to specify that we're dealing with the menu's cursor.
Cursor's coordinates are linked to the cursor's index (index in the item list) for a specific menu geometry. So if we have x, y = 0, 0 (which is equivalent to index = 0) the the cursor is on top/left "Cell" of the menu.

> * I got to the direction methods (up, down, left, right) and said to myself
> "WTF!? Why isn't there code to move something?" I then realized that the
> _set_selected_index method triggers the actual movement. I think it would be
> wise to mention this in the docstring.

Done.

> * Xc and Yc seems like the coordinates of "index" to me (line 234) and not
> like "coordinates of the cursor relatively to the moving group". Am I missing
> something?
> * Could you add some comments to the series of if statements in
> _set_selected_index? Mapping variable names in my head (because xm, Xc, and xc
> aren't very descriptive) and determining boolean logic at the same tim...

Read more...

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

Samuel,

You don't need to bother with adding new diffs for me. I either use LP loggerhead to look at the commit or I just look at the branch when I'm merging the code and testing it.

I'll hopefully get some more time to look at it when I get home from work today (more than 8 hours from now).

Revision history for this message
Matt Layman (mblayman) wrote :
Download full text (6.0 KiB)

Round 2
=======

grid_menu.py:
 * I counted 32 instance variables in use declared by the init. I know that GridMenu does a ton of stuff and I have no expectations for major refactorings of this branch, but maybe there are some class abstractions that can be extracted out somewhere else (because there's a lot of stuff to think about in this class). Maybe you have some thoughts on this subject. What do you think? Is there future common classes that could be pulled out from GridMenu?
 * Line 80 comment "usefull" should be "useful" (English is silly, I know. :)
 * _set_selected_index comment needs cleanup (start with "#start select..."). Still not fixed.
 * I think I may have push you too far with the comments for the series of if statements in _set_selected_index. Rather than a textual description of what the logic is saying for each if statement, I think what I meant to ask for is "What are the if statements doing as a whole?" Once I have an idea of what the if statements are supposed to accomplish, I should have a good idea of how to read the code. In all of these if statements, it looks like you're adjusting xm, ym, xc, or yc. Maybe these if statements could be extracted into a method to help explain what the section is supposed to do. As a good rule of thumb that I use personally, if you have to write more lines of comments than code, something probably needs to be changed.
 * I caught one more items_anchor_west still in the init.

image_menu.py:
 * You've already seen my comment about the specific types of async_add. I'll let it land, but please add an XXX comment so that we remember to come back to it.
 * async_add items are a pack of information, but there is no comment explaining what an item is (texture and data). I think that this could use a clarifying comment.

image_menu_item.py:
 * Glad you condensed this class into the image menu file.

label.py:
 * Can you add a color property test?

menu.py:
 * One more unneeded widget down!

menu_item.py:
 * No comment.

scroll_menu.py:
 * No comment.

selector.py:
 * No comment.

special_behaviours.py:
 * No comment.

tab_group.py:
 * No comment.

text_menu.py:
 * "self.extra_label.position = ( \" Is the continuation character helpful here?
 * ArtistsMenuItem. I don't think this needs to be specific to artists (even though it may be the only place it is used right now). How about AnimatingMenuItem instead?
 * self.timeline is only used by ArtistsMenuItem, not the parent TextMenuItem. Could you please move it to the subclass?
 * add_item also calls a mysterious pack item. A comment might help.
 * TextMenu also has the context specific async_add_artists method. I'll let this land too, but please add an XXX comment here too.

text_menu_item.py:
 * 1 less file, nice.

music.py:
 * I did this by mistake too in a different branch that I was working on (and should hopefully finish some day), but get_length_string should not zfill the first value. It should just zfill the second (otherwise it means there is a leading zero on the minute when we don't need there to be one). The places that in-lined this code only used one zfill as expected.

mock.py:
 * No comment.

test_base.py:
 * No comment.

test_gr...

Read more...

review: Approve
511. By Samuel Buffet

Fixed another docstring in the GridMenu.

512. By Samuel Buffet

Fixes after Matt's comment round 2.

Revision history for this message
Samuel Buffet (samuel-buffet) wrote :
Download full text (6.9 KiB)

Evening Matt,

> grid_menu.py:
> * I counted 32 instance variables in use declared by the init. I know that
> GridMenu does a ton of stuff and I have no expectations for major refactorings
> of this branch, but maybe there are some class abstractions that can be
> extracted out somewhere else (because there's a lot of stuff to think about in
> this class). Maybe you have some thoughts on this subject. What do you think?
> Is there future common classes that could be pulled out from GridMenu?

Short answer Yes.
Indeed this is something I have in mind since a long time. I'm deeply convinced that there's a lot of line of code we can save and a much better readability we can reach doing a widget API refactoring which would take better usage of inheritance.
>>> http://wiki.entertainer-project.com/wiki/BluePrints/widget-API-changes
This is still in my mind (a few updates needed though).

> * Line 80 comment "usefull" should be "useful" (English is silly, I know. :)

Fixed.

> * _set_selected_index comment needs cleanup (start with "#start select...").
> Still not fixed.

Was fixed in a standing alone commit.

> * I think I may have push you too far with the comments for the series of if
> statements in _set_selected_index. Rather than a textual description of what
> the logic is saying for each if statement, I think what I meant to ask for is
> "What are the if statements doing as a whole?" Once I have an idea of what the
> if statements are supposed to accomplish, I should have a good idea of how to
> read the code. In all of these if statements, it looks like you're adjusting
> xm, ym, xc, or yc. Maybe these if statements could be extracted into a method
> to help explain what the section is supposed to do. As a good rule of thumb
> that I use personally, if you have to write more lines of comments than code,
> something probably needs to be changed.

Agreed, so what I propose to you is that I'll create a wiki page with drawings and text to better explain that. And when it will be totally clear for you then maybe you'll be able to help be to find better words. I'll try to do that this week.

> * I caught one more items_anchor_west still in the init.

Yep, fixed now.

> image_menu.py:
> * You've already seen my comment about the specific types of async_add. I'll
> let it land, but please add an XXX comment so that we remember to come back to
> it.

Done.

> * async_add items are a pack of information, but there is no comment
> explaining what an item is (texture and data). I think that this could use a
> clarifying comment.

Done.

> label.py:
> * Can you add a color property test?

Indeed that was missing. I've added a test for this property.

> text_menu.py:
> * "self.extra_label.position = ( \" Is the continuation character helpful
> here?

Absolutely not. That's fixed.

> * ArtistsMenuItem. I don't think this needs to be specific to artists (even
> though it may be the only place it is used right now). How about
> AnimatingMenuItem instead?

Yes you're right. I've renamed this Class.

> * self.timeline is only used by ArtistsMenuItem, not the parent TextMenuItem.
> Could you please move it to the subclass?

Agreed with ...

Read more...

Revision history for this message
Samuel Buffet (samuel-buffet) wrote :
Download full text (17.9 KiB)

incremental diff:

=== modified file 'entertainerlib/frontend/gui/screens/disc.py'
--- entertainerlib/frontend/gui/screens/disc.py 2009-07-12 07:57:05 +0000
+++ entertainerlib/frontend/gui/screens/disc.py 2009-07-19 19:27:31 +0000
@@ -71,7 +71,13 @@
             self.track_menu = self._create_track_menu(tracks)
             self.add(self.track_menu)
             self._create_album_cover_texture(artist, title)
- self._create_list_indicator()
+
+ self.li = ListIndicator(0.75, 0.8, 0.2, 0.045,
+ ListIndicator.VERTICAL)
+ tracks = \
+ self.music_library.get_compact_disc_information().get_tracks()
+ self.li.set_maximum(len(tracks))
+ self.add(self.li)

             art_file = os.path.join(self.config.ALBUM_ART_DIR,
                 artist + " - " + title + ".jpg")
@@ -185,13 +191,6 @@

         return menu

- def _create_list_indicator(self):
- '''Create list indicator for track list.'''
- self.li = ListIndicator(0.75, 0.8, 0.2, 0.045, ListIndicator.VERTICAL)
- tracks = self.music_library.get_compact_disc_information().get_tracks()
- self.li.set_maximum(len(tracks))
- self.add(self.li)
-
     def _handle_up(self):
         '''Handle UserEvent.NAVIGATE_UP.'''
         self.track_menu.up()

=== modified file 'entertainerlib/frontend/gui/screens/main.py'
--- entertainerlib/frontend/gui/screens/main.py 2009-06-29 05:25:01 +0000
+++ entertainerlib/frontend/gui/screens/main.py 2009-07-19 19:27:31 +0000
@@ -47,6 +47,8 @@
         self.menu = self._create_main_menu()
         self.add(self.menu)

+ self._update_preview_area()
+
         self.add(ClockLabel(0.13, "screentitle", 0, 0.87))

         self.menu.connect('selected', self._handle_select)
@@ -60,11 +62,11 @@
         return Screen.NORMAL

     def get_name(self):
- """Return screen name (human readble)"""
+ """Return screen name (human readble)."""
         return "Main"

     def _create_main_menu(self):
- """Create main menu of the home screen"""
+ """Create main menu of the home screen."""
         menu = ScrollMenu(10, 60, 0.045, "menuitem_active")
         menu.set_name("mainmenu")

=== modified file 'entertainerlib/frontend/gui/screens/movie.py'
--- entertainerlib/frontend/gui/screens/movie.py 2009-07-12 07:57:05 +0000
+++ entertainerlib/frontend/gui/screens/movie.py 2009-07-19 19:27:31 +0000
@@ -155,7 +155,7 @@
         """
         Create menu of the screen. This displayed the left side of the screen.
         """
- self.menu = TextMenu(0.07, 0.1, 0.2196, 0.0781)
+ self.menu = TextMenu(0.07, 0.1, 0.2196, 0.0781)
         self.menu.add_item(_("Watch"), None, "watch")
         self.menu.active = True

=== modified file 'entertainerlib/frontend/gui/screens/photo_albums.py'
--- entertainerlib/frontend/gui/screens/photo_albums.py 2009-07-12 07:57:05 +0000
+++ entertainerlib/frontend/gui/screens/photo_albums.py 2009-07-19 19:27:31 +0000
@@ -240,7 +240,10 @@
     def _create_list_indicator(self):
         '''Create list indicator for albums list.'''
         self.li = ListIndicator(0.77, 0.9, 0.2, 0.045, ListIndic...

513. By Samuel Buffet

Removed a redundant "tracks" variable in disc.py.

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

Subscribers

People subscribed via source and target branches