2003-02-16 03:59:37 +00:00
# include "global.h"
2002-01-16 10:01:32 +00:00
/*
-----------------------------------------------------------------------------
2002-05-19 01:59:48 +00:00
Class: ThemeManager
2002-01-16 10:01:32 +00:00
2002-02-28 19:40:40 +00:00
Desc: See header.
2002-01-16 10:01:32 +00:00
2002-05-19 01:59:48 +00:00
Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved.
2002-02-28 19:40:40 +00:00
Chris Danford
2002-01-16 10:01:32 +00:00
-----------------------------------------------------------------------------
*/
# include "ThemeManager.h"
2002-05-01 19:14:55 +00:00
# include "RageLog.h"
2002-07-23 01:41:40 +00:00
# include "PrefsManager.h"
2002-07-27 19:29:51 +00:00
# include "RageException.h"
2002-08-31 10:33:13 +00:00
# include "RageTimer.h"
2002-07-31 19:40:40 +00:00
# include "GameState.h"
# include "GameDef.h"
2002-08-13 23:26:46 +00:00
# include "IniFile.h"
2002-10-28 19:05:05 +00:00
# include "RageTimer.h"
2003-01-10 02:02:51 +00:00
# include "Font.h"
2003-01-18 09:18:30 +00:00
# include "FontCharAliases.h"
2003-03-02 01:43:33 +00:00
# include "RageDisplay.h"
2002-01-16 10:01:32 +00:00
ThemeManager * THEME = NULL ; // global object accessable from anywhere in the program
2002-07-23 01:41:40 +00:00
const CString BASE_THEME_NAME = " default " ;
2003-02-14 08:15:15 +00:00
const CString THEMES_DIR = " Themes/ " ;
2003-04-12 17:39:27 +00:00
const CString ELEMENT_CATEGORY_STRING [ NUM_ELEMENT_CATEGORIES ] =
{
" BGAnimations " ,
" Fonts " ,
" Graphics " ,
" Numbers " ,
" Sounds "
} ;
2002-05-19 01:59:48 +00:00
2002-03-19 07:09:49 +00:00
ThemeManager : : ThemeManager ( )
{
2002-08-13 23:26:46 +00:00
m_pIniMetrics = new IniFile ;
2002-08-31 10:33:13 +00:00
/* Update the metric cache on the first call to GetMetric. */
2002-10-28 19:05:05 +00:00
m_fNextReloadTicks = 0 ;
2002-08-13 23:26:46 +00:00
2002-10-28 19:05:05 +00:00
m_sCurThemeName = BASE_THEME_NAME ; // Use the base theme for now. It's up to PrefsManager to change this.
2003-03-11 19:02:09 +00:00
m_uHashForCurThemeMetrics = m_uHashForBaseThemeMetrics = 0 ;
2002-03-19 07:09:49 +00:00
CStringArray arrayThemeNames ;
2003-04-13 04:50:08 +00:00
GetThemeNames ( arrayThemeNames ) ;
2002-03-19 07:09:49 +00:00
}
2002-08-13 23:26:46 +00:00
ThemeManager : : ~ ThemeManager ( )
{
delete m_pIniMetrics ;
}
2003-04-13 04:50:08 +00:00
void ThemeManager : : GetThemeNames ( CStringArray & AddTo )
2002-03-19 07:09:49 +00:00
{
2003-02-14 08:15:15 +00:00
GetDirListing ( THEMES_DIR + " /* " , AddTo , true ) ;
2002-03-19 07:09:49 +00:00
// strip out the folder called "CVS"
2002-10-31 04:11:08 +00:00
for ( CStringArray : : iterator i = AddTo . begin ( ) ; i ! = AddTo . end ( ) ; + + i )
{
2003-02-06 07:32:57 +00:00
if ( * i = = " CVS " ) {
2002-10-31 04:11:08 +00:00
AddTo . erase ( i , i + 1 ) ;
break ;
}
}
2002-03-19 07:09:49 +00:00
}
2002-07-23 01:41:40 +00:00
bool ThemeManager : : DoesThemeExist ( CString sThemeName )
2002-03-19 07:09:49 +00:00
{
2002-07-23 01:41:40 +00:00
CStringArray asThemeNames ;
2003-04-13 04:50:08 +00:00
GetThemeNames ( asThemeNames ) ;
2002-10-31 02:12:14 +00:00
for ( unsigned i = 0 ; i < asThemeNames . size ( ) ; i + + )
2002-07-27 19:29:51 +00:00
{
2003-01-03 05:29:45 +00:00
if ( ! sThemeName . CompareNoCase ( asThemeNames [ i ] ) )
2002-07-23 01:41:40 +00:00
return true ;
2002-07-27 19:29:51 +00:00
}
2002-07-23 01:41:40 +00:00
return false ;
2002-03-19 07:09:49 +00:00
}
2002-07-23 01:41:40 +00:00
void ThemeManager : : SwitchTheme ( CString sThemeName )
{
2002-09-29 17:38:07 +00:00
if ( ! DoesThemeExist ( sThemeName ) )
2002-07-23 01:41:40 +00:00
m_sCurThemeName = BASE_THEME_NAME ;
else
m_sCurThemeName = sThemeName ;
2002-08-13 23:26:46 +00:00
// update hashes for metrics files
2002-09-07 09:53:03 +00:00
m_uHashForCurThemeMetrics = GetHashForFile ( GetMetricsPathFromName ( m_sCurThemeName ) ) ;
m_uHashForBaseThemeMetrics = GetHashForFile ( GetMetricsPathFromName ( BASE_THEME_NAME ) ) ;
2002-08-13 23:26:46 +00:00
// read new metrics. First read base metrics, then read cur theme's metrics, overriding base theme
m_pIniMetrics - > Reset ( ) ;
m_pIniMetrics - > SetPath ( GetMetricsPathFromName ( BASE_THEME_NAME ) ) ;
m_pIniMetrics - > ReadFile ( ) ;
m_pIniMetrics - > SetPath ( GetMetricsPathFromName ( m_sCurThemeName ) ) ;
m_pIniMetrics - > ReadFile ( ) ;
2003-04-22 04:11:04 +00:00
2003-04-22 05:21:45 +00:00
LOG - > MapLog ( " theme " , " Theme: %s " , sThemeName . c_str ( ) ) ;
2002-03-19 07:09:49 +00:00
}
2003-01-23 04:41:57 +00:00
CString ThemeManager : : GetThemeDirFromName ( const CString & sThemeName )
2002-03-19 07:09:49 +00:00
{
2003-02-14 08:15:15 +00:00
return THEMES_DIR + sThemeName + " / " ;
2002-03-19 07:09:49 +00:00
}
2003-04-12 17:39:27 +00:00
CString ThemeManager : : GetPathTo ( CString sThemeName , ElementCategory category , CString sFileName )
2003-01-05 07:55:31 +00:00
{
2003-03-02 01:43:33 +00:00
# if defined(WIN32) // XXX arch?
2003-02-22 21:47:42 +00:00
try_element_again :
# endif
2003-01-05 07:55:31 +00:00
sFileName . MakeLower ( ) ;
2003-01-26 21:45:13 +00:00
const CString sThemeDir = GetThemeDirFromName ( sThemeName ) ;
2003-04-12 17:39:27 +00:00
const CString sCategory = ELEMENT_CATEGORY_STRING [ category ] ;
2003-01-05 07:55:31 +00:00
2003-03-15 00:16:45 +00:00
CStringArray asElementPaths ;
2003-01-05 07:55:31 +00:00
2003-04-12 17:39:27 +00:00
// If sFileName already has an extension, we're looking for a specific file
bool bLookingForSpecificFile = sFileName . find_last_of ( ' . ' ) ! = sFileName . npos ;
bool bDirsOnly = category = = BGAnimations ;
if ( bLookingForSpecificFile )
{
GetDirListing ( sThemeDir + sCategory + " / " + sFileName , asElementPaths , bDirsOnly , true ) ;
}
else // look for all files starting with sFileName that have types we can use
{
/* First, look for redirs. */
GetDirListing ( sThemeDir + sCategory + " / " + sFileName + " .redir " ,
asElementPaths , false , true ) ;
static const char * masks [ NUM_ELEMENT_CATEGORIES ] [ 12 ] = {
{ " " , NULL } ,
{ " *.ini " , NULL } ,
2003-05-11 07:23:47 +00:00
{ " *.model " , " *.sprite " , " *.png " , " *.jpg " , " *.bmp " , " *.gif " , " *.avi " , " *.mpg " , " *.mpeg " , NULL } ,
2003-04-12 17:39:27 +00:00
{ " *.png " , NULL } ,
2003-07-05 22:02:42 +00:00
{ " .set " , " *.mp3 " , " *.ogg " , " *.wav " , NULL } ,
2003-04-12 17:39:27 +00:00
} ;
const char * * asset_masks = masks [ category ] ;
for ( int i = 0 ; asset_masks [ i ] ; + + i )
GetDirListing ( sThemeDir + sCategory + " / " + sFileName + asset_masks [ i ] ,
asElementPaths , bDirsOnly , true ) ;
if ( category = = Fonts )
Font : : WeedFontNames ( asElementPaths , sFileName ) ;
}
2003-03-15 00:16:45 +00:00
if ( asElementPaths . size ( ) > 1 )
{
2003-07-03 02:41:27 +00:00
FlushDirCache ( ) ;
2003-03-15 03:09:06 +00:00
2003-03-15 00:16:45 +00:00
CString message = ssprintf (
" There is more than one theme element element that matches "
" '%s/%s/%s'. Please remove all but one of these matches. " ,
2003-04-25 00:01:35 +00:00
sThemeName . c_str ( ) , sCategory . c_str ( ) , sFileName . c_str ( ) ) ;
2003-03-15 00:16:45 +00:00
# if defined(WIN32) // XXX arch?
if ( DISPLAY - > IsWindowed ( ) )
if ( MessageBox ( NULL , message , " ThemeManager " , MB_RETRYCANCEL ) = = IDRETRY )
goto try_element_again ;
# endif
RageException : : Throw ( message ) ;
}
else if ( asElementPaths . size ( ) = = 0 )
{
return " " ; // This isn't fatal.
}
else // asElementPaths.size() == 1
{
ASSERT ( asElementPaths . size ( ) = = 1 ) ;
CString sPath = asElementPaths [ 0 ] ;
bool bIsARedirect = sPath . length ( ) > 6 & & sPath . Right ( 6 ) . CompareNoCase ( " .redir " ) = = 0 ;
if ( ! bIsARedirect )
{
return sPath ;
}
else // bIsARedirect
{
CString sNewFileName = GetRedirContents ( sPath ) ;
/* backwards-compatibility hack */
2003-04-12 17:39:27 +00:00
if ( category = = Fonts )
sNewFileName . Replace ( " 16x16.png " , " " ) ;
2003-03-15 00:16:45 +00:00
/* Search again. For example, themes/default/Fonts/foo might redir
* to "Hello"; but "Hello" might be overridden in themes/hot pink/Fonts/Hello. */
/* Important: We need to do a full search. For example, BG redirs in
* the default theme point to "_shared background", and themes override
* just "_shared background"; the redirs in the default theme should end
* up resolving to the overridden background. */
2003-03-16 00:05:23 +00:00
/* Use GetPathToOptional because we don't want report that there's an element
* missing. Instead we want to report that the redirect is invalid. */
2003-04-12 17:39:27 +00:00
CString sNewPath = GetPathTo ( category , sNewFileName , true ) ;
2003-03-15 00:16:45 +00:00
2003-03-16 20:55:45 +00:00
if ( ! sNewPath . empty ( ) )
return sNewPath ;
2003-03-15 00:16:45 +00:00
else
{
CString message = ssprintf (
" The redirect '%s' points to the file '%s', which does not exist. "
" Verify that this redirect is correct. " ,
2003-04-25 00:01:35 +00:00
sPath . c_str ( ) , sNewFileName . c_str ( ) ) ;
2003-03-15 00:16:45 +00:00
# if defined(WIN32) // XXX arch?
if ( DISPLAY - > IsWindowed ( ) )
if ( MessageBox ( NULL , message , " ThemeManager " , MB_RETRYCANCEL ) = = IDRETRY )
goto try_element_again ;
# endif
2003-04-25 00:01:35 +00:00
RageException : : Throw ( " %s " , message . c_str ( ) ) ;
2003-03-15 00:16:45 +00:00
}
}
}
2003-01-26 21:45:13 +00:00
}
2003-01-23 04:41:57 +00:00
2003-04-12 17:39:27 +00:00
CString ThemeManager : : GetPathTo ( ElementCategory category , CString sFileName , bool bOptional )
2003-01-19 03:26:05 +00:00
{
2003-02-17 19:32:05 +00:00
# if defined(DEBUG) && defined(WIN32)
2003-01-19 03:26:05 +00:00
try_element_again :
# endif
2003-04-12 17:39:27 +00:00
CString ret = GetPathTo ( m_sCurThemeName , category , sFileName ) ;
if ( ! ret . empty ( ) ) // we found something
return ret ;
ret = GetPathTo ( BASE_THEME_NAME , category , sFileName ) ;
2003-01-26 21:45:13 +00:00
if ( ! ret . empty ( ) ) // we found something
2003-01-19 03:26:05 +00:00
return ret ;
2003-04-12 17:39:27 +00:00
else if ( bOptional )
return " " ;
2003-01-19 03:26:05 +00:00
2003-04-12 17:39:27 +00:00
CString sCategory = ELEMENT_CATEGORY_STRING [ category ] ;
2003-04-12 20:22:42 +00:00
# if defined(DEBUG) && defined(WIN32)
2003-04-25 00:01:35 +00:00
CString sMessage = ssprintf ( " The theme element '%s/%s' is missing. " , sCategory . c_str ( ) , sFileName . c_str ( ) ) ;
2003-01-21 22:23:01 +00:00
switch ( MessageBox ( NULL , sMessage , " ThemeManager " , MB_RETRYCANCEL ) )
2003-01-19 03:26:05 +00:00
{
case IDRETRY :
2003-07-03 08:12:06 +00:00
FlushDirCache ( ) ;
2003-01-19 03:26:05 +00:00
goto try_element_again ;
2003-01-21 22:23:01 +00:00
case IDCANCEL :
RageException : : Throw ( " Theme element '%s/%s' could not be found in '%s' or '%s'. " ,
2003-04-25 00:01:35 +00:00
sCategory . c_str ( ) ,
sFileName . c_str ( ) ,
GetThemeDirFromName ( m_sCurThemeName ) . c_str ( ) ,
GetThemeDirFromName ( BASE_THEME_NAME ) . c_str ( ) ) ;
2003-01-21 22:23:01 +00:00
break ;
2003-01-19 03:26:05 +00:00
}
# endif
2003-01-19 21:08:44 +00:00
2003-01-21 22:23:01 +00:00
LOG - > Warn (
" Theme element '%s/%s' could not be found in '%s' or '%s'. " ,
2003-04-25 00:01:35 +00:00
sCategory . c_str ( ) ,
sFileName . c_str ( ) ,
GetThemeDirFromName ( m_sCurThemeName ) . c_str ( ) ,
GetThemeDirFromName ( BASE_THEME_NAME ) . c_str ( ) ) ;
2003-01-24 00:32:33 +00:00
/* Err? */
if ( sFileName = = " _missing " )
2003-04-25 00:01:35 +00:00
RageException : : Throw ( " _missing element missing from %s/%s " , GetThemeDirFromName ( BASE_THEME_NAME ) . c_str ( ) , sCategory . c_str ( ) ) ;
2003-04-12 17:39:27 +00:00
return GetPathTo ( category , " _missing " ) ;
2003-01-19 03:26:05 +00:00
}
2002-08-13 23:26:46 +00:00
CString ThemeManager : : GetMetricsPathFromName ( CString sThemeName )
{
return GetThemeDirFromName ( sThemeName ) + " metrics.ini " ;
}
2003-03-24 21:37:13 +00:00
bool ThemeManager : : HasMetric ( CString sClassName , CString sValueName )
{
CString sThrowAway ;
return m_pIniMetrics - > GetValue ( sClassName , sValueName , sThrowAway ) ;
}
2003-03-18 21:41:17 +00:00
CString ThemeManager : : GetMetricRaw ( CString sClassName , CString sValueName )
2002-08-13 23:26:46 +00:00
{
2003-02-17 19:32:05 +00:00
# if defined(DEBUG) && defined(WIN32)
2002-08-22 09:31:32 +00:00
try_metric_again :
2002-08-26 19:30:58 +00:00
# endif
2002-08-13 23:26:46 +00:00
CString sCurMetricPath = GetMetricsPathFromName ( m_sCurThemeName ) ;
CString sDefaultMetricPath = GetMetricsPathFromName ( BASE_THEME_NAME ) ;
2002-08-31 10:33:13 +00:00
// Is our metric cache out of date?
2002-10-28 19:05:05 +00:00
// XXX: GTSS wraps every ~40 days. Need a better way to handler timers like this.
2002-12-19 23:07:20 +00:00
if ( RageTimer : : GetTimeSinceStart ( ) > = m_fNextReloadTicks )
2002-08-13 23:26:46 +00:00
{
2002-12-19 23:07:20 +00:00
m_fNextReloadTicks = RageTimer : : GetTimeSinceStart ( ) + 1.0f ;
2002-09-07 09:53:03 +00:00
if ( m_uHashForCurThemeMetrics ! = GetHashForFile ( sCurMetricPath ) | |
m_uHashForBaseThemeMetrics ! = GetHashForFile ( sDefaultMetricPath ) )
2002-08-31 10:33:13 +00:00
{
2003-03-11 19:02:09 +00:00
if ( m_uHashForBaseThemeMetrics )
{
LOG - > Warn ( " Metrics file is out of date. Reloading... " ) ;
// MessageBeep( MB_OK );
}
2002-08-31 10:33:13 +00:00
SwitchTheme ( m_sCurThemeName ) ; // force a reload of the metrics cache
}
2002-08-13 23:26:46 +00:00
}
CString sValue ;
2002-09-17 23:02:37 +00:00
if ( m_pIniMetrics - > GetValue ( sClassName , sValueName , sValue ) )
2002-08-13 23:26:46 +00:00
return sValue ;
2003-02-16 03:59:37 +00:00
# if defined(DEBUG) && defined(WIN32)
2003-04-25 00:01:35 +00:00
if ( IDRETRY = = MessageBox ( NULL , ssprintf ( " The theme metric '%s-%s' is missing. Correct this and click Retry, or Cancel to break. " , sClassName . c_str ( ) , sValueName . c_str ( ) ) , " ThemeManager " , MB_RETRYCANCEL ) )
2002-08-22 09:31:32 +00:00
goto try_metric_again ;
# endif
2002-12-21 19:34:02 +00:00
RageException : : Throw ( " Theme metric '%s : %s' could not be found in '%s' or '%s'. " ,
2003-04-25 00:01:35 +00:00
sClassName . c_str ( ) ,
sValueName . c_str ( ) ,
sCurMetricPath . c_str ( ) ,
sDefaultMetricPath . c_str ( )
2002-08-13 23:26:46 +00:00
) ;
}
2003-03-18 21:41:17 +00:00
/* Get a string metric. */
CString ThemeManager : : GetMetric ( CString sClassName , CString sValueName )
{
CString sValue = GetMetricRaw ( sClassName , sValueName ) ;
// "::" means newline since you can't use line breaks in an ini file.
sValue . Replace ( " :: " , " \n " ) ;
/* XXX: add a parameter to turn this off if there are some metrics where
* we don't want markers */
FontCharAliases : : ReplaceMarkers ( sValue ) ;
return sValue ;
}
2002-09-17 23:02:37 +00:00
int ThemeManager : : GetMetricI ( CString sClassName , CString sValueName )
2002-08-13 23:26:46 +00:00
{
2003-03-18 21:41:17 +00:00
return atoi ( GetMetricRaw ( sClassName , sValueName ) ) ;
2002-08-13 23:26:46 +00:00
}
2002-09-17 23:02:37 +00:00
float ThemeManager : : GetMetricF ( CString sClassName , CString sValueName )
2002-08-13 23:26:46 +00:00
{
2003-03-18 21:41:17 +00:00
return ( float ) atof ( GetMetricRaw ( sClassName , sValueName ) ) ;
2002-08-13 23:26:46 +00:00
}
2002-09-17 23:02:37 +00:00
bool ThemeManager : : GetMetricB ( CString sClassName , CString sValueName )
2002-08-13 23:26:46 +00:00
{
2003-03-18 21:41:17 +00:00
return atoi ( GetMetricRaw ( sClassName , sValueName ) ) ! = 0 ;
2002-08-13 23:26:46 +00:00
}
2002-10-28 05:30:45 +00:00
RageColor ThemeManager : : GetMetricC ( CString sClassName , CString sValueName )
2002-08-13 23:26:46 +00:00
{
float r = 1 , b = 1 , g = 1 , a = 1 ; // initialize in case sscanf fails
2003-03-18 21:41:17 +00:00
CString sValue = GetMetricRaw ( sClassName , sValueName ) ;
int result = sscanf ( GetMetricRaw ( sClassName , sValueName ) , " %f,%f,%f,%f " , & r , & g , & b , & a ) ;
2002-08-13 23:26:46 +00:00
if ( result ! = 4 )
{
2003-04-25 00:01:35 +00:00
LOG - > Warn ( " The color value '%s' for NoteSkin metric '%s : %s' is invalid. " , GetMetricRaw ( sClassName , sValueName ) . c_str ( ) , sClassName . c_str ( ) , sValueName . c_str ( ) ) ;
2002-08-13 23:26:46 +00:00
}
2002-10-28 05:30:45 +00:00
return RageColor ( r , g , b , a ) ;
2002-08-13 23:26:46 +00:00
}
2003-04-13 04:50:08 +00:00
void ThemeManager : : NextTheme ( )
{
CStringArray as ;
GetThemeNames ( as ) ;
2003-04-14 04:35:19 +00:00
unsigned i ;
for ( i = 0 ; i < as . size ( ) ; i + + )
2003-04-13 04:50:08 +00:00
if ( as [ i ] . CompareNoCase ( m_sCurThemeName ) = = 0 )
break ;
int iNewIndex = ( i + 1 ) % as . size ( ) ;
SwitchTheme ( as [ iNewIndex ] ) ;
2003-04-14 04:35:19 +00:00
}