home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / tabs.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  13.3 KB  |  397 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 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. import database
  19. import app
  20. import template
  21. import views
  22. import eventloop
  23. import feed
  24. import folder
  25. import resources
  26. import guide
  27. import playlist
  28. import sorts
  29. from util import checkU, getSingletonDDBObject
  30. from databasehelper import TrackedIDList
  31.  
  32. from xml.dom.minidom import parse
  33. from gtcache import gettext as _
  34. import logging
  35.  
  36. ###############################################################################
  37. #### Tabs                                                                  ####
  38. ###############################################################################
  39.  
  40.  
  41. # Database object representing a static (non-feed-associated) tab.
  42. class StaticTab(database.DDBObject):
  43.     tabTitles = {
  44.         'librarytab': _('Library'),
  45.         'newtab': _('New'),
  46.         'searchtab': _('Video Search'),
  47.         'downloadtab': _('Downloading'),
  48.     }
  49.  
  50.     tabIcons = {
  51.         'librarytab': 'collection-icon-tablist.png',
  52.         'newtab': 'newvideos-icon-tablist.png',
  53.         'searchtab': 'search-icon-tablist.png',
  54.         'downloadtab': 'download-icon-tab.png',
  55.     }
  56.  
  57.     def __init__(self, tabTemplateBase, contentsTemplate, state, order):
  58.         self.tabTemplateBase = tabTemplateBase
  59.         self.contentsTemplate = contentsTemplate
  60.         self.order = order
  61.         self.templateState = state
  62.         database.DDBObject.__init__(self)
  63.  
  64.     def getTitle(self):
  65.         return self.tabTitles[self.tabTemplateBase]
  66.  
  67.     def getIconURL(self):
  68.         return resources.url("images/%s" % self.tabIcons[self.tabTemplateBase])
  69.  
  70.     def getNumberColor(self):
  71.         if self.tabTemplateBase == 'downloadtab':
  72.             return 'orange'
  73.         elif self.tabTemplateBase == 'newtab':
  74.             return 'green'
  75.         else:
  76.             return None
  77.  
  78.     def getNumber(self):
  79.         if self.tabTemplateBase == 'downloadtab':
  80.             return views.downloadingItems.len()
  81.         elif self.tabTemplateBase == 'newtab':
  82.             return views.unwatchedItems.len()
  83.         else:
  84.             return 0
  85.  
  86.     def enableNewVideoPlayButton(self):
  87.         return (self.tabTemplateBase == 'newtab' and 
  88.                 views.unwatchedItems.len() > 0)
  89.  
  90. class Tab:
  91.     idCounter = 0
  92.  
  93.     def __init__(self, tabTemplateBase, contentsTemplate, templateState, obj):
  94.         self.tabTemplateBase = tabTemplateBase
  95.         self.contentsTemplate = contentsTemplate
  96.         self.templateState = templateState
  97.         self.display = None
  98.         self.id = "tab%d" % Tab.idCounter
  99.         Tab.idCounter += 1
  100.         self.selected = False
  101.         self.active = False
  102.         self.obj = obj
  103.  
  104.         if obj.__class__ == guide.ChannelGuide:
  105.             self.type = 'guide'
  106.         elif obj.__class__ == StaticTab: 
  107.             self.type = 'statictab'
  108.         elif obj.__class__ in (feed.Feed, folder.ChannelFolder): 
  109.             self.type = 'feed'
  110.         elif obj.__class__ in (playlist.SavedPlaylist, folder.PlaylistFolder):
  111.             self.type = 'playlist'
  112.         else:
  113.             raise TypeError("Bad tab object type: %s" % type(obj))
  114.  
  115.     def getDragSourceType(self):
  116.         selection = app.controller.selection.tabListSelection
  117.         if self.type == 'feed':
  118.             if (isinstance(self.obj, folder.ChannelFolder) or
  119.                     (self.selected and selection.isFolderSelected())):
  120.                 return 'channelfolder'
  121.             else:
  122.                 return 'channel'
  123.         elif self.type == 'playlist':
  124.             if (isinstance(self.obj, folder.PlaylistFolder) or
  125.                     (self.selected and selection.isFolderSelected())):
  126.                 return 'playlistfolder'
  127.             else:
  128.                 return 'playlist'
  129.         else:
  130.             return ''
  131.  
  132.     def setActive(self, newValue):
  133.         self.obj.confirmDBThread()
  134.         self.active = newValue
  135.         self.obj.signalChange(needsSave=False)
  136.  
  137.     def setSelected(self, newValue):
  138.         self.obj.confirmDBThread()
  139.         self.selected = newValue
  140.         self.obj.signalChange(needsSave=False)
  141.  
  142.     def getSelected(self):
  143.         self.obj.confirmDBThread()
  144.         return self.selected
  145.  
  146.     def getActive(self):
  147.         self.obj.confirmDBThread()
  148.         return self.active
  149.  
  150.     # Returns "normal" "selected" or "selected-inactive"
  151.     def getState(self):
  152.         if not self.selected:
  153.             return 'normal'
  154.         elif not self.active:
  155.             return 'selected-inactive'
  156.         else:
  157.             return 'selected'
  158.  
  159.     def redraw(self):
  160.         # Force a redraw by sending a change notification on the underlying
  161.         # DB object.
  162.         self.obj.signalChange()
  163.  
  164.     def isStatic(self):
  165.         """True if this Tab represents a StaticTab."""
  166.         return isinstance(self.obj, StaticTab)
  167.  
  168.     def isFeed(self):
  169.         """True if this Tab represents a Feed."""
  170.         return isinstance(self.obj, feed.Feed)
  171.  
  172.     def isChannelFolder(self):
  173.         """True if this Tab represents a Channel Folder."""
  174.         return isinstance(self.obj, folder.ChannelFolder)
  175.  
  176.     def isGuide(self):
  177.         """True if this Tab represents a Channel Guide."""
  178.         return isinstance(self.obj, guide.ChannelGuide)
  179.  
  180.     def isPlaylist(self):
  181.         """True if this Tab represents a Playlist."""
  182.         return isinstance(self.obj, playlist.SavedPlaylist)
  183.  
  184.     def isPlaylistFolder(self):
  185.         """True if this Tab represents a Playlist Folder."""
  186.         return isinstance(self.obj, folder.PlaylistFolder)
  187.  
  188.     def feedURL(self):
  189.         """If this Tab represents a Feed or a Guide, the URL. Otherwise None."""
  190.         if self.isFeed() or self.isGuide():
  191.             return self.obj.getURL()
  192.         else:
  193.             return None
  194.  
  195.     def objID(self):
  196.         """If this Tab represents a Feed, the feed's ID. Otherwise None."""
  197.         if isinstance (self.obj, database.DDBObject):
  198.             return self.obj.getID()
  199.         else:
  200.             return None
  201.  
  202.     def getID(self):
  203.         """Gets an id that can be used to lookup this tab from views.allTabs.
  204.  
  205.         NOTE: Tabs are mapped database objects, they don't have actual
  206.         DDBObject ids.
  207.         """
  208.         return self.obj.getID()
  209.  
  210.     def signalChange(self, needsSave=True):
  211.         """Call signalChange on the object that is mapped to this tab (the
  212.         StaticTab, Feed, Playlist, etc.)
  213.         """
  214.         self.obj.signalChange(needsSave=needsSave)
  215.  
  216.     def idExists(self):
  217.         """Returns True if the object that maps to this tab still exists in
  218.         the DB.
  219.         """
  220.  
  221.         return self.obj.idExists()
  222.  
  223.     def onDeselected(self, frame):
  224.         self.display.onDeselect(frame)
  225.  
  226.     def getFragment(self):
  227.         """URL fragment to use as an anchor.  This lets us scroll the tablist
  228.         so that this tab is on top.
  229.         """
  230.         return 'tab-%d' % self.obj.getID()
  231.  
  232. def expandedFolderFilter(tab):
  233.     folder = tab.obj.getFolder()
  234.     return folder is None or folder.getExpanded()
  235.  
  236. class TabOrder(database.DDBObject):
  237.     """TabOrder objects keep track of the order of the tabs.  Democracy
  238.     creates 2 of these, one to track channels/channel folders and another to
  239.     track playlists/playlist folders.
  240.     """
  241.     def __init__(self, type):
  242.         """Construct a TabOrder.  type should be either "channel", or
  243.         "playlist".
  244.         """
  245.         checkU(type)
  246.         self.type = type
  247.         self.tab_ids = []
  248.         self._initRestore()
  249.         decorated = [(t.obj.getTitle().lower(), t) for t in self.tabView]
  250.         decorated.sort()
  251.         for sortkey, tab in decorated:
  252.             self.trackedTabs.appendID(tab.getID())
  253.         database.DDBObject.__init__(self)
  254.  
  255.     def onRestore(self):
  256.         self._initRestore()
  257.         eventloop.addIdle(self.checkForNonExistentIds, 
  258.                 "checking for non-existent TabOrder ids")
  259.  
  260.     def _initRestore(self):
  261.         if self.type == u'channel':
  262.             self.tabView = views.feedTabs
  263.         elif self.type == u'playlist':
  264.             self.tabView = views.playlistTabs
  265.         else:
  266.             raise ValueError("Bad type for TabOrder")
  267.         self.trackedTabs = TrackedIDList(self.tabView, self.tab_ids)
  268.         self.trackedTabs.setFilter(expandedFolderFilter)
  269.         self.tabView.addAddCallback(self.onAddTab)
  270.         self.tabView.addRemoveCallback(self.onRemoveTab)
  271.  
  272.     def checkForNonExistentIds(self):
  273.         changed = False
  274.         for id in self.tab_ids[:]:
  275.             if not self.tabView.idExists(id):
  276.                 self.trackedTabs.removeID(id)
  277.                 logging.warn("Throwing away non-existent TabOrder id: %s", id)
  278.                 changed = True
  279.         if changed:
  280.             self.signalChange()
  281.  
  282.     def makeLastTabVisible(self, obj):
  283.         try:
  284.             tabDisplay = app.controller.tabDisplay
  285.         except AttributeError:
  286.             # haven't created the tab display yet, just ignore this call
  287.             return
  288.         tabToShow = obj
  289.         # try to go back a little to make the view prettier
  290.         self.trackedTabs.view.moveCursorToID(obj.objID())
  291.         for i in range(3):
  292.             last = self.trackedTabs.view.getPrev()
  293.             if last is None:
  294.                 break
  295.             tabToShow = last
  296.         if hasattr(tabDisplay, 'navigateToFragment'):
  297.             tabDisplay.navigateToFragment(tabToShow.getFragment())
  298.         else:
  299.             logging.warn("HTMLDisplay.navigateToFragment not implemented")
  300.  
  301.     def getView(self):
  302.         """Get a database view for this tab ordering."""
  303.         return self.trackedTabs.view
  304.  
  305.     def getAllTabs(self):
  306.         """Get all the tabs in this tab ordering (in order), regardless if
  307.         they are visible in the tab list or not.
  308.         """
  309.         return [self.tabView.getObjectByID(id) for id in self.tab_ids \
  310.                 if self.tabView.idExists(id) ]
  311.  
  312.     def onAddTab(self, obj, id):
  313.         if id not in self.trackedTabs:
  314.             self.trackedTabs.appendID(id, sendSignalChange=False)
  315.             obj.signalChange(needsSave=False)
  316.             self.signalChange()
  317.             self.makeLastTabVisible(obj)
  318.  
  319.     def onRemoveTab(self, obj, id):
  320.         if id in self.trackedTabs:
  321.             self.trackedTabs.removeID(id)
  322.         self.signalChange()
  323.  
  324.     def handleDNDReorder(self, anchorItem, draggedIDs):
  325.         """Handle drag-and-drop reordering of the tab order."""
  326.  
  327.         for iid in draggedIDs:
  328.             if iid not in self.trackedTabs:
  329.                 raise ValueError("ID not in TabOrder: %s", iid)
  330.         if anchorItem is None:
  331.             newFolder = None
  332.         else:
  333.             newFolder = anchorItem.getFolder()
  334.  
  335.         childrenIDs = set()
  336.         for id in draggedIDs:
  337.             tab = self.trackedTabs.view.getObjectByID(id)
  338.             tab.obj.setFolder(newFolder)
  339.             if isinstance(tab.obj, folder.FolderBase):
  340.                 for child in tab.obj.getChildrenView():
  341.                     childrenIDs.add(child.getID())
  342.         toMove = draggedIDs.union(childrenIDs)
  343.         self.moveTabs(anchorItem, toMove, sendSignalChange=False)
  344.         self.signalChange()
  345.  
  346.     def moveTabs(self, anchorItem, toMove, sendSignalChange=True):
  347.         if anchorItem is not None:
  348.             self.trackedTabs.moveIDList(toMove, anchorItem.getID())
  349.         else:
  350.             self.trackedTabs.moveIDList(toMove, None)
  351.         if sendSignalChange:
  352.             self.signalChange()
  353.  
  354. # Remove all static tabs from the database
  355. def removeStaticTabs():
  356.     app.db.confirmDBThread()
  357.     for obj in views.staticTabsObjects:
  358.         obj.remove()
  359.  
  360. # Reload the StaticTabs in the database from the statictabs.xml resource file.
  361. def reloadStaticTabs():
  362.     app.db.confirmDBThread()
  363.     # Wipe all of the StaticTabs currently in the database.
  364.     removeStaticTabs()
  365.  
  366.     # Load them anew from the resource file.
  367.     # NEEDS: maybe better error reporting?
  368.     document = parse(resources.path('statictabs.xml'))
  369.     for n in document.getElementsByTagName('statictab'):
  370.         tabTemplateBase = n.getAttribute('tabtemplatebase')
  371.         contentsTemplate = n.getAttribute('contentstemplate')
  372.         state = n.getAttribute('state')
  373.         order = int(n.getAttribute('order'))
  374.         StaticTab(tabTemplateBase, contentsTemplate, state, order)
  375.  
  376. def tabIterator():
  377.     """Iterates over all tabs in order"""
  378.     for tab in views.guideTabs:
  379.         yield tab
  380.     for tab in views.staticTabs:
  381.         yield tab
  382.     for tab in getSingletonDDBObject(views.channelTabOrder).getView():
  383.         yield tab
  384.     for tab in getSingletonDDBObject(views.playlistTabOrder).getView():
  385.         yield tab
  386.  
  387. def getViewForTab(tab):
  388.     if tab.type == 'guide':
  389.         return views.guideTabs
  390.     elif tab.type == 'statictab':
  391.         return views.staticTabs
  392.     elif tab.type == 'feed':
  393.         return getSingletonDDBObject(views.channelTabOrder).getView()
  394.     elif tab.type == 'playlist':
  395.         return getSingletonDDBObject(views.playlistTabOrder).getView()
  396.     raise AssertionError("Unknown tab type")
  397.