Merge lp:~samuel-buffet/entertainer/reactive_menus into lp:entertainer
- reactive_menus
- Merge into trunk
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 | ||||||||||||||||||||||||||||
Related bugs: |
|
||||||||||||||||||||||||||||
Related blueprints: |
Touchscreen support
(Essential)
|
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.
Description of the change
Samuel Buffet (samuel-buffet) wrote : | # |
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. ;)
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-
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-
Matt Layman (mblayman) wrote : | # |
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_
* _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_
* _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_
* 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_
* Could you expand the raw_add_item docstring? For example, wh...
- 508. By Samuel Buffet
-
Fixes after first set of comments from Matt (part 1).
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_
> 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_
> * _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_
> 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.
Samuel Buffet (samuel-buffet) wrote : | # |
incremental diff:
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -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.
"""
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -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.
from entertainerlib.
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__ = {
@@ -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):
- self.motion_
- self.cursor_below = True
+ self.motion_
+ self.cursor_below = True # Is the cursor below items?
- self.items_
+ self.items_
self.items = []
+ # Items dimensions variable: relative, absolute, center
@@ -40,23 +45,30 @@
self._dx = int(self.
self._dy = int(self.
+ # Default cursor's index.
+
+ # Grid dimensions: real, visible
+ # The moving_group is a Clutter group containing all the items.
+ # The moving_group is translated using a `BehaviourPath`.
moving_...
- 509. By Samuel Buffet
-
Fixed a stupid regression.
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).
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).
Samuel Buffet (samuel-buffet) wrote : | # |
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_
> 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_
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_
> aren't very descriptive) and determining boolean logic at the same tim...
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).
Matt Layman (mblayman) wrote : | # |
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_
* 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_
* No comment.
tab_group.py:
* No comment.
text_menu.py:
* "self.extra_
* 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...
- 511. By Samuel Buffet
-
Fixed another docstring in the GridMenu.
- 512. By Samuel Buffet
-
Fixes after Matt's comment round 2.
Samuel Buffet (samuel-buffet) wrote : | # |
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://
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_
> 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_
> 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 ...
Samuel Buffet (samuel-buffet) wrote : | # |
incremental diff:
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -71,7 +71,13 @@
- self._create_
+
+ self.li = ListIndicator(0.75, 0.8, 0.2, 0.045,
+ ListIndicator.
+ tracks = \
+ self.music_
+ self.li.
+ self.add(self.li)
@@ -185,13 +191,6 @@
return menu
- def _create_
- '''Create list indicator for track list.'''
- self.li = ListIndicator(0.75, 0.8, 0.2, 0.045, ListIndicator.
- tracks = self.music_
- self.li.
- self.add(self.li)
-
def _handle_up(self):
'''Handle UserEvent.
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -47,6 +47,8 @@
self.menu = self._create_
+ self._update_
+
@@ -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_
- """Create main menu of the home screen"""
+ """Create main menu of the home screen."""
menu = ScrollMenu(10, 60, 0.045, "menuitem_active")
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -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)
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -240,7 +240,10 @@
def _create_
'''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
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] |
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-