2002-01-16 10:01:32 +00:00
#include "stdafx.h"
/*
-----------------------------------------------------------------------------
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"
2002-01-16 10:01:32 +00:00
2002-10-31 03:54:50 +00:00
using namespace std ;
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" ;
const CString THEMES_DIR = "Themes \\ " ;
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.
2002-07-23 01:41:40 +00:00
2002-03-19 07:09:49 +00:00
CStringArray arrayThemeNames ;
2002-07-31 19:40:40 +00:00
GetAllThemeNames ( arrayThemeNames );
2002-03-19 07:09:49 +00:00
}
2002-08-13 23:26:46 +00:00
ThemeManager ::~ ThemeManager ()
{
delete m_pIniMetrics ;
}
2002-07-31 19:40:40 +00:00
void ThemeManager :: GetAllThemeNames ( CStringArray & AddTo )
2002-03-19 07:09:49 +00:00
{
2002-07-23 01:41:40 +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-01-03 05:29:45 +00:00
if ( ! i -> CompareNoCase ( "cvs" ) ) {
2002-10-31 04:11:08 +00:00
AddTo . erase ( i , i + 1 );
break ;
}
}
2002-07-23 01:41:40 +00:00
}
2002-07-31 19:40:40 +00:00
void ThemeManager :: GetThemeNamesForCurGame ( CStringArray & AddTo )
{
GetAllThemeNames ( AddTo );
2002-08-27 16:53:25 +00:00
/*
2002-07-31 19:40:40 +00:00
// strip out announcers that don't have the current game name in them
CString sGameName = GAMESTATE->GetCurrentGameDef()->m_szName;
sGameName.MakeLower();
2002-10-31 02:12:14 +00:00
for( unsigned i=AddTo.size()-1; i>=0; i-- )
2002-07-31 19:40:40 +00:00
{
CString sLowercaseVer = AddTo[i];
sLowercaseVer.MakeLower();
if( sLowercaseVer.Find(sGameName)==-1 )
AddTo.RemoveAt(i);
}
2002-08-27 16:53:25 +00:00
*/
2002-07-31 19:40:40 +00:00
}
2002-07-23 01:41:40 +00:00
bool ThemeManager :: DoesThemeExist ( CString sThemeName )
{
CStringArray asThemeNames ;
2002-07-31 19:40:40 +00:00
GetAllThemeNames ( 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-05-19 01:59:48 +00:00
void ThemeManager :: SwitchTheme ( CString sThemeName )
2002-03-19 07:09:49 +00:00
{
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 ();
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
{
2002-07-23 01:41:40 +00:00
return THEMES_DIR + sThemeName + " \\ " ;
2002-02-09 04:30:27 +00:00
}
2003-01-19 03:26:05 +00:00
CString ThemeManager :: GetPathToOptional ( CString sAssetCategory , CString sFileName )
2002-08-13 23:26:46 +00:00
{
2002-08-22 09:31:32 +00:00
try_element_again :
2002-08-13 23:26:46 +00:00
sAssetCategory . MakeLower ();
sFileName . MakeLower ();
const CString sCurrentThemeDir = GetThemeDirFromName ( m_sCurThemeName );
const CString sDefaultThemeDir = GetThemeDirFromName ( BASE_THEME_NAME );
CStringArray asPossibleElementFilePaths ;
///////////////////////////////////////
// Search both the current theme and the default theme dirs for this element
///////////////////////////////////////
2003-01-23 04:41:57 +00:00
/* First, look for redirs. */
GetDirListing ( sCurrentThemeDir + sAssetCategory + "/" + sFileName + ".redir" ,
asPossibleElementFilePaths , false , true );
2003-01-05 03:32:41 +00:00
if ( asPossibleElementFilePaths . empty ())
2003-01-23 04:41:57 +00:00
GetDirListing ( sDefaultThemeDir + sAssetCategory + "/" + sFileName + ".redir" ,
asPossibleElementFilePaths , false , true );
2003-01-05 03:32:41 +00:00
2003-01-23 04:41:57 +00:00
if ( ! asPossibleElementFilePaths . empty () && asPossibleElementFilePaths [ 0 ]. GetLength () > 5 && asPossibleElementFilePaths [ 0 ]. Right ( 5 ) == "redir" ) // this is a redirect file
2002-08-13 23:26:46 +00:00
{
2003-01-01 22:02:41 +00:00
CString sNewFilePath = DerefRedir ( asPossibleElementFilePaths [ 0 ]);
2002-09-07 09:53:03 +00:00
2003-01-05 03:32:41 +00:00
if ( sAssetCategory == "fonts" )
{
/* backwards-compatibility hack */
if ( sAssetCategory == "fonts" )
sNewFilePath . Replace ( " 16x16.png" , "" );
}
2003-01-05 05:13:45 +00:00
else if ( ! DoesFileExist ( sNewFilePath ) )
2002-10-31 03:54:50 +00:00
{
2003-01-01 22:02:41 +00:00
CString message = ssprintf (
"The redirect '%s' points to the file '%s', which does not exist."
"Verify that this redirect is correct." ,
asPossibleElementFilePaths [ 0 ]. GetString (), sNewFilePath . GetString ());
2002-10-31 03:54:50 +00:00
2002-09-09 05:22:02 +00:00
#ifdef _DEBUG
2003-01-10 02:22:07 +00:00
if ( MessageBox ( NULL , message . GetString (), "ThemeManager" , MB_RETRYCANCEL ) == IDRETRY )
2002-09-09 05:50:32 +00:00
goto try_element_again ;
2002-09-09 05:22:02 +00:00
#endif
2003-01-01 22:02:41 +00:00
RageException :: Throw ( "%s" , message . GetString () );
2002-09-09 05:22:02 +00:00
}
2003-01-05 03:32:41 +00:00
2003-01-05 05:13: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. */
2003-01-05 03:32:41 +00:00
2003-01-05 05:13:45 +00:00
/* Strip off the path. */
unsigned pos = sNewFilePath . find_last_of ( "/ \\ " );
if ( pos != sNewFilePath . npos ) sNewFilePath = sNewFilePath . substr ( pos + 1 );
sFileName = sNewFilePath ;
/* XXX check for loops */
goto try_element_again ;
2002-08-13 23:26:46 +00:00
}
2002-05-19 06:13:16 +00:00
2003-01-23 04:41:57 +00:00
static const char * graphic_masks [] = {
".sprite" , ".png" , ".jpg" , ".bmp" , ".gif" ,
".avi" , ".mpg" , ".mpeg" , NULL
};
static const char * sound_masks [] = { ".set" , ".mp3" , ".ogg" , ".wav" , NULL };
static const char * font_masks [] = { "*.ini" , "*.png" , "*.jpg" , "*.bmp" , "*.gif" , NULL };
static const char * numbers_masks [] = { ".png" , NULL };
static const char * bganimations_masks [] = { "" , NULL };
static const char * blank_mask [] = { "" , NULL };
const char ** asset_masks = NULL ;
if ( sAssetCategory == "graphics" ) asset_masks = graphic_masks ;
else if ( sAssetCategory == "sounds" ) asset_masks = sound_masks ;
else if ( sAssetCategory == "fonts" ) asset_masks = font_masks ;
else if ( sAssetCategory == "numbers" ) asset_masks = numbers_masks ;
else if ( sAssetCategory == "bganimations" ) asset_masks = bganimations_masks ;
else ASSERT ( 0 ); // Unknown theme asset category
/* If the theme asset name has an extension, don't add
* a mask. This should only happen with redirs. */
if ( sFileName . find_last_of ( '.' ) != sFileName . npos )
asset_masks = blank_mask ;
/* Graphics can have hints, so add a wildcard. */
if ( sAssetCategory == "graphics" || sAssetCategory == "numbers" )
sFileName += "*" ;
bool DirsOnly = false ;
if ( sAssetCategory == "bganimations" )
DirsOnly = true ;
int i ;
for ( i = 0 ; asset_masks [ i ]; ++ i )
{
GetDirListing ( sCurrentThemeDir + sAssetCategory + "/" + sFileName + asset_masks [ i ],
asPossibleElementFilePaths , DirsOnly , true );
}
if ( sAssetCategory == "fonts" )
Font :: WeedFontNames ( asPossibleElementFilePaths , sFileName );
if ( asPossibleElementFilePaths . empty ())
{
for ( i = 0 ; asset_masks [ i ]; ++ i )
{
GetDirListing ( sDefaultThemeDir + sAssetCategory + "/" + sFileName + asset_masks [ i ],
asPossibleElementFilePaths , DirsOnly , true );
}
if ( sAssetCategory == "fonts" )
Font :: WeedFontNames ( asPossibleElementFilePaths , sFileName );
}
if ( asPossibleElementFilePaths . empty ())
return "" ;
2002-09-07 09:53:03 +00:00
return asPossibleElementFilePaths [ 0 ];
2002-01-16 10:01:32 +00:00
}
2002-05-19 01:59:48 +00:00
2003-01-19 03:26:05 +00:00
CString ThemeManager :: GetPathTo ( CString sAssetCategory , CString sFileName )
{
#ifdef _DEBUG
try_element_again :
#endif
CString ret = GetPathToOptional ( sAssetCategory , sFileName );
/* If it's empty, we found nothing. */
if ( ! ret . empty () )
return ret ;
#ifdef _DEBUG
CString sMessage = ssprintf ( "The theme element %s/%s is missing." , sAssetCategory . GetString (), sFileName . GetString ());
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-01-23 04:41:57 +00:00
FDB . 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-01-19 03:26:05 +00:00
sAssetCategory . GetString (),
sFileName . GetString (),
GetThemeDirFromName ( m_sCurThemeName ). GetString (),
GetThemeDirFromName ( BASE_THEME_NAME ). GetString () );
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-01-19 21:08:44 +00:00
sAssetCategory . GetString (),
sFileName . GetString (),
GetThemeDirFromName ( m_sCurThemeName ). GetString (),
GetThemeDirFromName ( BASE_THEME_NAME ). GetString () );
2003-01-21 22:23:01 +00:00
return GetPathTo ( sAssetCategory , "_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" ;
}
2002-09-17 23:02:37 +00:00
CString ThemeManager :: GetMetric ( CString sClassName , CString sValueName )
2002-08-13 23:26:46 +00:00
{
2002-08-26 19:30:58 +00:00
#ifdef _DEBUG
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
{
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-09-03 22:31:06 +00:00
{
sValue . Replace ( "::" , " \n " ); // "::" means newline since you can't use line breaks in an ini file.
2003-01-18 09:18:30 +00:00
/* XXX: add a parameter to turn this off if there are some metrics where
* we don't want markers */
FontCharAliases :: ReplaceMarkers ( sValue );
2002-08-13 23:26:46 +00:00
return sValue ;
2002-09-03 22:31:06 +00:00
}
2002-08-13 23:26:46 +00:00
2002-08-22 09:31:32 +00:00
#ifdef _DEBUG
2002-11-11 08:38:50 +00:00
if ( IDRETRY == MessageBox ( NULL , ssprintf ( "The theme metric %s-%s is missing. Correct this and click Retry, or Cancel to break." , sClassName . GetString (), sValueName . GetString ()), "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'." ,
2002-10-29 07:58:44 +00:00
sClassName . GetString (),
sValueName . GetString (),
sCurMetricPath . GetString (),
sDefaultMetricPath . GetString ()
2002-08-13 23:26:46 +00:00
);
}
2002-09-17 23:02:37 +00:00
int ThemeManager :: GetMetricI ( CString sClassName , CString sValueName )
2002-08-13 23:26:46 +00:00
{
2002-09-17 23:02:37 +00:00
return atoi ( GetMetric ( 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
{
2002-09-17 23:02:37 +00:00
return ( float ) atof ( GetMetric ( 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
{
2002-09-17 23:02:37 +00:00
return atoi ( GetMetric ( 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
2002-09-17 23:02:37 +00:00
CString sValue = GetMetric ( sClassName , sValueName );
2002-08-13 23:26:46 +00:00
char szValue [ 40 ];
2002-09-17 23:02:37 +00:00
strncpy ( szValue , sValue , 39 );
2002-08-13 23:26:46 +00:00
int result = sscanf ( szValue , "%f,%f,%f,%f" , & r , & g , & b , & a );
if ( result != 4 )
{
2002-10-29 07:58:44 +00:00
LOG -> Warn ( "The color value '%s' for NoteSkin metric '%s : %s' is invalid." , szValue , sClassName . GetString (), sValueName . GetString () );
2002-08-13 23:26:46 +00:00
ASSERT ( 0 );
}
2002-10-28 05:30:45 +00:00
return RageColor ( r , g , b , a );
2002-08-13 23:26:46 +00:00
}