"""
$Id: mythtvgui.py,v 1.26 2007/05/12 23:43:24 frooby Exp $
Copyright (C) 2005 Tom Warkentin <tom@ixionstudios.com>
Myth TV specific utility library for XBMC.
"""
from singleton import getInstance
import mythtv
import mythtvskin
import mythtvutil
import os
import string
import time
import traceback
import xbmcgui
ACTION_MOVE_LEFT = 1
ACTION_MOVE_RIGHT = 2
ACTION_MOVE_UP = 3
ACTION_MOVE_DOWN = 4
ACTION_PAGE_UP = 5
ACTION_PAGE_DOWN = 6
ACTION_SELECT_ITEM = 7
ACTION_HIGHLIGHT_ITEM = 8
ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10
ACTION_SHOW_INFO = 11
ACTION_PAUSE = 12
ACTION_STOP = 13
ACTION_NEXT_ITEM = 14
ACTION_PREV_ITEM = 15
ACTION_SCROLL_UP = 111
ACTION_SCROLL_DOWN = 112
ACTION_CONTEXT_MENU = 117
# from xbmc/guilib/common/xbfont.h
ALIGN_LEFT = 0
ALIGN_RIGHT = 1
ALIGN_CENTER_X = 2
ALIGN_CENTER_Y = 4
ALIGN_TRUNCATED = 8
picBase = os.getcwd()[:-1]+'\\images\\'
# channel icons should be placed in images\channel and named after channel id
picType = "_square.png" # change to reflect you image type
#picType = ".png"
def debug( str ):
mythtvutil.debug( mythtvutil.DEBUG_MISC, str )
def checkSettings():
debug( "> mythtvgui.checkSettings()" )
try:
s = getInstance( mythtv.Settings )
s.loadSettings()
s.verifySettings()
except mythtv.SettingsException, ex:
raise Exception, mythtvutil.getLocalizedString( 86 )
debug( "< mythtvgui.checkSettings()" )
class BaseWindow( mythtvskin.XBMC_SKIN ):
def __init__( self ):
mythtvskin.XBMC_SKIN.__init__( self )
self.actionConsumed = 0
self.lock = 0
def onAction( self, action ):
debug( "> mythtvgui.BaseWindow.onAction( action=[%s] )"%action )
try:
# Lock out concurrent events - XBMC fires events in separate
# threads. Without the lock, two threads can be modifying window
# objects at the same time corrupting data structures.
if self.lock == 0:
try:
self.lock += 1
# call subclass defined hook
actionConsumed = self.onActionHook( action )
# check if action was not consumed
if actionConsumed == 0:
# process help request - if subclass hook overrides
# this behavior then it should have consumed the event
if action in (ACTION_SHOW_INFO, ACTION_CONTEXT_MENU):
id = self.getcontrolid( self.getFocus() )
if len( id ) > 0:
help = self.controls[id].getoption( 'help' )
else:
help = ""
if len( help ) > 0:
Dialog().ok(
mythtvutil.getLocalizedString( 26 ),
help )
actionConsumed = 1
# check if event was consumed
if actionConsumed == 0:
# check if parent dir selected
if action == ACTION_PARENT_DIR:
self.close()
else:
mythtvskin.XBMC_SKIN.onAction( self, action )
self.onActionPostHook( action )
self.lock -= 1
except:
self.lock -= 1
raise
except mythtv.ProtocolVersionException, ex:
Dialog().ok(
mythtvutil.getLocalizedString( 27 ),
mythtvutil.getLocalizedString( 109 )%str( ex ) )
except Exception, ex:
traceback.print_exc()
Dialog().ok( mythtvutil.getLocalizedString( 27 ), str( ex ) )
debug( "< mythtvgui.BaseWindow.onAction( action=[%s] )"%action )
def onActionHook( self, action ):
"""
Method that is called by BaseWindow class. This method is intended to
be overridden by subclasses to perform custom logic.
Return values:
0 Event was not consumed by the hook. Event will be handled
internally. (default)
1 Event was consumed by hook. No further processing will be done.
BaseWindow catches exceptions and displays dialog. Therefore this
type of logic does not need to replicated in this hook.
"""
return 0
def onActionPostHook( self, action ):
"""
Method that is called after internal processing is done on an action.
This is meant to be overridden if additional logic needs to be
performed after the internal processing is complete.
"""
pass
def onControl( self, control ):
debug( "> mythtvgui.BaseWindow.onControl( control=[%s] )"%control )
try:
if self.lock == 0:
try:
self.lock += 1
rc = self.onControlHook( control )
self.lock -= 1
except:
self.lock -= 1
raise
except mythtv.ProtocolVersionException, ex:
Dialog().ok(
mythtvutil.getLocalizedString( 27 ),
mythtvutil.getLocalizedString( 109 )%str( ex ) )
except Exception, ex:
traceback.print_exc( ex )
Dialog().ok( mythtvutil.getLocalizedString( 27 ), str( ex ) )
debug( "< mythtvgui.BaseWindow.onControl( control=[%s] )"%control )
def onControlHook( self, control ):
"""
Method that is called by BaseWindow class. This method is intended to
be overridden by subclasses to perform custom logic.
Return values:
0 Event was not consumed by the hook. Event will be handled
internally.
1 Event was consumed by hook. No further processing will be done.
(default)
BaseWindow catches exceptions and displays dialog. Therefore this
type of logic does not need to replicated in this hook.
"""
return 1
## Needed a new class for a DialogWindow as required for OSD
class BaseWindowDialog( mythtvskin.XBMC_SKIN_DIALOG ):
def __init__( self ):
mythtvskin.XBMC_SKIN_DIALOG.__init__( self )
self.actionConsumed = 0
self.lock = 0
def onAction( self, action ):
debug( "> mythtvgui.BaseWindowDialog.onAction( action=[%s] )"%action )
try:
# Lock out concurrent events - XBMC fires events in separate
# threads. Without the lock, two threads can be modifying window
# objects at the same time corrupting data structures.
if self.lock == 0:
try:
self.lock += 1
# call subclass defined hook
actionConsumed = self.onActionHook( action )
# check if action was not consumed
if actionConsumed == 0:
# process help request - if subclass hook overrides
# this behavior then it should have consumed the event
if action in (ACTION_SHOW_INFO, ACTION_CONTEXT_MENU):
id = self.getcontrolid( self.getFocus() )
if len( id ) > 0:
help = self.controls[id].getoption( 'help' )
else:
help = ""
if len( help ) > 0:
Dialog().ok(
mythtvutil.getLocalizedString( 26 ),
help )
actionConsumed = 1
# check if event was consumed
if actionConsumed == 0:
# check if parent dir selected
if action == ACTION_PARENT_DIR:
self.close()
else:
mythtvskin.XBMC_SKIN_DIALOG.onAction( self, action )
self.onActionPostHook( action )
self.lock -= 1
except:
self.lock -= 1
raise
except mythtv.ProtocolVersionException, ex:
Dialog().ok(
mythtvutil.getLocalizedString( 27 ),
mythtvutil.getLocalizedString( 109 )%str( ex ) )
except Exception, ex:
traceback.print_exc()
Dialog().ok( mythtvutil.getLocalizedString( 27 ), str( ex ) )
debug( "< mythtvgui.BaseWindowDialog.onAction( action=[%s] )"%action )
def onActionHook( self, action ):
"""
Method that is called by BaseWindowDialog class. This method is intended to
be overridden by subclasses to perform custom logic.
Return values:
0 Event was not consumed by the hook. Event will be handled
internally. (default)
1 Event was consumed by hook. No further processing will be done.
BaseWindowDialog catches exceptions and displays dialog. Therefore this
type of logic does not need to replicated in this hook.
"""
return 0
def onActionPostHook( self, action ):
"""
Method that is called after internal processing is done on an action.
This is meant to be overridden if additional logic needs to be
performed after the internal processing is complete.
"""
pass
def onControl( self, control ):
debug( "> mythtvgui.BaseWindowDialog.onControl( control=[%s] )"%control )
try:
if self.lock == 0:
try:
self.lock += 1
rc = self.onControlHook( control )
self.lock -= 1
except:
self.lock -= 1
raise
except mythtv.ProtocolVersionException, ex:
Dialog().ok(
mythtvutil.getLocalizedString( 27 ),
mythtvutil.getLocalizedString( 109 )%str( ex ) )
except Exception, ex:
traceback.print_exc( ex )
Dialog().ok( mythtvutil.getLocalizedString( 27 ), str( ex ) )
debug( "< mythtvgui.BaseWindowDialog.onControl( control=[%s] )"%control )
def onControlHook( self, control ):
"""
Method that is called by BaseWindowDialog class. This method is intended to
be overridden by subclasses to perform custom logic.
Return values:
0 Event was not consumed by the hook. Event will be handled
internally.
1 Event was consumed by hook. No further processing will be done.
(default)
BaseWindowDialog catches exceptions and displays dialog. Therefore this
type of logic does not need to replicated in this hook.
"""
return 1
class DialogWin( object ):
def __init__( self ):
pass
def choose( self, event ):
return xbmcgui.Dialog().choose( event )
def ok( self, title, line1, line2 = "", line3 = "" ):
return xbmcgui.Dialog().ok( title, line1, line2, line3 )
def select( self, title, li ):
return xbmcgui.Dialog().select( title, li )
def yesno( self, title, line1, line2 = "", line3 = "" ):
return xbmcgui.Dialog().yesno( title, line1, line2, line3 )
def Dialog():
return getInstance( DialogWin )
class ChannelIconCache( mythtvutil.FileCache ):
def __init__( self ):
mythtvutil.FileCache.__init__(
self,
picBase + "channels",
numDays=0 ) # never delete these
def buildPath( self, channel ):
debug( "> mythtvgui.ChannelIconCache.buildPath()" )
file = self.getIconFile( channel )
if file:
file = self.cachePath + os.sep + file
debug( "< mythtvgui.ChannelIconCache.buildPath() => [%s]"%file )
return file
def getIconFile( self, channel ):
debug( "> mythtvgui.ChannelIconCache.getIconFile()" )
file = channel.icon()
if file:
file = string.replace( file, "/", os.sep )
file = os.path.basename( file )
debug( "< mythtvgui.ChannelIconCache.getIconFile() => [%s]"%file )
return file
def storeFile( self, chan ):
debug( "> mythtvgui.ChannelIconCache.storeFile( chan=[%s])"%(chan) )
localPath = None
file = chan.icon()
if file:
s = getInstance( mythtv.Settings )
host = s.getSetting( "mythtv_host" )
port = int( s.getSetting( "mythtv_port" ) )
remotePath = "myth://%s:%d%s"%(host,port,file)
localPath = self.buildPath( chan )
c = getInstance( mythtv.Connection )
rc = c.transferFile(
remotePath, localPath, host )
if rc != 0:
localPath = None
del c
debug( "< mythtvgui.ChannelIconCache.storeFile() => [%s]"%localPath )
return localPath
class ThumbnailCache( mythtvutil.FileCache ):
def __init__( self ):
mythtvutil.FileCache.__init__(
self, picBase + "thumbs" )
def buildThumbnailRemoteFilename( self, chanid, starttime, endtime ):
if mythtv.mythProtocolVersion >= 26:
return "%s_%s.mpg.png"%(chanid, starttime)
else:
return "%s_%s_%s.nuv.png"%(chanid, starttime, endtime)
def buildThumbnailFilename( self, chanid, starttime, endtime ):
return "%s_%s_%s.png"%(chanid, starttime, endtime)
def buildThumbnailPath( self, chanid, starttime, endtime ):
return self.cachePath + os.sep + \
self.buildThumbnailFilename( chanid, starttime, endtime )
def buildPath( self, program ):
chanid = program.chanid()
starttime = program.starttime()
endtime = program.endtime()
return self.buildThumbnailPath( chanid, starttime, endtime )
def storeFile( self, program, theHost ):
debug( "> mythtvgui.ThumbnailCache.storeFile( " + \
"program=[%s])"%(program) )
chanid = program.chanid()
starttime = program.starttime()
endtime = program.endtime()
# s = getInstance( mythtv.Settings )
# host = s.getSetting( "mythtv_host" )
# port = int( s.getSetting( "mythtv_port" ) )
# file = "/" + self.buildThumbnailRemoteFilename( \
# chanid, starttime, endtime )
# remotePath = "myth://%s:%d%s"%(host,port,file)
remotePath = "%s.png" % program.remoteMythPath()
localPath = self.buildThumbnailPath( chanid, starttime, endtime )
c = getInstance( mythtv.Connection )
rc = c.transferFile( remotePath, localPath, theHost )
if rc != 0:
rc = c.genPixmap( program, theHost )
if rc == 0:
for i in range(0,3):
time.sleep( 0.5 )
rc = c.transferFile( remotePath, localPath, theHost )
if rc == 0:
break
del c
if rc != 0:
localPath = None
debug( "< mythtvgui.ThumbnailCache.storeThumbnail() => [%s]"%localPath )
return localPath
################################################################################
# For testing purposes
################################################################################
if __name__ == '__main__':
import unittest
mythtvutil.debugSetLevel( mythtvutil.DEBUG_ALL )
mythtvutil.initialize()
class PublicInterfaceTest( unittest.TestCase ):
def testDialogOK( self ):
Dialog().ok( "ok test", "Hello World" )
def testDialogYesNo( self ):
Dialog().yesno( "yes no test", "Are you sure?" )
unittest.main()
|