~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to lib/tabs.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Miro - an RSS based video player application
 
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
 
3
#
 
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.
 
8
#
 
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.
 
13
#
 
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
 
17
#
 
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
 
20
# library.
 
21
#
 
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.
 
28
 
 
29
"""``miro.tabs`` -- Holds the TabOrder DDBObject.
 
30
"""
 
31
 
 
32
from miro import database
 
33
from miro import guide
 
34
from miro import feed
 
35
from miro import folder
 
36
from miro import eventloop
 
37
from miro import playlist
 
38
from miro.util import check_u
 
39
 
 
40
import logging
 
41
 
 
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.
 
46
    """
 
47
 
 
48
    def setup_new(self, type):
 
49
        """Construct a TabOrder.  type should be either ``channel`` or
 
50
        ``playlist``.
 
51
        """
 
52
        check_u(type)
 
53
        self.type = type
 
54
        self.tab_ids = []
 
55
        self._setup_views()
 
56
        decorated = [(t.get_title().lower(), t) for t in self.id_to_tab.values()]
 
57
        decorated.sort()
 
58
        for sortkey, tab in decorated:
 
59
            self.tab_ids.append(tab.id)
 
60
 
 
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 [].")
 
64
            self.tab_ids = []
 
65
            return
 
66
        for mem in self.tab_ids:
 
67
            if not isinstance(mem, int):
 
68
                self.tab_ids = []
 
69
                logging.warning("tab_ids wasn't a list of ints.  setting it to [].")
 
70
                return
 
71
 
 
72
    def restore_tab_list(self):
 
73
        """Restores the tablist.
 
74
        """
 
75
        self._setup_views()
 
76
        self._check_for_non_existent_ids()
 
77
        self._remove_out_of_order_children()
 
78
        self._add_untracked_ids()
 
79
 
 
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())
 
92
        else:
 
93
            raise ValueError("Bad type for TabOrder")
 
94
        return tab_views
 
95
 
 
96
    def _setup_views(self):
 
97
        """Sets up all the tab-related views.
 
98
        """
 
99
        tab_views = self._get_tab_views()
 
100
 
 
101
        self.id_to_tab = {}
 
102
        for view in tab_views:
 
103
            for obj in view:
 
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)
 
109
 
 
110
    @classmethod
 
111
    def view_for_type(cls, type):
 
112
        """View based on tab type.
 
113
        """
 
114
        return cls.make_view('type=?', (type,))
 
115
 
 
116
    @classmethod
 
117
    def site_order(cls):
 
118
        """View of sites based on order.
 
119
        """
 
120
        return cls.view_for_type(u'site').get_singleton()
 
121
 
 
122
    @classmethod
 
123
    def video_feed_order(cls):
 
124
        """View of feeds based on order.
 
125
        """
 
126
        return cls.view_for_type(u'channel').get_singleton()
 
127
 
 
128
    @classmethod
 
129
    def audio_feed_order(cls):
 
130
        """View of audio feeds based on order.
 
131
        """
 
132
        return cls.view_for_type(u'audio-channel').get_singleton()
 
133
 
 
134
    @classmethod
 
135
    def playlist_order(cls):
 
136
        """View of playlists based on order.
 
137
        """
 
138
        return cls.view_for_type(u'playlist').get_singleton()
 
139
 
 
140
    def _check_for_non_existent_ids(self):
 
141
        changed = False
 
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:
 
145
                del self.tab_ids[i]
 
146
                logging.warn("Throwing away non-existent TabOrder id: %s", tab_id)
 
147
                changed = True
 
148
        if changed:
 
149
            self.signal_change()
 
150
 
 
151
    def _add_untracked_ids(self):
 
152
        untracked_ids = set(self.id_to_tab.keys()) - set(self.tab_ids)
 
153
        if not untracked_ids:
 
154
            return
 
155
        tab_views = self._get_tab_views()
 
156
        # dict of folder_id -> list of children ids
 
157
        folders = {}
 
158
        # any non-folder item that isn't in a folder
 
159
        extras = []
 
160
        for view in tab_views:
 
161
            for obj in view:
 
162
                if obj.id not in untracked_ids:
 
163
                    continue
 
164
                if isinstance(obj, folder.FolderBase):
 
165
                    folders.setdefault(obj.id, [])
 
166
                    continue
 
167
                if obj.get_folder() is None:
 
168
                    extras.append(obj.id)
 
169
                else:
 
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)
 
176
            else:
 
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
 
180
 
 
181
        self.tab_ids.extend(extras)
 
182
        self.signal_change()
 
183
 
 
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())
 
188
        """
 
189
 
 
190
        current_folder_id = None
 
191
 
 
192
        out_of_order_children = []
 
193
 
 
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
 
198
                else:
 
199
                    current_folder_id = None
 
200
            else:
 
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]
 
206
 
 
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.
 
210
        """
 
211
        return [self.id_to_tab[tab_id] for tab_id in self.tab_ids]
 
212
 
 
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)
 
217
            self.signal_change()
 
218
 
 
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)
 
223
            self.signal_change()
 
224
 
 
225
    def reorder(self, newOrder):
 
226
        """Saves the new tab order.
 
227
        """
 
228
        self.tab_ids = newOrder
 
229
        self.signal_change()
 
230
 
 
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)