1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
29
"""``miro.tabs`` -- Holds the TabOrder DDBObject.
32
from miro import database
33
from miro import guide
35
from miro import folder
36
from miro import eventloop
37
from miro import playlist
38
from miro.util import check_u
42
class TabOrder(database.DDBObject):
43
"""TabOrder objects keep track of the order of the tabs. Miro
44
creates 2 of these, one to track channels/channel folders and another to
45
track playlists/playlist folders.
48
def setup_new(self, type):
49
"""Construct a TabOrder. type should be either ``channel`` or
56
decorated = [(t.get_title().lower(), t) for t in self.id_to_tab.values()]
58
for sortkey, tab in decorated:
59
self.tab_ids.append(tab.id)
61
def setup_restored(self):
62
if not isinstance(self.tab_ids, list):
63
logging.warning("tab_ids was not a list. setting it to [].")
66
for mem in self.tab_ids:
67
if not isinstance(mem, int):
69
logging.warning("tab_ids wasn't a list of ints. setting it to [].")
72
def restore_tab_list(self):
73
"""Restores the tablist.
76
self._check_for_non_existent_ids()
77
self._remove_out_of_order_children()
78
self._add_untracked_ids()
80
def _get_tab_views(self):
81
if self.type == u'site':
82
tab_views = (guide.ChannelGuide.site_view(),)
83
elif self.type == u'channel':
84
tab_views = (feed.Feed.visible_video_view(),
85
folder.ChannelFolder.video_view())
86
elif self.type == u'audio-channel':
87
tab_views = (feed.Feed.visible_audio_view(),
88
folder.ChannelFolder.audio_view())
89
elif self.type == u'playlist':
90
tab_views = (playlist.SavedPlaylist.make_view(),
91
folder.PlaylistFolder.make_view())
93
raise ValueError("Bad type for TabOrder")
96
def _setup_views(self):
97
"""Sets up all the tab-related views.
99
tab_views = self._get_tab_views()
102
for view in tab_views:
104
self.id_to_tab[obj.id] = obj
105
self.trackers = [view.make_tracker() for view in tab_views]
106
for tracker in self.trackers:
107
tracker.connect("added", self._on_add_tab)
108
tracker.connect("removed", self._on_remove_tab)
111
def view_for_type(cls, type):
112
"""View based on tab type.
114
return cls.make_view('type=?', (type,))
118
"""View of sites based on order.
120
return cls.view_for_type(u'site').get_singleton()
123
def video_feed_order(cls):
124
"""View of feeds based on order.
126
return cls.view_for_type(u'channel').get_singleton()
129
def audio_feed_order(cls):
130
"""View of audio feeds based on order.
132
return cls.view_for_type(u'audio-channel').get_singleton()
135
def playlist_order(cls):
136
"""View of playlists based on order.
138
return cls.view_for_type(u'playlist').get_singleton()
140
def _check_for_non_existent_ids(self):
142
for i in reversed(xrange(len(self.tab_ids))):
143
tab_id = self.tab_ids[i]
144
if not tab_id in self.id_to_tab:
146
logging.warn("Throwing away non-existent TabOrder id: %s", tab_id)
151
def _add_untracked_ids(self):
152
untracked_ids = set(self.id_to_tab.keys()) - set(self.tab_ids)
153
if not untracked_ids:
155
tab_views = self._get_tab_views()
156
# dict of folder_id -> list of children ids
158
# any non-folder item that isn't in a folder
160
for view in tab_views:
162
if obj.id not in untracked_ids:
164
if isinstance(obj, folder.FolderBase):
165
folders.setdefault(obj.id, [])
167
if obj.get_folder() is None:
168
extras.append(obj.id)
170
folders.setdefault(obj.get_folder().id, []).append(obj.id)
171
for folder_id, children in folders.items():
172
if folder_id in untracked_ids:
173
# folder isn't tracked, add everything to the bottom
174
self.tab_ids.append(folder_id)
175
self.tab_ids.extend(children)
177
# folder is tracked, insert the children after the folder
178
pos = self.tab_ids.index(folder_id)
179
self.tab_ids[pos+1:pos+1] = children
181
self.tab_ids.extend(extras)
184
def _remove_out_of_order_children(self):
185
"""Remove ids for objects that have parents, but aren't ordered
186
correctly relative to them. (they will get added back in in
187
_add_untracked_ids())
190
current_folder_id = None
192
out_of_order_children = []
194
for pos, obj in enumerate(self.get_all_tabs()):
195
if obj.get_folder() is None:
196
if isinstance(obj, folder.FolderBase):
197
current_folder_id = obj.id
199
current_folder_id = None
201
if (current_folder_id is None or
202
obj.get_folder().id != current_folder_id):
203
out_of_order_children.append(pos)
204
for pos in reversed(out_of_order_children):
205
del self.tab_ids[pos]
207
def get_all_tabs(self):
208
"""Get all the tabs in this tab ordering (in order), regardless if
209
they are visible in the tab list or not.
211
return [self.id_to_tab[tab_id] for tab_id in self.tab_ids]
213
def _on_add_tab(self, tracker, obj):
214
if obj.id not in self.id_to_tab:
215
self.id_to_tab[obj.id] = obj
216
self.tab_ids.append(obj.id)
219
def _on_remove_tab(self, tracker, obj):
220
if obj.id in self.id_to_tab:
221
del self.id_to_tab[obj.id]
222
self.tab_ids.remove(obj.id)
225
def reorder(self, newOrder):
226
"""Saves the new tab order.
228
self.tab_ids = newOrder
231
def move_tabs_after(self, anchor_id, tab_ids):
232
"""Move a sequence of tabs so that they are after another tab."""
233
anchor_pos = self.tab_ids.index(anchor_id)
234
move_set = set(tab_ids)
235
before = [tab_id for tab_id in self.tab_ids[:anchor_pos]
236
if tab_id not in move_set]
237
after = [tab_id for tab_id in self.tab_ids[anchor_pos+1:]
238
if tab_id not in move_set]
239
self.reorder(before + [anchor_id] + list(tab_ids) + after)