home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / components / pybridge.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  31.7 KB  |  866 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. from gettext import gettext as _
  19. from xpcom import components
  20. import ctypes
  21. import os
  22. import shutil
  23. import sys
  24. import traceback
  25. import _winreg
  26.  
  27. try:
  28.     import platformutils
  29.     platformutils.initializeLocale()
  30.     platformutils.setupLogging()
  31.     import gtcache
  32.     gtcache.init()
  33.     import app
  34.     import autoupdate
  35.     import eventloop
  36.     import config
  37.     import dialogs
  38.     import folder
  39.     import playlist
  40.     import prefs
  41.     import platformcfg
  42.     import singleclick
  43.     import frontend
  44.     import util
  45.     import menubar
  46.     import feed
  47.     import database
  48.     from frontend_implementation import HTMLDisplay
  49.     from frontend_implementation.UIBackendDelegate import UIBackendDelegate
  50.     from eventloop import asUrgent, asIdle
  51.     from platformutils import getLongPathName
  52.     import searchengines
  53.     import views
  54.     import moviedata
  55.     import migrateappname
  56.     import keyboard
  57.     moviedata.RUNNING_MAX = 1
  58. except:
  59.     errorOnImport = True
  60.     # get a fallback error message in case we can't import util either
  61.     import traceback
  62.     importErrorMessage = (_("Error importing modules:\n%s") %
  63.         traceback.format_exc())
  64.     try:
  65.         import util
  66.         importErrorMessage = util.failed(_("Starting up"),
  67.                 withExn=True)
  68.     except:
  69.         raise
  70.     # we need to make a fake asUrgent since we probably couldn't import
  71.     # eventloop.
  72.     def asUrgent(func):
  73.         return func
  74.     def asIdle(func):
  75.         return func
  76. else:
  77.     errorOnImport = False
  78.  
  79. # See http://www.xulplanet.com/tutorials/xultu/keyshort.html
  80. XUL_MOD_STRINGS = {menubar.CTRL : 'control',
  81.                    menubar.ALT:   'alt',
  82.                    menubar.SHIFT: 'shift'}
  83. XUL_KEY_STRINGS ={ menubar.RIGHT_ARROW: 'VK_RIGHT',
  84.                    menubar.LEFT_ARROW:  'VK_LEFT',
  85.                    menubar.UP_ARROW:    'VK_UP',
  86.                    menubar.DOWN_ARROW:  'VK_DOWN',
  87.                    menubar.SPACE : 'VK_SPACE',
  88.                    menubar.ENTER: 'VK_ENTER',
  89.                    menubar.DELETE: 'VK_DELETE',
  90.                    menubar.BKSPACE: 'VK_BACK',
  91.                    menubar.F1: 'VK_F1',
  92.                    menubar.F2: 'VK_F2',
  93.                    menubar.F3: 'VK_F3',
  94.                    menubar.F4: 'VK_F4',
  95.                    menubar.F5: 'VK_F5',
  96.                    menubar.F6: 'VK_F6',
  97.                    menubar.F7: 'VK_F7',
  98.                    menubar.F8: 'VK_F8',
  99.                    menubar.F9: 'VK_F9',
  100.                    menubar.F10: 'VK_F10',
  101.                    menubar.F11: 'VK_F11',
  102.                    menubar.F12: 'VK_F12'}
  103.  
  104. # Extent the ShortCut class to include a XULString() function
  105. def XULKey(shortcut):
  106.     if isinstance(shortcut.key, int):
  107.         return XUL_KEY_STRINGS[shortcut.key]
  108.     else:
  109.         return shortcut.key.upper()
  110.  
  111. def XULModifier(shortcut):
  112.     if shortcut.key is None:
  113.         return None
  114.     output = []
  115.     for modifier in shortcut.modifiers:
  116.         output.append(XUL_MOD_STRINGS[modifier])
  117.     return ' '.join(output)
  118.  
  119. def XULDisplayedShortcut(item):
  120.     """Return the index of the default shortcut.  Normally items only have 1
  121.     shortcut, which is an easy case.  If items have multiple shortcuts,
  122.     normally we display the last one in the menus, however there are some
  123.     special cases.
  124.  
  125.     Shortcut indicies are 1-based
  126.     """
  127.  
  128.     if item.action.startswith("Remove"):
  129.         return 1
  130.     return len(item.shortcuts)
  131.  
  132. nsIEventQueueService = components.interfaces.nsIEventQueueService
  133. nsIProperties = components.interfaces.nsIProperties
  134. nsIFile = components.interfaces.nsIFile
  135. nsIProxyObjectManager = components.interfaces.nsIProxyObjectManager
  136. pcfIDTVPyBridge = components.interfaces.pcfIDTVPyBridge
  137. pcfIDTVJSBridge = components.interfaces.pcfIDTVJSBridge
  138. pcfIDTVVLCRenderer = components.interfaces.pcfIDTVVLCRenderer
  139.  
  140. def makeComp(clsid, iid):
  141.     """Helper function to get an XPCOM component"""
  142.     return components.classes[clsid].createInstance(iid)
  143.  
  144. def makeService(clsid, iid):
  145.     """Helper function to get an XPCOM service"""
  146.     return components.classes[clsid].getService(iid)
  147.  
  148. def createProxyObjects():
  149.     """Creates the jsbridge and vlcrenderer xpcom components, then wraps them in
  150.     a proxy object, then stores them in the frontend module.  By making them
  151.     proxy objects, we ensure that the calls to them get made in the xul event
  152.     loop.
  153.     """
  154.  
  155.     proxyManager = makeComp("@mozilla.org/xpcomproxy;1",
  156.             nsIProxyObjectManager)
  157.     eventQueueService = makeService("@mozilla.org/event-queue-service;1",
  158.             nsIEventQueueService)
  159.     xulEventQueue = eventQueueService.getSpecialEventQueue(
  160.             nsIEventQueueService.UI_THREAD_EVENT_QUEUE)
  161.  
  162.     jsBridge = makeService("@participatoryculture.org/dtv/jsbridge;1",
  163.             pcfIDTVJSBridge)
  164.     frontend.jsBridge = proxyManager.getProxyForObject(xulEventQueue,
  165.             pcfIDTVJSBridge, jsBridge, nsIProxyObjectManager.INVOKE_ASYNC |
  166.             nsIProxyObjectManager.FORCE_PROXY_CREATION)
  167.  
  168.     vlcRenderer = makeService("@participatoryculture.org/dtv/vlc-renderer;1",
  169.             pcfIDTVVLCRenderer)
  170.     frontend.vlcRenderer = proxyManager.getProxyForObject(xulEventQueue,
  171.             pcfIDTVVLCRenderer, vlcRenderer, 
  172.             nsIProxyObjectManager.INVOKE_SYNC |
  173.             nsIProxyObjectManager.FORCE_PROXY_CREATION)
  174.  
  175. def initializeProxyObjects(window):
  176.     frontend.vlcRenderer.init(window)
  177.     frontend.jsBridge.init(window)
  178.  
  179. def initializeHTTPProxy():
  180.     klass = components.classes["@mozilla.org/preferences-service;1"]
  181.     xulprefs = klass.getService(components.interfaces.nsIPrefService)
  182.     branch = xulprefs.getBranch("network.proxy.")
  183.     if config.get(prefs.HTTP_PROXY_ACTIVE):                     
  184.         branch.setIntPref("type",1)
  185.         branch.setCharPref("http", config.get(prefs.HTTP_PROXY_HOST))
  186.         branch.setCharPref("ssl", config.get(prefs.HTTP_PROXY_HOST))
  187.         branch.setIntPref("http_port", config.get(prefs.HTTP_PROXY_PORT))
  188.         branch.setIntPref("ssl_port", config.get(prefs.HTTP_PROXY_PORT))
  189.         branch.setBoolPref("share_proxy_settings", True)
  190.     else:
  191.         branch.setIntPref("type",0)
  192.  
  193. def registerHttpObserver():
  194.     observer = makeComp("@participatoryculture.org/dtv/httprequestobserver;1",
  195.         components.interfaces.nsIObserver)
  196.     observer_service = makeService("@mozilla.org/observer-service;1",
  197.             components.interfaces.nsIObserverService)
  198.     observer_service.addObserver(observer, "http-on-modify-request", False);
  199.         
  200. def getArgumentList(commandLine):
  201.     """Convert a nsICommandLine component to a list of arguments to pass
  202.     to the singleclick module."""
  203.  
  204.     args = [commandLine.getArgument(i) for i in range(commandLine.length)]
  205.     # filter out the application.ini that gets included
  206.     if len(args) > 0 and args[0].lower().endswith('application.ini'):
  207.         args = args[1:]
  208.     return [getLongPathName(path) for path in args]
  209.  
  210. # Copied from resources.py; if you change this function here, change it
  211. # there too.
  212. def appRoot():
  213.     klass = components.classes["@mozilla.org/file/directory_service;1"]
  214.     service = klass.getService(nsIProperties)
  215.     file = service.get("XCurProcD", nsIFile)
  216.     return file.path
  217.  
  218. # Functions to convert menu information into XUL form
  219. def XULifyLabel(label):
  220.     return label.replace(u'_',u'')
  221. def XULAccelFromLabel(label):
  222.     parts = label.split(u'_')
  223.     if len(parts) > 1:
  224.         return parts[1][0]
  225.  
  226.  
  227. def prefsChangeCallback(mapped, id):
  228.     if isinstance (mapped.actualFeed, feed.DirectoryWatchFeedImpl):
  229.         frontend.jsBridge.directoryWatchAdded (str(id), mapped.dir, mapped.visible);
  230.  
  231. def prefsRemoveCallback(mapped, id):
  232.     if isinstance (mapped.actualFeed, feed.DirectoryWatchFeedImpl):
  233.         frontend.jsBridge.directoryWatchRemoved (str(id));
  234.  
  235.  
  236. @asIdle
  237. def startPrefs():
  238.     for f in views.feeds:
  239.         if isinstance (f.actualFeed, feed.DirectoryWatchFeedImpl):
  240.             frontend.jsBridge.directoryWatchAdded (str(f.getID()), f.dir, f.visible)
  241.     views.feeds.addChangeCallback(prefsChangeCallback)
  242.     views.feeds.addAddCallback(prefsChangeCallback)
  243.     views.feeds.addRemoveCallback(prefsRemoveCallback)
  244.  
  245. @asIdle
  246. def endPrefs():
  247.     views.feeds.removeChangeCallback(prefsChangeCallback)
  248.     views.feeds.removeAddCallback(prefsChangeCallback)
  249.     views.feeds.removeRemoveCallback(prefsRemoveCallback)
  250.  
  251.  
  252. class PyBridge:
  253.     _com_interfaces_ = [pcfIDTVPyBridge]
  254.     _reg_clsid_ = "{F87D30FF-C117-401e-9194-DF3877C926D4}"
  255.     _reg_contractid_ = "@participatoryculture.org/dtv/pybridge;1"
  256.     _reg_desc_ = "Bridge into DTV Python core"
  257.  
  258.     def __init__(self):
  259.         migrateappname.migrateSupport('Democracy Player', 'Miro')
  260.         self.started = False
  261.         self.cursorDisplayCount = 0
  262.         if not errorOnImport:
  263.             self.delegate = UIBackendDelegate()
  264.  
  265.     def getStartupError(self):
  266.         if not errorOnImport:
  267.             return ""
  268.         else:
  269.             return importErrorMessage
  270.  
  271.     def onStartup(self, window):
  272.         if self.started:
  273.             util.failed(_("Loading window"), 
  274.                 details=_("onStartup called twice"))
  275.             return
  276.         else:
  277.             self.started = True
  278.  
  279.         initializeProxyObjects(window)
  280.         registerHttpObserver()
  281.         app.main()
  282.         initializeHTTPProxy()
  283.  
  284.     @asUrgent
  285.     def initializeViews(self):
  286.         views.initialize()
  287.  
  288.     def onShutdown(self):
  289.         frontend.vlcRenderer.stop()
  290.         app.controller.onShutdown()
  291.  
  292.     def deleteVLCCache(self):
  293.         appDataPath = platformcfg.getSpecialFolder("AppData")
  294.         if appDataPath:
  295.             vlcCacheDir = os.path.join(appDataPath, "PCF-VLC")
  296.             shutil.rmtree(vlcCacheDir, ignore_errors=True)
  297.  
  298.     def shortenDirectoryName(self, path):
  299.         """Shorten a directory name by recognizing well-known nicknames, like
  300.         "Desktop", and "My Documents"
  301.         """
  302.  
  303.         tries = [ "My Music", "My Pictures", "My Videos", "My Documents",
  304.             "Desktop", 
  305.         ]
  306.  
  307.         for name in tries:
  308.             virtualPath = platformcfg.getSpecialFolder(name)
  309.             if virtualPath is None:
  310.                 continue
  311.             if path == virtualPath:
  312.                 return name
  313.             elif path.startswith(virtualPath):
  314.                 relativePath = path[len(virtualPath):]
  315.                 if relativePath.startswith("\\"):
  316.                     return name + relativePath
  317.                 else:
  318.                     return "%s\\%s" % (name, relativePath)
  319.         return path
  320.  
  321.  
  322.     # Preference setters/getters.
  323.     #
  324.     # NOTE: these are not in the mail event loop, so we have to be careful in
  325.     # accessing data.  config.get and config.set are threadsafe though.
  326.     #
  327.     def getRunAtStartup(self):
  328.         return config.get(prefs.RUN_AT_STARTUP)
  329.     def setRunAtStartup(self, value):
  330.         self.delegate.setRunAtStartup(value)
  331.         config.set(prefs.RUN_AT_STARTUP, value)
  332.     def getCheckEvery(self):
  333.         return config.get(prefs.CHECK_CHANNELS_EVERY_X_MN)
  334.     def setCheckEvery(self, value):
  335.         return config.set(prefs.CHECK_CHANNELS_EVERY_X_MN, value)
  336.     def getMoviesDirectory(self):
  337.         return config.get(prefs.MOVIES_DIRECTORY)
  338.     def changeMoviesDirectory(self, path, migrate):
  339.         app.changeMoviesDirectory(path, migrate)
  340.     def getLimitUpstream(self):
  341.         return config.get(prefs.LIMIT_UPSTREAM)
  342.     def setLimitUpstream(self, value):
  343.         config.set(prefs.LIMIT_UPSTREAM, value)
  344.     def getLimitUpstreamAmount(self):
  345.         return config.get(prefs.UPSTREAM_LIMIT_IN_KBS)
  346.     def setLimitUpstreamAmount(self, value):
  347.         return config.set(prefs.UPSTREAM_LIMIT_IN_KBS, value)
  348.     def getMaxManual(self):
  349.         return config.get(prefs.MAX_MANUAL_DOWNLOADS)
  350.     def setMaxManual(self, value):
  351.         return config.set(prefs.MAX_MANUAL_DOWNLOADS, value)
  352.     def startPrefs(self):
  353.         startPrefs()
  354.     def updatePrefs(self):
  355.         endPrefs()
  356.     def getPreserveDiskSpace(self):
  357.         return config.get(prefs.PRESERVE_DISK_SPACE)
  358.     def setPreserveDiskSpace(self, value):
  359.         config.set(prefs.PRESERVE_DISK_SPACE, value)
  360.     def getPreserveDiskSpaceAmount(self):
  361.         return config.get(prefs.PRESERVE_X_GB_FREE)
  362.     def setPreserveDiskSpaceAmount(self, value):
  363.         return config.set(prefs.PRESERVE_X_GB_FREE, value)
  364.     def getExpireAfter(self):
  365.         return config.get(prefs.EXPIRE_AFTER_X_DAYS)
  366.     def setExpireAfter(self, value):
  367.         return config.set(prefs.EXPIRE_AFTER_X_DAYS, value)
  368.     def getSinglePlayMode(self):
  369.         return config.get(prefs.SINGLE_VIDEO_PLAYBACK_MODE)
  370.     def setSinglePlayMode(self, value):
  371.         return config.set(prefs.SINGLE_VIDEO_PLAYBACK_MODE, value)
  372.     def getResumeVideosMode(self):
  373.         return config.get(prefs.RESUME_VIDEOS_MODE)
  374.     def setResumeVideosMode(self, value):
  375.         return config.set(prefs.RESUME_VIDEOS_MODE, value)
  376.     def getBTMinPort(self):
  377.         return config.get(prefs.BT_MIN_PORT)
  378.     def setBTMinPort(self, value):
  379.         return config.set(prefs.BT_MIN_PORT, value)
  380.     def getBTMaxPort(self):
  381.         return config.get(prefs.BT_MAX_PORT)
  382.     def setBTMaxPort(self, value):
  383.         return config.set(prefs.BT_MAX_PORT, value)
  384.     def getStartupTasksDone(self):
  385.         return config.get(prefs.STARTUP_TASKS_DONE)
  386.     def setStartupTasksDone(self, value):
  387.         return config.set(prefs.STARTUP_TASKS_DONE, value)
  388.     def getWarnIfDownloadingOnQuit(self):
  389.         return config.get(prefs.WARN_IF_DOWNLOADING_ON_QUIT)
  390.     def setWarnIfDownloadingOnQuit(self, value):
  391.         return config.set(prefs.WARN_IF_DOWNLOADING_ON_QUIT, value)
  392.  
  393.     def handleCommandLine(self, commandLine):
  394.         # Here's a massive hack to get command line parameters into config
  395.         args = getArgumentList(commandLine)
  396.         for x in range(len(args)-1):
  397.             if args[x] == '--theme':
  398.                 config.__currentThemeHack = args[x+1]
  399.                 config.load(theme = args[x+1])
  400.                 break
  401.  
  402.         # Doesn't matter if this executes before the call to
  403.         # parseCommandLineArgs in app.py. -clahey
  404.         self._handleCommandLine(commandLine)
  405.  
  406.     @asUrgent
  407.     def _handleCommandLine(self, commandLine):
  408.         singleclick.handleCommandLineArgs(getArgumentList(commandLine))
  409.  
  410.     def pageLoadFinished(self, area, url):
  411.         eventloop.addUrgentCall(HTMLDisplay.runPageFinishCallback, 
  412.                 "%s finish callback" % area, args=(area, url))
  413.  
  414.     @asUrgent
  415.     def openFile(self, path):
  416.         singleclick.openFile(getLongPathName(path))
  417.  
  418.     @asUrgent
  419.     def setVolume(self, volume):
  420.         volume = float(volume)
  421.         config.set(prefs.VOLUME_LEVEL, volume)
  422.         if hasattr(app.controller, 'videoDisplay'):
  423.             app.controller.videoDisplay.setVolume(volume, moveSlider=False)
  424.  
  425.     @asUrgent
  426.     def quit(self):
  427.         if app.controller.finishedStartup:
  428.             app.controller.quit()
  429.  
  430.     @asUrgent
  431.     def removeCurrentChannel(self):
  432.         app.controller.removeCurrentFeed()
  433.  
  434.     @asUrgent
  435.     def updateCurrentChannel(self):
  436.         app.controller.updateCurrentFeed()
  437.  
  438.     @asUrgent
  439.     def updateChannels(self):
  440.         app.controller.updateAllFeeds()
  441.  
  442.     @asUrgent
  443.     def showHelp(self):
  444.         self.delegate.openExternalURL(config.get(prefs.HELP_URL))
  445.  
  446.     @asUrgent
  447.     def reportBug(self):
  448.         self.delegate.openExternalURL(config.get(prefs.BUG_REPORT_URL))
  449.  
  450.     @asUrgent
  451.     def copyChannelLink(self):
  452.         app.controller.copyCurrentFeedURL()
  453.  
  454.     @asUrgent
  455.     def handleContextMenu(self, index):
  456.         self.delegate.handleContextMenu(index)
  457.  
  458.     @asUrgent
  459.     def handleSimpleDialog(self, id, buttonIndex):
  460.         self.delegate.handleDialog(id, buttonIndex)
  461.  
  462.     @asUrgent
  463.     def handleCheckboxDialog(self, id, buttonIndex, checkbox_value):
  464.         self.delegate.handleDialog(id, buttonIndex,
  465.                 checkbox_value=checkbox_value)
  466.     @asUrgent
  467.     def handleCheckboxTextboxDialog(self, id, buttonIndex, checkbox_value,
  468.                                     textbox_value):
  469.         self.delegate.handleDialog(id, buttonIndex,
  470.                                    checkbox_value=checkbox_value,
  471.                                    textbox_value=textbox_value)
  472.  
  473.     @asUrgent
  474.     def handleHTTPAuthDialog(self, id, buttonIndex, username, password):
  475.         self.delegate.handleDialog(id, buttonIndex, username=username,
  476.                 password=password)
  477.  
  478.     @asUrgent
  479.     def handleTextEntryDialog(self, id, buttonIndex, text):
  480.         self.delegate.handleDialog(id, buttonIndex, value=text)
  481.  
  482.     @asUrgent
  483.     def handleSearchChannelDialog(self, id, buttonIndex, term, style, loc):
  484.         self.delegate.handleDialog(id, buttonIndex, term=term, style=style, loc=loc)
  485.     @asUrgent
  486.     def handleFileDialog(self, id, pathname):
  487.         self.delegate.handleFileDialog(id, pathname)
  488.  
  489.     @asUrgent
  490.     def addChannel(self, url):
  491.         app.controller.addAndSelectFeed(url)
  492.  
  493.     @asUrgent
  494.     def openURL(self, url):
  495.         self.delegate.openExternalURL(url)
  496.  
  497.     @asUrgent
  498.     def playPause(self):
  499.         app.controller.playbackController.playPause()
  500.  
  501.     @asUrgent
  502.     def pause(self):
  503.         if hasattr(app.controller, 'playbackController'):
  504.             app.controller.playbackController.pause()
  505.  
  506.     @asUrgent
  507.     def stop(self):
  508.         app.controller.playbackController.stop()
  509.  
  510.     @asUrgent
  511.     def skip(self, step):
  512.         app.controller.playbackController.skip(step)
  513.  
  514.     @asUrgent
  515.     def skipPrevious(self):
  516.         app.controller.playbackController.skip(-1, allowMovieReset=False)
  517.  
  518.     @asUrgent
  519.     def onMovieFinished(self):
  520.         app.controller.playbackController.onMovieFinished()
  521.  
  522.     @asUrgent
  523.     def loadURLInBrowser(self, browserId, url):
  524.         try:
  525.             display = app.controller.frame.selectedDisplays[browserId]
  526.         except KeyError:
  527.             print "No HTMLDisplay for %s in loadURLInBrowser: "% browserId
  528.         else:
  529.             display.onURLLoad(url)
  530.  
  531.     @asUrgent
  532.     def performSearch(self, engine, query):
  533.         app.controller.performSearch(engine, query)
  534.  
  535.     # Returns a list of search engine titles and names
  536.     # Should we just keep a map of engines to names?
  537.     def getSearchEngineNames(self):
  538.         out = []
  539.         for engine in views.searchEngines:
  540.             out.append(engine.name)
  541.         return out
  542.     def getSearchEngineTitles(self):
  543.         out = []
  544.         for engine in views.searchEngines:
  545.             out.append(engine.title)
  546.         return out
  547.  
  548.     def showCursor(self, display):
  549.         # ShowCursor has an amazing API.  From Microsoft:
  550.         #
  551.         # This function sets an internal display counter that determines
  552.         # whether the cursor should be displayed. The cursor is displayed
  553.         # only if the display count is greater than or equal to 0. If a
  554.         # mouse is installed, the initial display count is 0.  If no mouse
  555.         # is installed, the display count is -1
  556.         #
  557.         # How do we get the initial display count?  There's no method.  We
  558.         # assume it's 0 and the mouse is plugged in.
  559.         if ((display and self.cursorDisplayCount >= 0) or
  560.                 (not display and self.cursorDisplayCount < 0)):
  561.             return
  562.         if display:
  563.             arg = 1
  564.         else:
  565.             arg = 0
  566.         self.cursorDisplayCount = ctypes.windll.user32.ShowCursor(arg)
  567.  
  568.     @asUrgent
  569.     def createNewPlaylist(self):
  570.         playlist.createNewPlaylist()
  571.  
  572.     @asUrgent
  573.     def createNewPlaylistFolder(self):
  574.         folder.createNewPlaylistFolder()
  575.  
  576.     @asUrgent
  577.     def createNewSearchChannel(self):
  578.         app.controller.addSearchFeed()
  579.  
  580.     @asUrgent
  581.     def createNewChannelFolder(self):
  582.         folder.createNewChannelFolder()
  583.  
  584.     @asUrgent
  585.     def handleDrop(self, dropData, dropType, sourceData):
  586.         app.controller.handleDrop(dropData, dropType, sourceData)
  587.  
  588.  
  589.     @asUrgent
  590.     def removeCurrentSelection(self):
  591.         app.controller.removeCurrentSelection()
  592.  
  593.     
  594.     @asUrgent
  595.     def checkForUpdates(self):
  596.         autoupdate.checkForUpdates()
  597.  
  598.     @asUrgent
  599.     def removeCurrentItems(self):
  600.         app.controller.removeCurrentItems()
  601.  
  602.     @asUrgent
  603.     def copyCurrentItemURL(self):
  604.         app.controller.copyCurrentItemURL()
  605.  
  606.     @asUrgent
  607.     def selectAllItems(self):
  608.         app.controller.selectAllItems()
  609.  
  610.     @asUrgent
  611.     def createNewChannelFolder(self):
  612.         folder.createNewChannelFolder()
  613.  
  614.     @asUrgent
  615.     def createNewChannelGuide(self):
  616.         app.controller.addAndSelectGuide()
  617.  
  618.     @asUrgent
  619.     def createNewDownload(self):
  620.         app.controller.newDownload()
  621.  
  622.     @asUrgent
  623.     def importChannels(self):
  624.         app.controller.importChannels()
  625.  
  626.     @asUrgent
  627.     def exportChannels(self):
  628.         app.controller.exportChannels()
  629.  
  630.     @asUrgent
  631.     def addChannel(self):
  632.         app.controller.addAndSelectFeed()
  633.  
  634.     @asUrgent
  635.     def renameCurrentChannel(self):
  636.         app.controller.renameCurrentChannel()
  637.  
  638.     @asUrgent
  639.     def recommendCurrentChannel(self):
  640.         app.controller.recommendCurrentFeed()
  641.  
  642.     @asUrgent
  643.     def renameCurrentPlaylist(self):
  644.         app.controller.renameCurrentPlaylist()
  645.  
  646.     @asUrgent
  647.     def removeCurrentPlaylist(self):
  648.         app.controller.removeCurrentPlaylist()
  649.  
  650.     def openDonatePage(self):
  651.         self.delegate.openExternalURL(config.get(prefs.DONATE_URL))
  652.  
  653.     def openBugTracker(self):
  654.         self.delegate.openExternalURL(config.get(prefs.BUG_TRACKER_URL))
  655.  
  656.     @asUrgent
  657.     def saveVideoFile(self, path):
  658.         if frontend.currentVideoPath is None:
  659.             return
  660.         app.saveVideo(frontend.currentVideoPath, path)
  661.  
  662.     def startupDoSearch(self, path):
  663.         if path.endswith(":"):
  664.             path = path + "\\" # convert C: to C:\
  665.         frontend.startup.doSearch(path)
  666.  
  667.     def startupCancelSearch(self):
  668.         frontend.startup.cancelSearch()
  669.  
  670.     def getSpecialFolder(self, name):
  671.         return platformcfg.getSpecialFolder(name)
  672.  
  673.     def extractFinish (self, duration, screenshot_success):
  674.         app.controller.videoDisplay.extractFinish(duration, screenshot_success)
  675.  
  676.     def createProxyObjects(self):
  677.         createProxyObjects()
  678.  
  679.     def printOut(self, output):
  680.         print output
  681.  
  682.     def addMenubar(self, document):
  683.         menubarElement = document.getElementById("titlebar-menu")
  684.         trayMenuElement = document.getElementById("traypopup")
  685.         keysetElement = document.createElementNS(
  686.          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","keyset")
  687.  
  688.         for menu in (menubar.menubar.menus + (menubar.traymenu,)):
  689.             for item in menu.menuitems:
  690.                 if isinstance(item, menubar.MenuItem):
  691.                     count = 0
  692.                     for shortcut in item.shortcuts:
  693.                         count += 1
  694.                         keyElement = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","key")
  695.                         keyElement.setAttribute("id", "%s-key%d" % (item.action, count))
  696.                         if len(XULKey(shortcut)) == 1:
  697.                             keyElement.setAttribute("key", XULKey(shortcut))
  698.                         else:
  699.                             keyElement.setAttribute("keycode", XULKey(shortcut))
  700.                         if XULKey(shortcut) == 'VK_SPACE':
  701.                             # spacebar doesn't get display text for some reason
  702.                             keyElement.setAttribute('keytext', _('Spacebar'))
  703.                         if len(shortcut.modifiers) > 0:
  704.                             keyElement.setAttribute("modifiers", XULModifier(shortcut))
  705.                         keyElement.setAttribute("command", item.action)
  706.                         keysetElement.appendChild(keyElement)
  707.         menubarElement.appendChild(keysetElement)
  708.  
  709.         for menu in menubar.menubar.menus:
  710.             menuElement = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","menu")
  711.             menuElement.setAttribute("id", "menu-%s" % menu.action.lower())
  712.             menuElement.setAttribute("label", XULifyLabel(menu.getLabel(menu.action)))
  713.             if XULAccelFromLabel(menu.getLabel(menu.action)):
  714.                 menuElement.setAttribute("accesskey", XULAccelFromLabel(menu.getLabel(menu.action)))
  715.             menupopup = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","menupopup")
  716.  
  717.             menupopup.setAttribute("id", "menupopup-%s" % menu.action.lower())
  718.             menuElement.appendChild(menupopup)
  719.             menubarElement.appendChild(menuElement)
  720.             for item in menu.menuitems:
  721.                 if isinstance(item, menubar.Separator):
  722.                     menuitem = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","menuseparator")
  723.                 else:
  724.                     menuitem = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","menuitem")
  725.                     menuitem.setAttribute("id","menuitem-%s" % item.action.lower())
  726.                     menuitem.setAttribute("label",XULifyLabel(menu.getLabel(item.action)))
  727.                     menuitem.setAttribute("command", item.action)
  728.                     if XULAccelFromLabel(item.label):
  729.                         menuitem.setAttribute("accesskey",
  730.                                           XULAccelFromLabel(item.label))
  731.                     if len(item.shortcuts)>0:
  732.                         menuitem.setAttribute("key","%s-key%d"%(item.action,
  733.                             XULDisplayedShortcut(item)))
  734.                         
  735.                 menupopup.appendChild(menuitem)
  736.  
  737.         for item in menubar.traymenu.menuitems:
  738.             if isinstance(item, menubar.Separator):
  739.                 menuitem = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","menuseparator")
  740.             else:
  741.                 menuitem = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","menuitem")
  742.                 menuitem.setAttribute("id","traymenu-%s" % item.action.lower())
  743.                 menuitem.setAttribute("label",XULifyLabel(item.label))
  744.                 menuitem.setAttribute("command", item.action)
  745.                 if XULAccelFromLabel(item.label):
  746.                     menuitem.setAttribute("accesskey",
  747.                                           XULAccelFromLabel(item.label))
  748.                 if len(item.shortcuts)>0:
  749.                     menuitem.setAttribute("key","%s-key%d"%(item.action,len(item.shortcuts)))
  750.                         
  751.             trayMenuElement.appendChild(menuitem)
  752.  
  753.     # Grab the database information, then throw it over the fence to
  754.     # the UI thread
  755.     @asIdle
  756.     def updateTrayMenus(self):
  757.         if views.initialized:
  758.             numUnwatched = len(views.unwatchedItems)
  759.             numDownloading = len(views.downloadingItems)
  760.             numPaused = len(views.pausedItems)
  761.         else:
  762.             numPaused = numDownloading = numUnwatched = 0
  763.         frontend.jsBridge.updateTrayMenus(numUnwatched, numDownloading, numPaused)
  764.  
  765.     # HACK ALERT - We should change this to take a dictionary instead
  766.     # of all of the possible database variables. Since there's no
  767.     # equivalent in XPCOM, it would be a pain, so we can wait. -- NN
  768.     def getLabel(self,action,state,unwatched = 0,downloading = 0, paused = 0):
  769.         variables = {}
  770.         variables['numUnwatched'] = unwatched
  771.         variables['numDownloading'] = downloading
  772.         variables['numPaused'] = paused
  773.  
  774.         # Ih8XPCOM
  775.         if len(state) == 0:
  776.             state = None
  777.  
  778.         ret = XULifyLabel(menubar.menubar.getLabel(action,state,variables))
  779.         if ret == action:
  780.             ret = XULifyLabel(menubar.traymenu.getLabel(action,state, variables))
  781.         return ret
  782.  
  783.     @asIdle
  784.     def addDirectoryWatch(self, filename):
  785.         feed.Feed (u"dtv:directoryfeed:%s" % (platformutils.makeURLSafe(filename),))
  786.  
  787.     @asIdle
  788.     def removeDirectoryWatch(self, id):
  789.         try:
  790.             obj = database.defaultDatabase.getObjectByID (int(id))
  791.             app.controller.removeFeeds ([obj])
  792.     except:
  793.         pass
  794.  
  795.     @asIdle
  796.     def toggleDirectoryWatchShown(self, id):
  797.         try:
  798.             obj = database.defaultDatabase.getObjectByID (int(id))
  799.             obj.setVisible (not obj.visible)
  800.         except:
  801.             pass
  802.  
  803.     @asUrgent
  804.     def playUnwatched(self):
  805.         minimizer = makeService(
  806.             "@participatoryculture.org/dtv/minimize;1",
  807.             components.interfaces.pcfIDTVMinimize)
  808.         if minimizer.isMinimized():
  809.             minimizer.minimizeOrRestore()
  810.         app.controller.frame.mainDisplayCallback(u'action:playUnwatched')
  811.  
  812.     @asIdle
  813.     def pauseDownloads(self):
  814.         app.controller.frame.mainDisplayCallback(u'action:pauseAll')
  815.  
  816.     @asIdle
  817.     def resumeDownloads(self):
  818.         app.controller.frame.mainDisplayCallback(u'action:resumeAll')
  819.  
  820.     def minimizeToTray(self):
  821.         return config.get(prefs.MINIMIZE_TO_TRAY)
  822.  
  823.     def setMinimizeToTray(self, newSetting):
  824.         config.set(prefs.MINIMIZE_TO_TRAY, newSetting)
  825.  
  826.     def handleKeyPress(self, keycode, shiftDown, controlDown):
  827.         keycode_to_portable_code = {
  828.             37: keyboard.LEFT,
  829.             38: keyboard.UP,
  830.             39: keyboard.RIGHT,
  831.             40: keyboard.DOWN,
  832.         }
  833.         if keycode in keycode_to_portable_code:
  834.             key = keycode_to_portable_code[keycode]
  835.             keyboard.handleKey(key, shiftDown, controlDown)
  836.  
  837.     def handleCloseButton(self):
  838.         if not app.controller.finishedStartup:
  839.             return
  840.         if config.get(prefs.MINIMIZE_TO_TRAY_ASK_ON_CLOSE):
  841.             self.askUserForCloseBehaviour()
  842.         elif config.get(prefs.MINIMIZE_TO_TRAY):
  843.             minimizer = makeService(
  844.                     "@participatoryculture.org/dtv/minimize;1",
  845.                     components.interfaces.pcfIDTVMinimize)
  846.             minimizer.minimizeOrRestore()
  847.         else:
  848.             self.quit()
  849.  
  850.     def askUserForCloseBehaviour(self):
  851.         title = _("Close to tray?")
  852.         description = _("When you click the red close button, would you like Miro to close to the system tray or quit?  You can change this setting later in the Options.")
  853.  
  854.         dialog = dialogs.ChoiceDialog(title, description, 
  855.                 dialogs.BUTTON_CLOSE_TO_TRAY, dialogs.BUTTON_QUIT)
  856.         def callback(dialog):
  857.             if dialog.choice is None:
  858.                 return
  859.             if dialog.choice == dialogs.BUTTON_CLOSE_TO_TRAY:
  860.                 config.set(prefs.MINIMIZE_TO_TRAY, True)
  861.             else:
  862.                 config.set(prefs.MINIMIZE_TO_TRAY, False)
  863.             config.set(prefs.MINIMIZE_TO_TRAY_ASK_ON_CLOSE, False)
  864.             self.handleCloseButton()
  865.         dialog.run(callback)
  866.