Merge lp://staging/~samuel-buffet/entertainer/reactive_tabs into lp://staging/entertainer
- reactive_tabs
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Matt Layman | ||||
Approved revision: | 405 | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp://staging/~samuel-buffet/entertainer/reactive_tabs | ||||
Merge into: | lp://staging/entertainer | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp://staging/~samuel-buffet/entertainer/reactive_tabs | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Matt Layman | Approve | ||
Review via email: mp+6315@code.staging.launchpad.net |
Commit message
Tabs are now reactive to pointer events.
Description of the change
Samuel Buffet (samuel-buffet) wrote : | # |
- 380. By Samuel Buffet
-
merged with trunk 376
Matt Layman (mblayman) wrote : | # |
I really liked the results of this branch Samuel. The few added python properties went a long way to cleaning up some code that used to be fairly awkward and confusing. The fade in and fade out will have to grow on me a little (I'm just jaded from the old transition), but I'm fine with the change you made. Like I said on IRC, it only looks slightly weird (IMO) when there are multiple "no content" message between tabs. Here are my specific review comments:
General:
* Need a full stop for docstrings (since they are normal English sentences).
* _on_activate should be _on_activated because the event is 'activated.' This will affect the doc string too ('''Tab activated.''').
entertainerlib/
* _update_series_info doc string. There is no such thing as a TV-Serie. Series always has an 's' on the end in English. I know this one is just a carry over from the previous doc string, but now is a good time to correct the spelling mistake.
entertainerlib/
* Tab inherits from Base which inherits from object so there is no need for Tab to directly inherit from object.
entertainerlib/
* TabGroup inherits from Base which inherits from object so there is no need for TabGroup to directly inherit from object.
* If you're inheriting from Base, there is no need for another call to "self.config = Configuration()" because that happens in the init of Base.
* Could you add a comment about the timelines code in _activate_tab? I had to comment out the lines to see that they affected the label name of each tab. It wasn't very clear what that code did.
I noticed that there are no tests for this branch either. I'm going to approve this code because I'm not sure exactly what you could test. However, I will leave it up to your discretion to write tests that might be helpful for the long term maintenance of this code. Maybe you could test the boundary conditions of the TabGroup? Simulate clicking new tabs and see if the current tab got updated correctly? I think that there are some worthwhile things here to test, and I hope that you agree and can come up with some stuff to add to the test suite.
Thanks,
Matt
- 381. By Samuel Buffet
-
- docstrings update in album.tab.py
- self._on_activate is now self._on_activated - 382. By Samuel Buffet
-
- docstrings update in artists_tab.py
- self._on_activate is now self._on_activated - 383. By Samuel Buffet
-
- docstrings update in lyrics_tab.py
- self._on_activate is now self._on_activated - 384. By Samuel Buffet
-
- docstrings update in movies_tab.py
- self._on_activate is now self._on_activated - 385. By Samuel Buffet
-
- docstrings update in series_tab.py
- self._on_activate is now self._on_activated - 386. By Samuel Buffet
-
- docstrings update in tracks_tab.py
- self._on_activate is now self._on_activated - 387. By Samuel Buffet
-
- docstrings update in video_clips_tab.py
- self._on_activate is now self._on_activated - 388. By Samuel Buffet
-
docstring update in series_tab.py because TV-serie doesn't exist in English.
- 389. By Samuel Buffet
-
- removed object in Tab and TabGroup Classes (they inherit from Base which already inherit from object)
- removed self.config = Configuration() from TabGroup __init__ because that's already done in Base's __init__ - 390. By Samuel Buffet
-
lint fix
- 391. By Samuel Buffet
-
comments added in tab_group.py to give more information about what we do with timelines in the _activate_tab method.
- 392. By Samuel Buffet
-
a test has been created for the TabGroup Class
- 393. By Samuel Buffet
-
- some fixes because the Tab Class uses now an active property
- Screen is fixed because the TabGroup Class uses an active property
- creation of the active property on TabGroup
- 2 new methods added to the music library mock
- TabGroup test improvments - 394. By Samuel Buffet
-
removed the useless resize_widget method
- 395. By Samuel Buffet
-
2 new tests added to test_tabgroup.py
- test_add_tab and
- test_keyboard_navigation - 396. By Samuel Buffet
-
removed some forgotten set_active
- 397. By Samuel Buffet
-
fixed a forgotten _on_activate in series_tab.py
- 398. By Samuel Buffet
-
docstrings clean up to comply with standards.
- 399. By Samuel Buffet
-
- created an activated signal for the TabGroup Class
- removed the deactivated signal for the Tab Class - 400. By Samuel Buffet
-
- fix the tests
- tabgroup change its opacity accordingly to is state active/deactive
- clean up
Samuel Buffet (samuel-buffet) wrote : | # |
Matt,
> I really liked the results of this branch Samuel. The few added python
> properties went a long way to cleaning up some code that used to be fairly
> awkward and confusing. The fade in and fade out will have to grow on me a
> little (I'm just jaded from the old transition), but I'm fine with the change
> you made. Like I said on IRC, it only looks slightly weird (IMO) when there
> are multiple "no content" message between tabs. Here are my specific review
> comments:
>
> General:
> * Need a full stop for docstrings (since they are normal English sentences).
> * _on_activate should be _on_activated because the event is 'activated.' This
> will affect the doc string too ('''Tab activated.''').
Yeah, I don't know why I've choosen on_activate for the "activated" signal. I surly was dreaming a bit.
>
> entertainerlib/
> * _update_series_info doc string. There is no such thing as a TV-Serie.
> Series always has an 's' on the end in English. I know this one is just a
> carry over from the previous doc string, but now is a good time to correct the
> spelling mistake.
Fixed.
> entertainerlib/
> * Tab inherits from Base which inherits from object so there is no need for
> Tab to directly inherit from object.
Yes totally right, thanks.
> entertainerlib/
> * TabGroup inherits from Base which inherits from object so there is no need
> for TabGroup to directly inherit from object.
> * If you're inheriting from Base, there is no need for another call to
> "self.config = Configuration()" because that happens in the init of Base.
Yes totally again.
> * Could you add a comment about the timelines code in _activate_tab? I had to
> comment out the lines to see that they affected the label name of each tab. It
> wasn't very clear what that code did.
Comment added.
> I noticed that there are no tests for this branch either. I'm going to approve
> this code because I'm not sure exactly what you could test. However, I will
> leave it up to your discretion to write tests that might be helpful for the
> long term maintenance of this code. Maybe you could test the boundary
> conditions of the TabGroup? Simulate clicking new tabs and see if the current
> tab got updated correctly? I think that there are some worthwhile things here
> to test, and I hope that you agree and can come up with some stuff to add to
> the test suite.
Yes Matt, I fully agree with you once more. I've created test for this widget as you will see in the diff. But not really for the reactive part of the widget (for the standard one). I'm missing inspiration for the reactive part, I've not found a good way to do that so I'll come to that later.
See the incremental diff which is a bit big because I took the opportunity to change/fix a few things I should have done before.
Samuel-
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -39,7 +39,7 @@
Override function from Screen class. See Screen class for
...
Matt Layman (mblayman) wrote : | # |
Samuel,
Here are my last few follow up comments after reviewing your branch again following all of my initial input.
albums_tab.py
* redundant gobject.timeout_add was removed from artists_tab so it should probably be removed from albums_tab too.
movies_tab.py
* In _update_movie_info, the text for movie_info should be translatable, but it currently isn't. Can you put the value of the "set_text" into translatable format?
tab.py
* self._active = None seem problematic for init. What happens if something tries to call the active property before you set the value? Shouldn't we default to False initially?
tab_group.py
* Same comment as tab.py
test_tabgroup.py
* tearDown isn't necessary since you aren't doing anything that the superclass isn't already doing.
- 401. By Samuel Buffet
-
removed the tearDown method of test_tabgroup.py
- 402. By Samuel Buffet
-
small modification in albums_tab.py to avoid a useless _init_selected_
album. - 403. By Samuel Buffet
-
movies_tab.py, improvments in _update_movie_info.
- 404. By Samuel Buffet
-
The MockMusicLibrary is modified in order to provide a MockAlbum in return of get_albums_
by_artist.
This was mandatory to fix the test__generate_artist - 405. By Samuel Buffet
-
- artist screen pass test as I've added a protection against empty results
- Tab & TabGroup Classes will return False on the first attempt to read the active property
- RAZ of what I've done in mock.py
Samuel Buffet (samuel-buffet) wrote : | # |
Hi Matt,
All your points are very good questions. See bellow my fixes and explanations.
> albums_tab.py
> * redundant gobject.timeout_add was removed from artists_tab so it should
> probably be removed from albums_tab too.
Fixed.
> movies_tab.py
> * In _update_movie_info, the text for movie_info should be translatable, but
> it currently isn't. Can you put the value of the "set_text" into translatable
> format?
Fixed using the gettext macro _() and using a formatted string.
> tab.py
> * self._active = None seem problematic for init. What happens if something
> tries to call the active property before you set the value? Shouldn't we
> default to False initially?
>
> tab_group.py
> * Same comment as tab.py
Here, there's an explanation to have None in the __init__ and not False (but the point really makes sense). Indeed, if self._active is set to False then if we try to do Tab.active = False later on then we wont run the entire setter's code because of the IF statement at the beginning.
The IF statement is needed to run the setter's code only when there's a change in the property's value. This is really frequent on every language using object's properties.
So look the new incremental diff, I've fixed the point keeping that None initialization of self._active
> test_tabgroup.py
> * tearDown isn't necessary since you aren't doing anything that the
> superclass isn't already doing.
Removed.
the new new incremental diff (very short this time).
Thanks for the review.
Samuel-
inc_diff :
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -26,13 +26,14 @@
# Tabs
albums = music_library.
- tab1 = AlbumsTab(albums, move_to_
+ if albums:
+ tab1 = AlbumsTab(albums, move_to_
+ self.add_tab(tab1)
tracks = music_library.
- tab2 = TracksTab(tracks, move_to_
-
- self.add_tab(tab1)
- self.add_tab(tab2)
+ if tracks:
+ tab2 = TracksTab(tracks, move_to_
+ self.add_tab(tab2)
def is_interested_
"""
=== modified file 'entertainerlib
--- entertainerlib/
+++ entertainerlib/
@@ -62,7 +62,8 @@
# Default texture
- gobject.
+ self._async_
+ self._selected_
@@ -95,9 +96,6 @@
- self._sel...
Samuel Buffet (samuel-buffet) wrote : | # |
Forgot to mention that the modification on the artist screen was done to fix Test.
For some reason, the generation test of this screen was failing (due to changes on the AlbumTab). The failure itself was due to the fact that this screen does not tolerate an empty music_library on creation (and we mock and empty library). This is not a big issue as to access that screen we have to go first through the music screen which HAS this protection (creating an empty notice in that case).
But anyway, I've added to that screen an internal protection.
Samuel-
Samuel Buffet (samuel-buffet) wrote : | # |
Matt I don't understand what you mean with?
"* tearDown isn't necessary since you aren't doing anything that the superclass isn't already doing."
I don't get when we have to do that tearDown and when we don't have to do it.
Samuel-
Matt Layman (mblayman) wrote : | # |
Sure Samuel, I can explain. A unittest test will always run the following
for each test (specifically, I think the TestLoader is delegating the work
and running each TestCase):
1) setUp
2) The actual test method
3) tearDown
Our test class, EntertainerTest, is a subclass of testtools.TestCase, which
is a subclass of unittest.TestCase. Just like any other subclasses in
Python, method resolution order will try to execute the lowest level class
first. So if you run a test on Foo() in FooTest below, the execution of
setUp will call FooTest.setUp() -> EntertainerTest
testtools.
We need the setUp method for FooTest because we need to have self.foo
accessible. However, foo doesn't need to do anything special to clean up so
FooTest doesn't have any extra code to write. Therefore, when the loader
tries to execute tearDown, it will "see" that no tearDown exists for FooTest
so it will attempt to execute the method from the superclass. Since
EntertainerTest has a tearDown method, the loader happily executes it.
In summary, tearDown is not need when all you would have is:
def tearDown(self):
Entertainer
but is necessary when your test has:
def tearDown(self):
Entertainer
<do clean up stuff for this TestCase here>
I hope this makes sense. Please let me know if you have more questions.
-Matt
from foo import Foo
from entertainerlib.
class FooTest(
def setUp(self):
self.foo = Foo()
def test_create(self)
On Tue, May 12, 2009 at 3:03 PM, Samuel Buffet <email address hidden>wrote:
> Matt I don't understand what you mean with?
>
> "* tearDown isn't necessary since you aren't doing anything that the
> superclass isn't already doing."
>
> I don't get when we have to do that tearDown and when we don't have to do
> it.
>
> Samuel-
> --
>
> https:/
> You are reviewing the proposed merge of
> lp:~samuel-buffet/entertainer/reactive_tabs into lp:entertainer.
>
Preview Diff
1 | === modified file 'entertainerlib/frontend/gui/screens/artist.py' |
2 | --- entertainerlib/frontend/gui/screens/artist.py 2009-04-27 03:32:30 +0000 |
3 | +++ entertainerlib/frontend/gui/screens/artist.py 2009-05-05 11:18:31 +0000 |
4 | @@ -33,7 +33,6 @@ |
5 | |
6 | tracks = music_library.get_tracks_by_artist(artist) |
7 | tab2 = TracksTab(tracks, move_to_new_screen_callback) |
8 | - tab2.hide_tab() |
9 | |
10 | self.add_tab(tab1) |
11 | self.add_tab(tab2) |
12 | |
13 | === modified file 'entertainerlib/frontend/gui/screens/music.py' |
14 | --- entertainerlib/frontend/gui/screens/music.py 2009-04-27 03:32:30 +0000 |
15 | +++ entertainerlib/frontend/gui/screens/music.py 2009-05-05 11:18:31 +0000 |
16 | @@ -34,7 +34,6 @@ |
17 | move_to_new_screen_callback) |
18 | tab2 = AlbumsTab(music_library.get_all_albums(), |
19 | move_to_new_screen_callback) |
20 | - tab2.hide_tab() |
21 | |
22 | self.add_tab(tab1) |
23 | self.add_tab(tab2) |
24 | |
25 | === modified file 'entertainerlib/frontend/gui/tabs/albums_tab.py' |
26 | --- entertainerlib/frontend/gui/tabs/albums_tab.py 2009-02-03 23:37:57 +0000 |
27 | +++ entertainerlib/frontend/gui/tabs/albums_tab.py 2009-04-22 10:28:29 +0000 |
28 | @@ -102,6 +102,9 @@ |
29 | self._selected_album = None |
30 | gobject.timeout_add(50, self._init_selected_album) |
31 | |
32 | + self.connect('activated', self._on_activate) |
33 | + self.connect('deactivated', self._on_deactivated) |
34 | + |
35 | def _async_menu_build(self, albums, item_size_percent): |
36 | """ |
37 | This function is called as an async callback. It is also a recursive |
38 | @@ -152,30 +155,23 @@ |
39 | least one album''' |
40 | return True |
41 | |
42 | - def set_active(self, boolean): |
43 | - """ |
44 | - Override method from parent class. |
45 | - @param boolean: True to set this tab active, False to set inactive |
46 | - """ |
47 | - Tab.set_active(self, boolean) |
48 | - if boolean: |
49 | - self._update_album_info( |
50 | - self.menu.get_current_menuitem().get_userdata()) |
51 | - self.menu.set_active(True) |
52 | - else: |
53 | - self.menu.set_active(False) |
54 | - |
55 | - def _update_album_info(self, album): |
56 | + def _update_album_info(self): |
57 | '''Update the album information labels''' |
58 | - self.album_title.set_text(album.get_title()) |
59 | - self.album_artist.set_text(album.get_artist()) |
60 | - self.album_tracks.set_text(_("%(total)s tracks") % \ |
61 | - {'total': album.get_number_of_tracks()}) |
62 | + if self.active: |
63 | + album = self.menu.get_current_menuitem().get_userdata() |
64 | + self.album_title.set_text(album.get_title()) |
65 | + self.album_artist.set_text(album.get_artist()) |
66 | + self.album_tracks.set_text(_("%(total)s tracks") % \ |
67 | + {'total': album.get_number_of_tracks()}) |
68 | + else: |
69 | + self.album_title.set_text("") |
70 | + self.album_artist.set_text("") |
71 | + self.album_tracks.set_text("") |
72 | |
73 | def _update_menu(self, direction): |
74 | '''Update the menu based on the provided menu direction''' |
75 | self.menu.move(direction) |
76 | - self._update_album_info(self.menu.get_current_menuitem().get_userdata()) |
77 | + self._update_album_info() |
78 | self.li.set_current(self.menu.get_current_position() + 1) |
79 | return False |
80 | |
81 | @@ -205,3 +201,17 @@ |
82 | self.callback("album", kwargs) |
83 | return False |
84 | |
85 | + def _on_activate(self, event=None): |
86 | + '''Tab activation''' |
87 | + self._update_album_info() |
88 | + self.menu.set_active(True) |
89 | + self.menu.set_opacity(255) |
90 | + return False |
91 | + |
92 | + def _on_deactivated(self, event=None): |
93 | + '''Tab deactivated''' |
94 | + self._update_album_info() |
95 | + self.menu.set_active(False) |
96 | + self.menu.set_opacity(128) |
97 | + return False |
98 | + |
99 | |
100 | === modified file 'entertainerlib/frontend/gui/tabs/artists_tab.py' |
101 | --- entertainerlib/frontend/gui/tabs/artists_tab.py 2009-02-03 23:37:57 +0000 |
102 | +++ entertainerlib/frontend/gui/tabs/artists_tab.py 2009-04-22 10:28:29 +0000 |
103 | @@ -73,6 +73,9 @@ |
104 | self._selected_artist = None |
105 | gobject.timeout_add(25, self._init_selected_artist) |
106 | |
107 | + self.connect('activated', self._on_activate) |
108 | + self.connect('deactivated', self._on_deactivated) |
109 | + |
110 | def _async_menu_build(self, artists): |
111 | """ |
112 | This function is called as an async callback. It is also a recursive |
113 | @@ -118,32 +121,24 @@ |
114 | least one album''' |
115 | return True |
116 | |
117 | - def set_active(self, boolean): |
118 | - """ |
119 | - Override method from parent class. |
120 | - @param boolean: True to set this tab active, False to set inactive |
121 | - """ |
122 | - Tab.set_active(self, boolean) |
123 | - if boolean: |
124 | - self._update_artist_info( |
125 | - self.menu.get_current_menuitem().get_userdata()) |
126 | - self.menu.set_active(True) |
127 | - else: |
128 | - self.menu.set_active(False) |
129 | - |
130 | - def _update_artist_info(self, artist): |
131 | + def _update_artist_info(self): |
132 | '''Update the artist information labels''' |
133 | - self.artist_title.set_text(artist) |
134 | - self.artist_albums.set_text(_("%(albums)d albums") % |
135 | - {'albums': self.library.number_of_albums_by_artist(artist)}) |
136 | - self.artist_tracks.set_text(_("%(tracks)d tracks") % |
137 | - {'tracks': self.library.number_of_tracks_by_artist(artist)}) |
138 | + if self.active: |
139 | + artist = self.menu.get_current_menuitem().get_userdata() |
140 | + self.artist_title.set_text(artist) |
141 | + self.artist_albums.set_text(_("%(albums)d albums") % |
142 | + {'albums': self.library.number_of_albums_by_artist(artist)}) |
143 | + self.artist_tracks.set_text(_("%(tracks)d tracks") % |
144 | + {'tracks': self.library.number_of_tracks_by_artist(artist)}) |
145 | + else: |
146 | + self.artist_title.set_text("") |
147 | + self.artist_albums.set_text("") |
148 | + self.artist_tracks.set_text("") |
149 | |
150 | def _update_menu(self, direction): |
151 | '''Update the menu based on the provided menu direction''' |
152 | self.menu.move(direction) |
153 | - self._update_artist_info( |
154 | - self.menu.get_current_menuitem().get_userdata()) |
155 | + self._update_artist_info() |
156 | self.li.set_current(self.menu.get_current_position() + 1) |
157 | return False |
158 | |
159 | @@ -173,3 +168,17 @@ |
160 | self.callback("artist", kwargs) |
161 | return False |
162 | |
163 | + def _on_activate(self, event=None): |
164 | + '''Tab activation''' |
165 | + self._update_artist_info() |
166 | + self.menu.set_active(True) |
167 | + self.menu.set_opacity(255) |
168 | + return False |
169 | + |
170 | + def _on_deactivated(self, event=None): |
171 | + '''Tab deactivated''' |
172 | + self._update_artist_info() |
173 | + self.menu.set_active(False) |
174 | + self.menu.set_opacity(128) |
175 | + return False |
176 | + |
177 | |
178 | === modified file 'entertainerlib/frontend/gui/tabs/lyrics_tab.py' |
179 | --- entertainerlib/frontend/gui/tabs/lyrics_tab.py 2009-04-21 21:04:56 +0000 |
180 | +++ entertainerlib/frontend/gui/tabs/lyrics_tab.py 2009-05-07 16:39:54 +0000 |
181 | @@ -32,6 +32,9 @@ |
182 | |
183 | self.lyrics_area = ScrollArea(0.4, 0.57, 0.5, 0.23, lyrics) |
184 | self.add(self.lyrics_area) |
185 | + |
186 | + self.connect('activated', self._on_activate) |
187 | + self.connect('deactivated', self._on_deactivated) |
188 | else: |
189 | # Display throbber animation while searching for lyrics |
190 | white = clutter.Color(255, 255, 255, 255) |
191 | @@ -42,8 +45,6 @@ |
192 | self.add(self.throbber) |
193 | self.track.fetch_lyrics(self._lyrics_search_callback) |
194 | |
195 | - self.hide_tab() |
196 | - |
197 | def _lyrics_search_callback(self, lyrics_text): |
198 | """ |
199 | This function is called when lyrics search is over. |
200 | @@ -75,17 +76,6 @@ |
201 | else: |
202 | return True |
203 | |
204 | - def set_active(self, boolean): |
205 | - """ |
206 | - Override method from parent class. |
207 | - @param boolean: True to set this tab active, False to set inactive |
208 | - """ |
209 | - Tab.set_active(self, boolean) |
210 | - if boolean: |
211 | - self.lyrics_area.set_active(True) |
212 | - else: |
213 | - self.lyrics_area.set_active(False) |
214 | - |
215 | def _handle_up(self): |
216 | '''Handle the up user event''' |
217 | if self.lyrics_area.get_offset() == 0: |
218 | @@ -97,3 +87,15 @@ |
219 | '''Handle the down user event''' |
220 | return self.lyrics_area.scroll_down() |
221 | |
222 | + def _on_activate(self, event=None): |
223 | + '''Tab activation''' |
224 | + self.lyrics_area.set_active(True) |
225 | + self.lyrics_area.set_opacity(255) |
226 | + return False |
227 | + |
228 | + def _on_deactivated(self, event=None): |
229 | + '''Tab deactivated''' |
230 | + self.lyrics_area.set_active(False) |
231 | + self.lyrics_area.set_opacity(128) |
232 | + return False |
233 | + |
234 | |
235 | === modified file 'entertainerlib/frontend/gui/tabs/movies_tab.py' |
236 | --- entertainerlib/frontend/gui/tabs/movies_tab.py 2009-02-03 23:37:57 +0000 |
237 | +++ entertainerlib/frontend/gui/tabs/movies_tab.py 2009-04-22 10:28:29 +0000 |
238 | @@ -40,6 +40,9 @@ |
239 | else: |
240 | self._create_menu() |
241 | |
242 | + self.connect('activated', self._on_activate) |
243 | + self.connect('deactivated', self._on_deactivated) |
244 | + |
245 | def can_activate(self): |
246 | """ |
247 | Allow if we have some movies indexed. |
248 | @@ -49,19 +52,6 @@ |
249 | else: |
250 | return True |
251 | |
252 | - def set_active(self, boolean): |
253 | - """ |
254 | - Override method from parent class. |
255 | - @param boolean: True to set this tab active, False to set inactive |
256 | - """ |
257 | - Tab.set_active(self, boolean) |
258 | - if boolean: |
259 | - self._update_movie_info( |
260 | - self.menu.get_current_menuitem().get_userdata()) |
261 | - self.menu.set_active(True) |
262 | - else: |
263 | - self.menu.set_active(False) |
264 | - |
265 | def _create_empty_library_notice(self): |
266 | """ |
267 | Create an information box that is displayed if there are no indexed |
268 | @@ -138,26 +128,29 @@ |
269 | self.movie_plot.width = 0.7 |
270 | self.add(self.movie_plot) |
271 | |
272 | - def _update_movie_info(self, movie): |
273 | - """ |
274 | - Update movie information. Updates movie specific labels. |
275 | - @param movie: Currently selected movie (Movie object) |
276 | - """ |
277 | - genres = movie.get_genres() |
278 | - if len(genres) > 1: |
279 | - genre = genres[0] + "/" + genres[1] |
280 | + def _update_movie_info(self): |
281 | + '''Update the movie information labels''' |
282 | + if self.active: |
283 | + movie = self.menu.get_current_menuitem().get_userdata() |
284 | + genres = movie.get_genres() |
285 | + if len(genres) > 1: |
286 | + genre = genres[0] + "/" + genres[1] |
287 | + else: |
288 | + genre = genres[0] |
289 | + |
290 | + self.movie_title.set_text(movie.get_title() + \ |
291 | + " (" + str(movie.get_year()) + ")") |
292 | + self.movie_info.set_text(str(movie.get_runtime()) + "min, " + genre) |
293 | + self.movie_plot.set_text(movie.get_short_plot()) |
294 | else: |
295 | - genre = genres[0] |
296 | - |
297 | - self.movie_title.set_text(movie.get_title() + \ |
298 | - " (" + str(movie.get_year()) + ")") |
299 | - self.movie_info.set_text(str(movie.get_runtime()) + "min, " + genre) |
300 | - self.movie_plot.set_text(movie.get_short_plot()) |
301 | + self.movie_title.set_text("") |
302 | + self.movie_info.set_text("") |
303 | + self.movie_plot.set_text("") |
304 | |
305 | def _update_menu(self, direction): |
306 | '''Update the menu based on the provided menu direction''' |
307 | self.menu.move(direction) |
308 | - self._update_movie_info(self.menu.get_current_menuitem().get_userdata()) |
309 | + self._update_movie_info() |
310 | self.list_indicator.set_current(self.menu.get_current_position() + 1) |
311 | return False |
312 | |
313 | @@ -187,3 +180,17 @@ |
314 | self.callback("movie", kwargs) |
315 | return False |
316 | |
317 | + def _on_activate(self, event=None): |
318 | + '''Tab activation''' |
319 | + self._update_movie_info() |
320 | + self.menu.set_active(True) |
321 | + self.menu.set_opacity(255) |
322 | + return False |
323 | + |
324 | + def _on_deactivated(self, event=None): |
325 | + '''Tab deactivated''' |
326 | + self._update_movie_info() |
327 | + self.menu.set_active(False) |
328 | + self.menu.set_opacity(128) |
329 | + return False |
330 | + |
331 | |
332 | === modified file 'entertainerlib/frontend/gui/tabs/series_tab.py' |
333 | --- entertainerlib/frontend/gui/tabs/series_tab.py 2009-02-03 23:37:57 +0000 |
334 | +++ entertainerlib/frontend/gui/tabs/series_tab.py 2009-04-22 10:28:29 +0000 |
335 | @@ -39,7 +39,8 @@ |
336 | else: |
337 | self._create_menu() |
338 | |
339 | - self.hide_tab() |
340 | + self.connect('activated', self._on_activate) |
341 | + self.connect('deactivated', self._on_deactivated) |
342 | |
343 | def can_activate(self): |
344 | """ |
345 | @@ -50,19 +51,6 @@ |
346 | else: |
347 | return True |
348 | |
349 | - def set_active(self, boolean): |
350 | - """ |
351 | - Override method from parent class. |
352 | - @param boolean: True to set this tab active, False to set inactive |
353 | - """ |
354 | - Tab.set_active(self, boolean) |
355 | - if boolean: |
356 | - self._update_series_info( |
357 | - self.menu.get_current_menuitem().get_userdata()) |
358 | - self.menu.set_active(True) |
359 | - else: |
360 | - self.menu.set_active(False) |
361 | - |
362 | def _create_empty_library_notice(self): |
363 | """ |
364 | Create an information box that is displayed if there are no indexed |
365 | @@ -134,23 +122,24 @@ |
366 | self.series_info = Label(0.034, "subtitle", 0.2, 0.82, "") |
367 | self.add(self.series_info) |
368 | |
369 | - def _update_series_info(self, series): |
370 | - """ |
371 | - Update TV-Serie information. Updates clutter labels on TV-series tab |
372 | - @param series: Currently selected TV-Serie (TVSeries object) |
373 | - """ |
374 | - info = _("%(season)d Seasons\n%(episode)d Episodes") % {'season': |
375 | - series.get_number_of_seasons(), 'episode': |
376 | - series.get_number_of_episodes()} |
377 | + def _update_series_info(self): |
378 | + '''Update the TV-Serie information labels''' |
379 | + if self.active: |
380 | + series = self.menu.get_current_menuitem().get_userdata() |
381 | + info = _("%(season)d Seasons\n%(episode)d Episodes") % {'season': |
382 | + series.get_number_of_seasons(), 'episode': |
383 | + series.get_number_of_episodes()} |
384 | |
385 | - self.series_info.set_text(info) |
386 | - self.series_title.set_text(series.get_title()) |
387 | + self.series_info.set_text(info) |
388 | + self.series_title.set_text(series.get_title()) |
389 | + else: |
390 | + self.series_info.set_text("") |
391 | + self.series_title.set_text("") |
392 | |
393 | def _update_menu(self, direction): |
394 | '''Update the menu based on the provided menu direction''' |
395 | self.menu.move(direction) |
396 | - self._update_series_info( |
397 | - self.menu.get_current_menuitem().get_userdata()) |
398 | + self._update_series_info() |
399 | self.list_indicator.set_current(self.menu.get_current_position() + 1) |
400 | return False |
401 | |
402 | @@ -180,3 +169,17 @@ |
403 | self.callback("tv_series", kwargs) |
404 | return False |
405 | |
406 | + def _on_activate(self, event=None): |
407 | + '''Tab activation''' |
408 | + self._update_series_info() |
409 | + self.menu.set_active(True) |
410 | + self.menu.set_opacity(255) |
411 | + return False |
412 | + |
413 | + def _on_deactivated(self, event=None): |
414 | + '''Tab deactivated''' |
415 | + self._update_series_info() |
416 | + self.menu.set_active(False) |
417 | + self.menu.set_opacity(128) |
418 | + return False |
419 | + |
420 | |
421 | === modified file 'entertainerlib/frontend/gui/tabs/tab.py' |
422 | --- entertainerlib/frontend/gui/tabs/tab.py 2009-02-28 17:22:46 +0000 |
423 | +++ entertainerlib/frontend/gui/tabs/tab.py 2009-05-07 19:25:47 +0000 |
424 | @@ -5,28 +5,33 @@ |
425 | __author__ = "Lauri Taimila <lauri@taimila.com>" |
426 | |
427 | import clutter |
428 | +import gobject |
429 | |
430 | from entertainerlib.frontend.gui.user_event import UserEvent |
431 | from entertainerlib.frontend.gui.widgets.base import Base |
432 | from entertainerlib.frontend.gui.widgets.label import Label |
433 | from entertainerlib.frontend.gui.widgets.texture import Texture |
434 | |
435 | -class Tab(Base, clutter.Group): |
436 | +class Tab(Base, clutter.Group, object): |
437 | """ |
438 | Tab can be used as part of the TabGroup |
439 | |
440 | Tab is a very simple container that contains all the widgets and logic |
441 | of the tab page. |
442 | """ |
443 | + __gtype_name__ = 'Tab' |
444 | + __gsignals__ = { |
445 | + 'activated' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ), |
446 | + 'deactivated' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ), |
447 | + } |
448 | |
449 | def __init__(self, name, title="Untitled tab", callback=None): |
450 | - |
451 | Base.__init__(self) |
452 | clutter.Group.__init__(self) |
453 | - self._name = name |
454 | - self._title = title |
455 | + |
456 | + self.name = name |
457 | + self.title = title |
458 | self.callback = callback |
459 | - self.active = False |
460 | self.theme = self.config.theme |
461 | |
462 | self.dispatch = { |
463 | @@ -37,20 +42,49 @@ |
464 | UserEvent.NAVIGATE_SELECT : self._handle_select, |
465 | } |
466 | |
467 | - self.timeline = clutter.Timeline(15, 60) |
468 | + # show/hide animation on the Tab |
469 | + self.timeline = clutter.Timeline(30, 60) |
470 | self.alpha = clutter.Alpha(self.timeline, clutter.smoothstep_inc_func) |
471 | self.behaviour = clutter.BehaviourOpacity(0, 255, self.alpha) |
472 | self.behaviour.apply(self) |
473 | |
474 | - @property |
475 | - def name(self): |
476 | - '''Internal string name that can be used as a key for the tab group''' |
477 | - return self._name |
478 | - |
479 | - @property |
480 | - def title(self): |
481 | - '''User string title displayed in the interface''' |
482 | - return self._title |
483 | + # Tabs are created deactivated and invisible |
484 | + self._active = False |
485 | + self._visible = False |
486 | + self.set_opacity(0) |
487 | + |
488 | + def _get_active(self): |
489 | + """active property getter.""" |
490 | + return self._active |
491 | + |
492 | + def _set_active(self, boolean): |
493 | + """active property setter.""" |
494 | + self._active = boolean |
495 | + |
496 | + if boolean: |
497 | + self.emit('activated') |
498 | + else: |
499 | + self.emit('deactivated') |
500 | + |
501 | + active = property(_get_active, _set_active) |
502 | + |
503 | + def _get_visible(self): |
504 | + """visible property getter.""" |
505 | + return self._visible |
506 | + |
507 | + def _set_visible(self, boolean): |
508 | + """visible property setter.""" |
509 | + self._visible = boolean |
510 | + |
511 | + if boolean: |
512 | + self.timeline.set_direction(clutter.TIMELINE_FORWARD) |
513 | + self.timeline.rewind() |
514 | + self.timeline.start() |
515 | + else: |
516 | + self.timeline.set_direction(clutter.TIMELINE_BACKWARD) |
517 | + self.timeline.start() |
518 | + |
519 | + visible = property(_get_visible, _set_visible) |
520 | |
521 | def can_activate(self): |
522 | """ |
523 | @@ -61,20 +95,6 @@ |
524 | """ |
525 | return False |
526 | |
527 | - def is_active(self): |
528 | - """ |
529 | - Is this tab active |
530 | - @return: boolean |
531 | - """ |
532 | - return self.active |
533 | - |
534 | - def set_active(self, boolean): |
535 | - """ |
536 | - Set this tab to active or inactive state |
537 | - @param boolean: True to set active, False to set inactive |
538 | - """ |
539 | - self.active = boolean |
540 | - |
541 | def show_empty_tab_notice(self, title=_("Empty tab"), |
542 | message_body=_("This tab doesn't contain any elements.")): |
543 | """ |
544 | @@ -95,23 +115,6 @@ |
545 | info.width = 0.57 |
546 | self.add(info) |
547 | |
548 | - def show_tab(self): |
549 | - """ |
550 | - Activate this tab. This is called when tab is changed and this tab |
551 | - is becoming active. This method should activate animations. |
552 | - """ |
553 | - self.set_opacity(0) |
554 | - self.show() |
555 | - self.timeline.set_direction(clutter.TIMELINE_FORWARD) |
556 | - self.timeline.start() |
557 | - |
558 | - def hide_tab(self): |
559 | - """ |
560 | - Deactivate this tab. This is called when tab is changed and this tab |
561 | - is becoming non-active. This method should disable animations. |
562 | - """ |
563 | - self.hide() |
564 | - |
565 | def _handle_up(self): |
566 | '''Dummy method for unimplemented handlers in subclass''' |
567 | return False |
568 | |
569 | === modified file 'entertainerlib/frontend/gui/tabs/tracks_tab.py' |
570 | --- entertainerlib/frontend/gui/tabs/tracks_tab.py 2009-02-03 23:37:57 +0000 |
571 | +++ entertainerlib/frontend/gui/tabs/tracks_tab.py 2009-04-22 10:28:29 +0000 |
572 | @@ -67,37 +67,33 @@ |
573 | self.li.show() |
574 | self.add(self.li) |
575 | |
576 | + self.connect('activated', self._on_activate) |
577 | + self.connect('deactivated', self._on_deactivated) |
578 | + |
579 | def can_activate(self): |
580 | '''Tracks tab will always be created from an existing artist with at |
581 | least one track''' |
582 | return True |
583 | |
584 | - def set_active(self, boolean): |
585 | - """ |
586 | - Override method from parent class. |
587 | - @param boolean: True to set this tab active, False to set inactive |
588 | - """ |
589 | - Tab.set_active(self, boolean) |
590 | - if boolean: |
591 | - self._update_track_info( |
592 | - self.menu.get_current_menuitem().get_userdata()) |
593 | - self.menu.set_active(True) |
594 | - else: |
595 | - self.menu.set_active(False) |
596 | - |
597 | - def _update_track_info(self, track): |
598 | + def _update_track_info(self): |
599 | '''Update the track information labels''' |
600 | - self.track_title.set_text(track.get_title()) |
601 | - self.track_length.set_text(str(track.get_length() / 60) + ":" + \ |
602 | - str(track.get_length() % 60).zfill(2)) |
603 | - self.track_number.set_text(_("Track %(track)s from %(album)s") % \ |
604 | - {'track': track.get_tracknumber(), |
605 | - 'album': track.get_album().get_title()}) |
606 | + if self.active: |
607 | + track = self.menu.get_current_menuitem().get_userdata() |
608 | + self.track_title.set_text(track.get_title()) |
609 | + self.track_length.set_text(str(track.get_length() / 60) + ":" + \ |
610 | + str(track.get_length() % 60).zfill(2)) |
611 | + self.track_number.set_text(_("Track %(track)s from %(album)s") % \ |
612 | + {'track': track.get_tracknumber(), |
613 | + 'album': track.get_album().get_title()}) |
614 | + else: |
615 | + self.track_title.set_text("") |
616 | + self.track_length.set_text("") |
617 | + self.track_number.set_text("") |
618 | |
619 | def _update_menu(self, direction): |
620 | '''Update the menu based on the provided menu direction''' |
621 | self.menu.move(direction) |
622 | - self._update_track_info(self.menu.get_current_menuitem().get_userdata()) |
623 | + self._update_track_info() |
624 | self.li.set_current(self.menu.get_current_position() + 1) |
625 | return False |
626 | |
627 | @@ -127,3 +123,17 @@ |
628 | self.callback("audio_play", kwargs) |
629 | return False |
630 | |
631 | + def _on_activate(self, event=None): |
632 | + '''Tab activation''' |
633 | + self._update_track_info() |
634 | + self.menu.set_active(True) |
635 | + self.menu.set_opacity(255) |
636 | + return False |
637 | + |
638 | + def _on_deactivated(self, event=None): |
639 | + '''Tab deactivated''' |
640 | + self._update_track_info() |
641 | + self.menu.set_active(False) |
642 | + self.menu.set_opacity(128) |
643 | + return False |
644 | + |
645 | |
646 | === modified file 'entertainerlib/frontend/gui/tabs/video_clips_tab.py' |
647 | --- entertainerlib/frontend/gui/tabs/video_clips_tab.py 2009-02-03 23:37:57 +0000 |
648 | +++ entertainerlib/frontend/gui/tabs/video_clips_tab.py 2009-04-22 10:28:29 +0000 |
649 | @@ -40,7 +40,8 @@ |
650 | else: |
651 | self._create_menu() |
652 | |
653 | - self.hide_tab() |
654 | + self.connect('activated', self._on_activate) |
655 | + self.connect('deactivated', self._on_deactivated) |
656 | |
657 | def can_activate(self): |
658 | """ |
659 | @@ -51,19 +52,6 @@ |
660 | else: |
661 | return True |
662 | |
663 | - def set_active(self, boolean): |
664 | - """ |
665 | - Override method from parent class. |
666 | - @param boolean: True to set this tab active, False to set inactive |
667 | - """ |
668 | - Tab.set_active(self, boolean) |
669 | - if boolean: |
670 | - self._update_clip_info( |
671 | - self.menu.get_current_menuitem().get_userdata()) |
672 | - self.menu.set_active(True) |
673 | - else: |
674 | - self.menu.set_active(False) |
675 | - |
676 | def _create_empty_library_notice(self): |
677 | """ |
678 | Create an information box that is displayed if there are no indexed |
679 | @@ -126,19 +114,21 @@ |
680 | self.clip_info.width = 0.5 |
681 | self.add(self.clip_info) |
682 | |
683 | - def _update_clip_info(self, clip): |
684 | - """ |
685 | - Update VideoClip information. Updates clutter labels on Clips tab |
686 | - @param clip: Currently selected VideoClip (VideoClip object) |
687 | - """ |
688 | - (folder, filename) = os.path.split(clip.get_filename()) |
689 | - self.clip_title.set_text(filename) |
690 | - self.clip_info.set_text(folder) |
691 | + def _update_clip_info(self): |
692 | + '''Update the VideoClip information labels''' |
693 | + if self.active: |
694 | + clip = self.menu.get_current_menuitem().get_userdata() |
695 | + (folder, filename) = os.path.split(clip.get_filename()) |
696 | + self.clip_title.set_text(filename) |
697 | + self.clip_info.set_text(folder) |
698 | + else: |
699 | + self.clip_title.set_text("") |
700 | + self.clip_info.set_text("") |
701 | |
702 | def _update_menu(self, direction): |
703 | '''Update the menu based on the provided menu direction''' |
704 | self.menu.move(direction) |
705 | - self._update_clip_info(self.menu.get_current_menuitem().get_userdata()) |
706 | + self._update_clip_info() |
707 | self.list_indicator.set_current(self.menu.get_current_position() + 1) |
708 | return False |
709 | |
710 | @@ -169,3 +159,17 @@ |
711 | self.callback("video_osd") |
712 | return False |
713 | |
714 | + def _on_activate(self, event=None): |
715 | + '''Tab activation''' |
716 | + self._update_clip_info() |
717 | + self.menu.set_active(True) |
718 | + self.menu.set_opacity(255) |
719 | + return False |
720 | + |
721 | + def _on_deactivated(self, event=None): |
722 | + '''Tab deactivated''' |
723 | + self._update_clip_info() |
724 | + self.menu.set_active(False) |
725 | + self.menu.set_opacity(128) |
726 | + return False |
727 | + |
728 | |
729 | === modified file 'entertainerlib/frontend/gui/widgets/tab_group.py' |
730 | --- entertainerlib/frontend/gui/widgets/tab_group.py 2009-02-27 00:01:53 +0000 |
731 | +++ entertainerlib/frontend/gui/widgets/tab_group.py 2009-04-16 18:52:06 +0000 |
732 | @@ -7,11 +7,12 @@ |
733 | import clutter |
734 | import pango |
735 | |
736 | +from entertainerlib.frontend.gui.widgets.base import Base |
737 | from entertainerlib.frontend.gui.widgets.label import Label |
738 | from entertainerlib.frontend.gui.user_event import UserEvent |
739 | from entertainerlib.utils.configuration import Configuration |
740 | |
741 | -class TabGroup(clutter.Group): |
742 | +class TabGroup(Base, clutter.Group, object): |
743 | """ |
744 | This is a top level container for tabs. |
745 | |
746 | @@ -26,7 +27,9 @@ |
747 | @param height: height of the tab bar in percents (0-1) |
748 | @param color: color string to call from the theme |
749 | """ |
750 | + Base.__init__(self) |
751 | clutter.Group.__init__(self) |
752 | + |
753 | self.config = Configuration() |
754 | |
755 | self.active = False |
756 | @@ -112,16 +115,23 @@ |
757 | |
758 | self.tabs_list.append(tab) |
759 | |
760 | - # Set first tab active |
761 | + tab.active = False |
762 | + |
763 | + # Set first tab visible |
764 | if self.current_tab == None: |
765 | self.current_tab = 0 |
766 | - self.tabs_list[0].show_tab() |
767 | + self.tabs_list[0].visible = True |
768 | |
769 | tab_title = Label(0.01, self.color, 0, 0, tab.title) |
770 | + self.labels.append(tab_title) |
771 | + tab_title.set_name(tab.name) |
772 | + |
773 | self.add(tab_title) |
774 | - self.labels.append(tab_title) |
775 | self._reposition_labels() |
776 | |
777 | + tab_title.set_reactive(True) |
778 | + tab_title.connect('button-press-event', self._on_tab_title_button_press) |
779 | + |
780 | timeline = clutter.Timeline(25, 60) |
781 | alpha = clutter.Alpha(timeline, clutter.smoothstep_inc_func) |
782 | behaviour = clutter.BehaviourOpacity(255, 96, alpha) |
783 | @@ -151,44 +161,45 @@ |
784 | label.set_line_wrap(False) |
785 | label.set_ellipsize(pango.ELLIPSIZE_END) |
786 | |
787 | + def _activate_tab(self, new_tab): |
788 | + """Activation of new selected Tab""" |
789 | + self.timelines[self.current_tab][0].set_direction( |
790 | + clutter.TIMELINE_FORWARD) |
791 | + self.timelines[self.current_tab][0].start() |
792 | + |
793 | + self.timelines[new_tab][0].set_direction(clutter.TIMELINE_BACKWARD) |
794 | + self.timelines[new_tab][0].start() |
795 | + |
796 | + # Change visible tab |
797 | + self.tabs_list[self.current_tab].visible = False |
798 | + self.tabs_list[self.current_tab].active = False |
799 | + self.tabs_list[new_tab].visible = True |
800 | + self.tabs_list[new_tab].active = False |
801 | + |
802 | def _switch_tab_to_left(self): |
803 | """ |
804 | Switch one tab to left if possible. Do nothing if there is no tab |
805 | """ |
806 | if self.current_tab > 0 and self.current_tab is not None: |
807 | + self._activate_tab(self.current_tab - 1) |
808 | self.current_tab = self.current_tab - 1 |
809 | |
810 | - # Animate labels |
811 | - self.timelines[self.current_tab + 1][0].set_direction( |
812 | - clutter.TIMELINE_FORWARD) |
813 | - self.timelines[self.current_tab + 1][0].start() |
814 | - self.timelines[self.current_tab][0].set_direction( |
815 | - clutter.TIMELINE_BACKWARD) |
816 | - self.timelines[self.current_tab][0].start() |
817 | - |
818 | - # Change visible tab |
819 | - self.tabs_list[self.current_tab + 1].hide_tab() |
820 | - self.tabs_list[self.current_tab].show_tab() |
821 | - |
822 | def _switch_tab_to_right(self): |
823 | """ |
824 | Switch one tab to right if possible. Do nothing if there is no tab |
825 | """ |
826 | if self.current_tab < len(self.tabs_list) - 1 and ( |
827 | self.current_tab is not None): |
828 | + self._activate_tab(self.current_tab + 1) |
829 | self.current_tab = self.current_tab + 1 |
830 | |
831 | - # Animate labels |
832 | - self.timelines[self.current_tab - 1][0].set_direction( |
833 | - clutter.TIMELINE_FORWARD) |
834 | - self.timelines[self.current_tab - 1][0].start() |
835 | - self.timelines[self.current_tab][0].set_direction( |
836 | - clutter.TIMELINE_BACKWARD) |
837 | - self.timelines[self.current_tab][0].start() |
838 | - |
839 | - # Change visible tab |
840 | - self.tabs_list[self.current_tab - 1].hide_tab() |
841 | - self.tabs_list[self.current_tab].show_tab() |
842 | + def _switch_tab_to_down(self): |
843 | + """ |
844 | + Switch down from current tab if allowed |
845 | + """ |
846 | + if self.tabs_list[self.current_tab].can_activate(): |
847 | + self.set_active(False) |
848 | + self.tabs_list[self.current_tab].active = True |
849 | |
850 | def handle_user_event(self, event): |
851 | """ |
852 | @@ -201,13 +212,19 @@ |
853 | self._switch_tab_to_left() |
854 | elif event_type == UserEvent.NAVIGATE_RIGHT: |
855 | self._switch_tab_to_right() |
856 | - elif event_type == UserEvent.NAVIGATE_DOWN and ( |
857 | - self.tabs_list[self.current_tab].can_activate()): |
858 | - self.set_active(False) |
859 | - self.tabs_list[self.current_tab].set_active(True) |
860 | + elif event_type == UserEvent.NAVIGATE_DOWN: |
861 | + self._switch_tab_to_down() |
862 | else: |
863 | result = self.tabs_list[self.current_tab].handle_user_event(event) |
864 | if result: |
865 | self.set_active(True) |
866 | - self.tabs_list[self.current_tab].set_active(False) |
867 | + self.tabs_list[self.current_tab].active = False |
868 | + |
869 | + def _on_tab_title_button_press(self, actor, event): |
870 | + '''Title Label button-press handler''' |
871 | + clicked_tab = self._tabs[actor.get_name()] |
872 | + clicked_tab_index = self.tabs_list.index(clicked_tab) |
873 | + |
874 | + self._activate_tab(clicked_tab_index) |
875 | + self.current_tab = clicked_tab_index |
876 |
Hi everybody,
This branch provides reactive Tabs on Screens like Music, AudioPlay etc
The interaction is at the moment limited to switching from one tab to another one by clicking on the Tab title. Further interactions will be possible when other reactive widgets will be integrated on Tabs.
I've also slightly changed effect when switching (now there's a fade out AND a fade in)
Samuel-