2003-02-16 03:59:37 +00:00
#include "global.h"
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"
2004-04-30 07:26:06 +00:00
#include "RageUtil.h"
2004-07-25 17:07:32 +00:00
#include "Game.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"
2004-06-10 22:47:51 +00:00
#include "arch/Dialog/Dialog.h"
2003-07-22 07:47:27 +00:00
#include "RageFile.h"
2003-11-05 04:56:41 +00:00
#include "ScreenManager.h"
2004-04-30 07:26:06 +00:00
#include "StepMania.h"
2004-09-21 06:07:12 +00:00
#include "Foreach.h"
#include "ThemeMetric.h"
2004-09-22 04:55:31 +00:00
#include "LuaHelpers.h"
2005-01-09 01:31:06 +00:00
#include "ScreenDimensions.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-12-10 09:44:16 +00:00
const CString LANGUAGES_SUBDIR = "Languages/" ;
2003-08-19 08:01:15 +00:00
const CString BASE_LANGUAGE = "english" ;
2003-12-10 09:26:05 +00:00
const CString THEMES_DIR = "Themes/" ;
2003-08-19 08:01:15 +00:00
const CString METRICS_FILE = "metrics.ini" ;
2003-04-12 17:39:27 +00:00
const CString ELEMENT_CATEGORY_STRING [ NUM_ELEMENT_CATEGORIES ] =
{
"BGAnimations" ,
"Fonts" ,
"Graphics" ,
"Numbers" ,
2003-09-11 23:21:33 +00:00
"Sounds" ,
"Other"
2003-04-12 17:39:27 +00:00
};
2002-05-19 01:59:48 +00:00
2004-08-10 04:33:36 +00:00
struct Theme
{
CString sThemeName ;
2005-01-07 14:28:00 +00:00
IniFile * iniMetrics ; // pointer because the copy constructor isn't a deep copy
2004-08-10 04:33:36 +00:00
};
// When looking for a metric or an element, search these from head to tail.
deque < Theme > g_vThemes ;
2004-09-21 06:07:12 +00:00
//
// For self-registering metrics
//
static vector < IThemeMetric *> * g_pvpSubscribers = NULL ;
void ThemeManager :: Subscribe ( IThemeMetric * p )
{
// TRICKY: If we make this a global vector instead of a global pointer,
// then we'd have to be careful that the static constructors of all
// Preferences are called before the vector constructor. It's
// too tricky to enfore that, so we'll allocate the vector ourself
// so that the compiler can't possibly call the vector constructor
// after we've already added to the vector.
if ( g_pvpSubscribers == NULL )
g_pvpSubscribers = new vector < IThemeMetric *> ;
g_pvpSubscribers -> push_back ( p );
2005-01-05 04:31:36 +00:00
// It's ThemeManager's responsibility to make sure all of it's subscribers
// are updated with current data. If a metric is created after
// a theme is loaded, ThemeManager should update it right away (not just
// when the theme changes).
if ( THEME && THEME -> GetCurThemeName (). size () )
p -> Read ();
2004-09-21 06:07:12 +00:00
}
2004-11-06 20:36:04 +00:00
void ThemeManager :: Unsubscribe ( IThemeMetric * p )
{
vector < IThemeMetric *>:: iterator iter = find ( g_pvpSubscribers -> begin (), g_pvpSubscribers -> end (), p );
ASSERT ( iter != g_pvpSubscribers -> end () ); // tried to unregister when not registered
g_pvpSubscribers -> erase ( iter );
}
2004-09-21 06:07:12 +00:00
2003-10-22 07:56:08 +00:00
/* We spend a lot of time doing redundant theme path lookups. Cache results. */
static map < CString , CString > g_ThemePathCache [ NUM_ELEMENT_CATEGORIES ];
2004-01-24 21:16:07 +00:00
void FileNameToClassAndElement ( const CString & sFileName , CString & sClassNameOut , CString & sElementOut )
{
// split into class name and file name
int iIndexOfFirstSpace = sFileName . Find ( " " );
if ( iIndexOfFirstSpace == - 1 ) // no space
{
sClassNameOut = "" ;
sElementOut = sFileName ;
}
else
{
sClassNameOut = sFileName . Left ( iIndexOfFirstSpace );
sElementOut = sFileName . Right ( sFileName . length () - iIndexOfFirstSpace - 1 );
}
}
CString ClassAndElementToFileName ( const CString & sClassName , const CString & sElement )
{
if ( sClassName . empty () )
return sElement ;
else
return sClassName + " " + sElement ;
}
2002-03-19 07:09:49 +00:00
ThemeManager :: ThemeManager ()
{
2004-09-16 22:53:40 +00:00
/* We don't have any theme loaded until SwitchThemeAndLanguage is called. */
m_sCurThemeName = "" ;
2003-03-11 19:02:09 +00:00
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 ()
{
}
2003-04-13 04:50:08 +00:00
void ThemeManager :: GetThemeNames ( CStringArray & AddTo )
2002-03-19 07:09:49 +00:00
{
2003-07-22 07:47:27 +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-07-23 01:41:40 +00:00
}
2004-11-06 06:34:21 +00:00
bool ThemeManager :: DoesThemeExist ( const CString & sThemeName )
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 ;
}
2003-08-19 08:01:15 +00:00
void ThemeManager :: GetLanguages ( CStringArray & AddTo )
{
AddTo . clear ();
CStringArray asTemp ;
2004-08-13 02:13:39 +00:00
/* XXX: this should use FallbackTheme */
2003-08-19 08:01:15 +00:00
GetLanguagesForTheme ( m_sCurThemeName , AddTo );
GetLanguagesForTheme ( BASE_THEME_NAME , asTemp );
AddTo . insert ( AddTo . begin (), asTemp . begin (), asTemp . end () );
// remove dupes
sort ( AddTo . begin (), AddTo . end () );
CStringArray :: iterator it = unique ( AddTo . begin (), AddTo . end () );
AddTo . erase ( it , AddTo . end ());
}
2004-11-06 06:34:21 +00:00
bool ThemeManager :: DoesLanguageExist ( const CString & sLanguage )
2003-08-19 08:01:15 +00:00
{
CStringArray asLanguages ;
GetLanguages ( asLanguages );
for ( unsigned i = 0 ; i < asLanguages . size (); i ++ )
if ( sLanguage . CompareNoCase ( asLanguages [ i ]) == 0 )
return true ;
return false ;
}
2004-11-06 06:34:21 +00:00
void ThemeManager :: LoadThemeRecursive ( deque < Theme > & theme , const CString & sThemeName )
2004-08-10 04:33:36 +00:00
{
static int depth = 0 ;
2004-08-13 02:13:39 +00:00
static bool loaded_base = false ;
2004-08-10 04:33:36 +00:00
depth ++ ;
ASSERT_M ( depth < 20 , "Circular NoteSkin fallback references detected." );
2004-08-13 02:13:39 +00:00
if ( ! sThemeName . CompareNoCase ( BASE_THEME_NAME ) )
loaded_base = true ;
2004-08-10 04:33:36 +00:00
Theme t ;
2005-01-07 14:28:00 +00:00
t . iniMetrics = new IniFile ;
2004-08-10 04:33:36 +00:00
t . sThemeName = sThemeName ;
2005-01-07 14:28:00 +00:00
t . iniMetrics -> ReadFile ( GetMetricsIniPath ( sThemeName ) );
t . iniMetrics -> ReadFile ( GetLanguageIniPath ( sThemeName , BASE_LANGUAGE ) );
2004-08-10 04:33:36 +00:00
if ( m_sCurLanguage . CompareNoCase ( BASE_LANGUAGE ) )
2005-01-07 14:28:00 +00:00
t . iniMetrics -> ReadFile ( GetLanguageIniPath ( sThemeName , m_sCurLanguage ) );
2004-08-10 04:33:36 +00:00
2004-08-13 02:13:39 +00:00
/* Read the fallback theme. If no fallback theme is specified, and we havn't
* already loaded it, fall back on BASE_THEME_NAME. That way, default theme
* fallbacks can be disabled with "FallbackTheme=". */
2004-08-10 04:33:36 +00:00
CString sFallback ;
2005-01-07 14:28:00 +00:00
if ( ! t . iniMetrics -> GetValue ( "Global" , "FallbackTheme" , sFallback ) )
2004-08-13 02:13:39 +00:00
{
if ( sThemeName . CompareNoCase ( BASE_THEME_NAME ) && ! loaded_base )
sFallback = BASE_THEME_NAME ;
}
if ( ! sFallback . empty () )
2004-08-10 04:33:36 +00:00
LoadThemeRecursive ( theme , sFallback );
g_vThemes . push_front ( t );
2004-08-13 02:13:39 +00:00
if ( ! sThemeName . CompareNoCase ( sThemeName ) )
loaded_base = false ;
2004-08-10 04:33:36 +00:00
depth -- ;
}
2004-11-06 06:34:21 +00:00
void ThemeManager :: SwitchThemeAndLanguage ( const CString & sThemeName , const CString & sLanguage )
2002-03-19 07:09:49 +00:00
{
2004-11-06 06:34:21 +00:00
CString sTheme = sThemeName ;
CString sLang = sLanguage ;
if ( ! DoesThemeExist ( sTheme ) )
sTheme = BASE_THEME_NAME ;
if ( ! DoesLanguageExist ( sLang ) )
sLang = BASE_LANGUAGE ;
2004-09-16 22:45:55 +00:00
LOG -> Trace ( "ThemeManager::SwitchThemeAndLanguage: \" %s \" , \" %s \" " ,
2004-11-06 06:34:21 +00:00
sTheme . c_str (), sLang . c_str () );
2004-09-16 22:45:55 +00:00
2004-11-06 06:34:21 +00:00
if ( sTheme == m_sCurThemeName && sLang == m_sCurLanguage )
2004-09-16 22:45:55 +00:00
return ;
2004-11-06 06:34:21 +00:00
m_sCurThemeName = sTheme ;
m_sCurLanguage = sLang ;
2003-08-19 08:01:15 +00:00
2003-12-01 00:27:55 +00:00
// clear theme path cache
2004-09-21 07:53:39 +00:00
for ( int i = 0 ; i < NUM_ELEMENT_CATEGORIES ; ++ i )
2003-12-01 00:27:55 +00:00
g_ThemePathCache [ i ]. clear ();
2004-08-10 04:33:36 +00:00
g_vThemes . clear ();
2004-07-21 23:42:52 +00:00
2004-08-10 04:33:36 +00:00
// load current theme
2004-08-13 02:13:39 +00:00
LoadThemeRecursive ( g_vThemes , m_sCurThemeName );
2004-08-10 04:33:36 +00:00
2004-04-30 07:26:06 +00:00
CString sMetric ;
2004-09-21 07:53:39 +00:00
for ( int i = 0 ; GetCommandlineArgument ( "metric" , & sMetric , i ); ++ i )
2004-04-30 07:26:06 +00:00
{
/* sMetric must be "foo::bar=baz". "foo" and "bar" never contain "=", so in
* "foo::bar=1+1=2", "baz" is always "1+1=2". Neither foo nor bar may be
* empty, but baz may be. */
Regex re ( "^([^=]+) :: ([ ^= ] + ) = (. * ) $ " ) ;
vector < CString > sBits ;
if ( ! re . Compare ( sMetric , sBits ) )
RageException :: Throw ( "Invalid argument \" --metric=%s \" " , sMetric . c_str () );
2005-01-07 14:28:00 +00:00
g_vThemes . front (). iniMetrics -> SetValue ( sBits [ 0 ], sBits [ 1 ], sBits [ 2 ] );
2004-04-30 07:26:06 +00:00
}
2004-11-06 06:34:21 +00:00
LOG -> MapLog ( "theme" , "Theme: %s" , sTheme . c_str ());
LOG -> MapLog ( "language" , "Language: %s" , sLang . c_str ());
2004-09-16 22:45:55 +00:00
// reload common sounds
if ( SCREENMAN != NULL )
SCREENMAN -> ThemeChanged ();
2004-09-21 06:07:12 +00:00
2004-11-11 22:31:55 +00:00
/* Lua globals can use metrics which are cached, and vice versa. Update Lua
* globals first; it's Lua's job to explicitly update cached metrics that it
* uses. */
2005-01-09 01:31:06 +00:00
UpdateLuaGlobals ();
2004-11-11 22:31:55 +00:00
2004-09-21 06:07:12 +00:00
// reload subscribers
FOREACH ( IThemeMetric * , * g_pvpSubscribers , p ) ( * p ) -> Read ();
2002-03-19 07:09:49 +00:00
}
2005-01-09 01:31:06 +00:00
void ThemeManager :: UpdateLuaGlobals ()
{
2005-01-19 23:36:15 +00:00
LUA -> Init ();
2005-01-09 01:31:06 +00:00
/* Important: explicitly refresh cached metrics that we use. */
THEME_SCREEN_WIDTH . Read ();
THEME_SCREEN_HEIGHT . Read ();
2005-01-19 23:01:53 +00:00
LUA -> SetGlobal ( "SCREEN_WIDTH" , ( int ) SCREEN_WIDTH );
LUA -> SetGlobal ( "SCREEN_HEIGHT" , ( int ) SCREEN_HEIGHT );
LUA -> SetGlobal ( "SCREEN_LEFT" , ( int ) SCREEN_LEFT );
LUA -> SetGlobal ( "SCREEN_RIGHT" , ( int ) SCREEN_RIGHT );
LUA -> SetGlobal ( "SCREEN_TOP" , ( int ) SCREEN_TOP );
LUA -> SetGlobal ( "SCREEN_BOTTOM" , ( int ) SCREEN_BOTTOM );
LUA -> SetGlobal ( "SCREEN_CENTER_X" , ( int ) SCREEN_CENTER_X );
LUA -> SetGlobal ( "SCREEN_CENTER_Y" , ( int ) SCREEN_CENTER_Y );
2005-01-19 23:36:15 +00:00
/* Run all script files in Lua for all themes. Start from the deepest fallback
* theme and work outwards. */
deque < Theme >:: const_iterator iter = g_vThemes . end ();
-- iter ;
do
{
const CString & sThemeDir = GetThemeDirFromName ( iter -> sThemeName );
CStringArray asElementPaths ;
GetDirListing ( sThemeDir + "Lua/*.lua" , asElementPaths , false , true );
for ( unsigned i = 0 ; i < asElementPaths . size (); ++ i )
{
const CString & sPath = asElementPaths [ i ];
LOG -> Trace ( "Loading \" %s \" ..." , sPath . c_str () );
LUA -> RunScriptFile ( sPath );
}
}
while ( iter != g_vThemes . begin () );
2005-01-09 01:31:06 +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-12-10 09:44:16 +00:00
return THEMES_DIR + sThemeName + "/" ;
2002-02-09 04:30:27 +00:00
}
2004-11-06 06:34:21 +00:00
CString ThemeManager :: GetPathToAndFallback ( const CString & sThemeName , ElementCategory category , const CString & sClassName , const CString & sElement )
2004-02-03 04:41:43 +00:00
{
2004-11-06 06:34:21 +00:00
CString sClass = sClassName ;
2004-02-15 21:39:15 +00:00
int n = 100 ;
while ( n -- )
2004-02-03 04:41:43 +00:00
{
2004-02-15 21:39:15 +00:00
// search with requested name
2004-11-06 06:34:21 +00:00
CString sRet = GetPathToRaw ( sThemeName , category , sClass , sElement );
2004-02-03 04:41:43 +00:00
if ( ! sRet . empty () )
return sRet ;
2004-02-15 21:39:15 +00:00
// search fallback name (if any)
CString sFallback ;
2004-11-06 06:34:21 +00:00
GetMetricRaw ( sClass , "Fallback" , sFallback );
2004-03-20 03:02:12 +00:00
if ( sFallback . empty () )
2004-02-15 21:39:15 +00:00
return "" ;
2004-11-06 06:34:21 +00:00
sClass = sFallback ;
2004-02-03 04:41:43 +00:00
}
2004-02-15 21:39:15 +00:00
RageException :: Throw ( "Infinite recursion looking up theme element from theme \" %s \" , class \" %s \" " ,
2004-11-06 06:34:21 +00:00
sThemeName . c_str (), sClass . c_str () );
2004-02-03 04:41:43 +00:00
}
2004-11-06 06:34:21 +00:00
CString ThemeManager :: GetPathToRaw ( const CString & sThemeName , ElementCategory category , const CString & sClassName , const CString & sElement )
2002-08-13 23:26:46 +00:00
{
2003-02-22 21:47:42 +00:00
try_element_again :
2002-08-13 23:26:46 +00:00
2003-01-26 21:45:13 +00:00
const CString sThemeDir = GetThemeDirFromName ( sThemeName );
2004-11-06 06:34:21 +00:00
const CString & sCategory = ELEMENT_CATEGORY_STRING [ category ];
2002-08-13 23:26:46 +00:00
2003-03-15 00:16:45 +00:00
CStringArray asElementPaths ;
2002-08-13 23:26:46 +00:00
2003-04-12 17:39:27 +00:00
// If sFileName already has an extension, we're looking for a specific file
2004-01-24 21:16:07 +00:00
bool bLookingForSpecificFile = sElement . find_last_of ( '.' ) != sElement . npos ;
2003-04-12 17:39:27 +00:00
bool bDirsOnly = category == BGAnimations ;
2002-05-19 06:13:16 +00:00
2003-04-12 17:39:27 +00:00
if ( bLookingForSpecificFile )
{
2004-01-24 21:16:07 +00:00
GetDirListing ( sThemeDir + sCategory + "/" + ClassAndElementToFileName ( sClassName , sElement ), asElementPaths , bDirsOnly , true );
2003-04-12 17:39:27 +00:00
}
else // look for all files starting with sFileName that have types we can use
{
2004-04-06 00:46:05 +00:00
const CString wildcard = ( category == BGAnimations ? "" : "*" );
2003-04-12 17:39:27 +00:00
/* First, look for redirs. */
2004-04-06 00:46:05 +00:00
GetDirListing ( sThemeDir + sCategory + "/" + ClassAndElementToFileName ( sClassName , sElement ) + wildcard + ".redir" ,
2003-04-12 17:39:27 +00:00
asElementPaths , false , true );
2003-01-23 04:41:57 +00:00
2003-10-19 23:00:11 +00:00
CStringArray asPaths ;
2004-01-24 21:16:07 +00:00
GetDirListing ( sThemeDir + sCategory + "/" + ClassAndElementToFileName ( sClassName , sElement ) + wildcard ,
2003-10-19 23:00:11 +00:00
asPaths , bDirsOnly , true );
2003-01-23 04:41:57 +00:00
2003-10-19 23:00:11 +00:00
for ( unsigned p = 0 ; p < asPaths . size (); ++ p )
{
2005-01-07 22:01:57 +00:00
static const char * masks [ NUM_ELEMENT_CATEGORIES ][ 13 ] = {
2003-10-19 23:00:11 +00:00
{ "" , NULL },
{ "ini" , NULL },
2005-01-07 22:01:57 +00:00
{ "xml" , "actor" , "sprite" , "png" , "jpg" , "bmp" , "gif" , "avi" , "mpg" , "mpeg" , "txt" , "" , NULL },
2003-10-19 23:00:11 +00:00
{ "png" , NULL },
{ "mp3" , "ogg" , "wav" , NULL },
{ "sm" , NULL },
};
const char ** asset_masks = masks [ category ];
const CString ext = GetExtension ( asPaths [ p ] );
if ( ext == "redir" )
continue ; // got it already
for ( int i = 0 ; asset_masks [ i ]; ++ i )
{
2003-10-31 02:06:48 +00:00
/* No extension means directories. */
if ( asset_masks [ i ][ 0 ] == 0 && ! IsADirectory ( asPaths [ p ]) )
continue ;
2003-10-19 23:00:11 +00:00
if ( ext == asset_masks [ i ] )
{
asElementPaths . push_back ( asPaths [ p ] );
break ;
}
}
}
2003-04-12 17:39:27 +00:00
if ( category == Fonts )
2004-01-24 21:16:07 +00:00
Font :: WeedFontNames ( asElementPaths , ClassAndElementToFileName ( sClassName , sElement ));
2003-04-12 17:39:27 +00:00
}
2003-03-15 00:16:45 +00:00
2003-11-17 03:37:51 +00:00
if ( asElementPaths . size () == 0 )
{
2004-07-24 23:26:34 +00:00
// HACK: have Fonts fall back to Numbers. Eventually Numbers will be removed.
if ( category == Fonts )
return GetPathToRaw ( sThemeName , Numbers , sClassName , sElement ) ;
2003-11-17 03:37:51 +00:00
return "" ; // This isn't fatal.
}
2003-03-15 00:16:45 +00:00
if ( asElementPaths . size () > 1 )
{
2003-07-03 02:41:27 +00:00
FlushDirCache ();
2003-10-22 07:56:08 +00:00
g_ThemePathCache [ category ]. clear ();
2003-03-15 03:09:06 +00:00
2003-03-15 00:16:45 +00:00
CString message = ssprintf (
2003-07-11 03:15:28 +00:00
"ThemeManager: There is more than one theme element element that matches "
2004-01-24 21:16:07 +00:00
"'%s/%s/%s %s'. Please remove all but one of these matches." ,
sThemeName . c_str (), sCategory . c_str (), sClassName . c_str (), sElement . c_str () );
2003-07-11 03:39:02 +00:00
2004-06-10 22:47:51 +00:00
switch ( Dialog :: AbortRetryIgnore ( message ) )
2003-11-17 03:37:51 +00:00
{
2004-06-10 22:47:51 +00:00
case Dialog :: abort :
2003-11-17 03:37:51 +00:00
RageException :: Throw ( message );
break ;
2004-06-10 22:47:51 +00:00
case Dialog :: retry :
2004-02-01 03:14:37 +00:00
FlushDirCache ();
ReloadMetrics ();
2003-11-15 06:08:13 +00:00
goto try_element_again ;
2004-06-10 22:47:51 +00:00
case Dialog :: ignore :
2003-11-17 03:37:51 +00:00
break ;
2003-03-15 00:16:45 +00:00
}
2003-11-17 03:37:51 +00:00
}
CString sPath = asElementPaths [ 0 ];
bool bIsARedirect = GetExtension ( sPath ). CompareNoCase ( "redir" ) == 0 ;
if ( ! bIsARedirect )
{
return sPath ;
}
else // bIsARedirect
{
CString sNewFileName = GetRedirContents ( sPath );
2004-01-24 21:16:07 +00:00
CString sNewClassName , sNewFile ;
FileNameToClassAndElement ( sNewFileName , sNewClassName , sNewFile );
2003-11-17 03:37:51 +00:00
/* backwards-compatibility hack */
if ( category == Fonts )
sNewFileName . Replace ( " 16x16.png" , "" );
/* 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. */
/* Use GetPathToOptional because we don't want report that there's an element
* missing. Instead we want to report that the redirect is invalid. */
2004-02-14 01:16:04 +00:00
CString sNewPath = GetPath ( category , sNewClassName , sNewFile , true );
2003-11-17 03:37:51 +00:00
if ( ! sNewPath . empty () )
2005-01-13 09:53:36 +00:00
{
2003-11-17 03:37:51 +00:00
return sNewPath ;
2005-01-13 09:53:36 +00:00
}
2003-11-17 03:37:51 +00:00
else
2003-03-15 00:16:45 +00:00
{
2003-11-17 03:37:51 +00:00
CString message = ssprintf (
"ThemeManager: The redirect '%s' points to the file '%s', which does not exist. "
"Verify that this redirect is correct." ,
sPath . c_str (), sNewFileName . c_str ());
2003-03-15 00:16:45 +00:00
2004-06-10 22:47:51 +00:00
if ( Dialog :: AbortRetryIgnore ( message ) == Dialog :: retry )
2004-02-01 03:14:37 +00:00
{
FlushDirCache ();
ReloadMetrics ();
2003-11-17 03:37:51 +00:00
goto try_element_again ;
2004-02-01 03:14:37 +00:00
}
2003-03-15 00:16:45 +00:00
2003-11-17 03:37:51 +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
2004-11-06 06:34:21 +00:00
CString ThemeManager :: GetPath ( ElementCategory category , const CString & sClassName , const CString & sElement , bool bOptional )
2003-01-19 03:26:05 +00:00
{
2004-01-24 21:16:07 +00:00
CString sFileName = ClassAndElementToFileName ( sClassName , sElement );
2003-10-22 07:56:08 +00:00
map < CString , CString > & Cache = g_ThemePathCache [ category ];
{
map < CString , CString >:: const_iterator i ;
i = Cache . find ( sFileName );
if ( i != Cache . end () )
return i -> second ;
}
2003-01-19 03:26:05 +00:00
try_element_again :
2004-01-24 21:16:07 +00:00
2004-08-10 04:33:36 +00:00
for ( deque < Theme >:: const_iterator iter = g_vThemes . begin ();
iter != g_vThemes . end ();
iter ++ )
{
// search the current theme
CString ret = GetPathToAndFallback ( iter -> sThemeName , category , sClassName , sElement );
if ( ! ret . empty () ) // we found something
{
Cache [ sFileName ] = ret ;
return ret ;
}
}
2004-01-24 21:16:07 +00:00
if ( bOptional )
2003-10-22 07:56:08 +00:00
{
Cache [ sFileName ] = "" ;
2003-04-12 17:39:27 +00:00
return "" ;
2003-10-22 07:56:08 +00:00
}
2003-01-19 03:26:05 +00:00
2004-11-06 06:34:21 +00:00
const CString & sCategory = ELEMENT_CATEGORY_STRING [ category ];
2003-04-12 20:22:42 +00:00
2003-12-28 22:27:27 +00:00
/* We can't fall back on _missing in Other: the file types are unknown. */
2004-02-03 04:41:43 +00:00
CString sMessage = "The theme element \" " + sCategory + "/" + sFileName + " \" is missing." ;
2004-06-10 22:47:51 +00:00
Dialog :: Result res ;
2003-12-28 22:27:27 +00:00
if ( category != Other )
2004-06-10 22:47:51 +00:00
res = Dialog :: AbortRetryIgnore ( sMessage , "MissingThemeElement" );
2003-12-28 22:27:27 +00:00
else
2004-06-10 22:47:51 +00:00
res = Dialog :: RetryCancel ( sMessage , "MissingThemeElement" );
2003-12-28 22:27:27 +00:00
switch ( res )
2003-01-19 03:26:05 +00:00
{
2004-06-10 22:47:51 +00:00
case Dialog :: retry :
2003-07-03 08:12:06 +00:00
FlushDirCache ();
2004-02-01 03:14:37 +00:00
ReloadMetrics ();
2003-01-19 03:26:05 +00:00
goto try_element_again ;
2004-06-10 22:47:51 +00:00
case Dialog :: ignore :
2003-11-17 03:37:51 +00:00
LOG -> Warn (
2003-12-10 09:44:16 +00:00
"Theme element '%s/%s' could not be found in '%s' or '%s'." ,
2003-11-17 03:37:51 +00:00
sCategory . c_str (),
sFileName . c_str (),
GetThemeDirFromName ( m_sCurThemeName ). c_str (),
GetThemeDirFromName ( BASE_THEME_NAME ). c_str () );
/* Err? */
if ( sFileName == "_missing" )
RageException :: Throw ( "'_missing' isn't present in '%s%s'" , GetThemeDirFromName ( BASE_THEME_NAME ). c_str (), sCategory . c_str () );
2004-02-14 01:16:04 +00:00
Cache [ sFileName ] = GetPath ( category , "" , "_missing" );
2003-11-17 03:37:51 +00:00
return Cache [ sFileName ];
2003-12-28 22:27:27 +00:00
/* XXX: "abort" and "cancel" are synonyms; merge */
2004-06-10 22:47:51 +00:00
case Dialog :: abort :
case Dialog :: cancel :
2003-12-10 09:44:16 +00:00
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-11-15 06:08:13 +00:00
default :
ASSERT ( 0 );
2003-11-17 03:37:51 +00:00
return "" ;
2003-01-19 03:26:05 +00:00
}
}
2002-08-13 23:26:46 +00:00
2004-11-06 06:34:21 +00:00
CString ThemeManager :: GetMetricsIniPath ( const CString & sThemeName )
2002-08-13 23:26:46 +00:00
{
2003-08-19 08:01:15 +00:00
return GetThemeDirFromName ( sThemeName ) + METRICS_FILE ;
2002-08-13 23:26:46 +00:00
}
2004-11-06 06:34:21 +00:00
bool ThemeManager :: HasMetric ( const CString & sClassName , const CString & sValueName )
2003-03-24 21:37:13 +00:00
{
CString sThrowAway ;
2004-02-15 01:32:12 +00:00
return GetMetricRaw ( sClassName , sValueName , sThrowAway );
2003-03-24 21:37:13 +00:00
}
2003-11-07 20:43:17 +00:00
void ThemeManager :: ReloadMetrics ()
{
2004-09-16 22:45:55 +00:00
// force a reload of the metrics cache
2004-11-06 06:34:21 +00:00
const CString sThemeName = m_sCurThemeName ;
const CString sCurLanguage = m_sCurLanguage ;
2004-09-16 22:45:55 +00:00
m_sCurThemeName = "" ;
m_sCurLanguage = "" ;
SwitchThemeAndLanguage ( sThemeName , sCurLanguage );
2003-12-10 06:07:15 +00:00
if ( SCREENMAN )
SCREENMAN -> SystemMessage ( "Reloaded metrics" );
2003-11-05 04:56:41 +00:00
//
// clear theme path cache
//
for ( int i = 0 ; i < NUM_ELEMENT_CATEGORIES ; ++ i )
g_ThemePathCache [ i ]. clear ();
}
2004-11-06 06:34:21 +00:00
bool ThemeManager :: GetMetricRaw ( const CString & sClassName , const CString & sValueName , CString & ret , int level )
2004-02-15 01:32:12 +00:00
{
2004-03-20 10:24:42 +00:00
if ( level > 100 )
RageException :: Throw ( "Infinite recursion looking up theme metric \" %s::%s \" " , sClassName . c_str (), sValueName . c_str () );
2004-02-15 01:32:12 +00:00
2004-03-20 10:24:42 +00:00
CString sFallback ;
2004-03-20 03:02:12 +00:00
2004-08-10 04:33:36 +00:00
for ( deque < Theme >:: const_iterator iter = g_vThemes . begin ();
iter != g_vThemes . end ();
iter ++ )
2004-03-20 03:02:12 +00:00
{
2005-01-07 14:28:00 +00:00
if ( iter -> iniMetrics -> GetValue ( sClassName , sValueName , ret ) )
2004-07-23 04:33:05 +00:00
return true ;
2005-01-07 14:28:00 +00:00
if ( iter -> iniMetrics -> GetValue ( sClassName , "Fallback" , sFallback ) )
2004-08-10 04:33:36 +00:00
{
if ( GetMetricRaw ( sFallback , sValueName , ret , level + 1 ) )
return true ;
}
2004-07-23 04:33:05 +00:00
}
2004-03-20 03:02:12 +00:00
return false ;
2004-02-15 01:32:12 +00:00
}
2004-11-06 06:34:21 +00:00
CString ThemeManager :: GetMetricRaw ( const CString & sClassName , const CString & sValueName )
2002-08-13 23:26:46 +00:00
{
2002-08-22 09:31:32 +00:00
try_metric_again :
2003-11-05 05:17:56 +00:00
2004-02-15 01:32:12 +00:00
CString ret ;
if ( ThemeManager :: GetMetricRaw ( sClassName , sValueName , ret ) )
return ret ;
2002-08-13 23:26:46 +00:00
2004-01-24 21:16:07 +00:00
2003-11-15 06:08:13 +00:00
CString sMessage = ssprintf ( "The theme metric '%s-%s' is missing. Correct this and click Retry, or Cancel to break." , sClassName . c_str (), sValueName . c_str () );
2004-06-10 22:47:51 +00:00
switch ( Dialog :: AbortRetryIgnore ( sMessage ) )
2003-11-05 05:17:56 +00:00
{
2004-06-10 22:47:51 +00:00
case Dialog :: abort :
2003-11-15 06:08:13 +00:00
break ; // fall through
2004-06-10 22:47:51 +00:00
case Dialog :: retry :
2003-11-15 06:08:13 +00:00
FlushDirCache ();
ReloadMetrics ();
goto try_metric_again ;
2004-06-10 22:47:51 +00:00
case Dialog :: ignore :
2003-11-15 06:08:13 +00:00
return "" ;
default :
ASSERT ( 0 );
2003-11-05 05:17:56 +00:00
}
2002-08-22 09:31:32 +00:00
2004-02-15 01:32:12 +00:00
CString sCurMetricPath = GetMetricsIniPath ( m_sCurThemeName );
CString sDefaultMetricPath = GetMetricsIniPath ( BASE_THEME_NAME );
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. */
2004-11-06 06:34:21 +00:00
CString ThemeManager :: GetMetric ( const CString & sClassName , const CString & sValueName )
2003-03-18 21:41:17 +00:00
{
CString sValue = GetMetricRaw ( sClassName , sValueName );
2005-01-16 20:36:27 +00:00
EvaluateString ( sValue );
2003-03-18 21:41:17 +00:00
return sValue ;
}
2005-01-16 20:36:27 +00:00
void ThemeManager :: EvaluateString ( CString & sText )
{
/* If the string begins with an @, treat it as a raw Lua expression, and don't do any
* other filtering. (XXX: maybe we should still do font aliases) */
if ( sText . size () >= 1 && sText [ 0 ] == '@' )
{
/* Erase "@". */
sText . erase ( 0 , 1 );
CString sOut ;
2005-01-19 23:01:53 +00:00
LUA -> RunExpressionS ( sText , sOut );
2005-01-16 20:36:27 +00:00
sText = sOut ;
return ;
}
// "::" means newline since you can't use line breaks in an ini file.
// XXX: this makes it impossible to put a colon at the end of a line, eg: "Color:\nRed"
sText . Replace ( "::" , " \n " );
FontCharAliases :: ReplaceMarkers ( sText );
}
2004-11-06 06:34:21 +00:00
int ThemeManager :: GetMetricI ( const CString & sClassName , const 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
}
2004-11-06 06:34:21 +00:00
float ThemeManager :: GetMetricF ( const CString & sClassName , const CString & sValueName )
2002-08-13 23:26:46 +00:00
{
2004-11-06 06:46:43 +00:00
CString sValue = GetMetricRaw ( sClassName , sValueName );
2004-09-28 10:03:49 +00:00
2004-11-06 06:46:43 +00:00
#if defined(DEBUG)
if ( sValueName . Right ( 1 ) == "X" || sValueName . Right ( 1 ) == "Y" ) // an absolute X or Y position
{
2004-11-06 06:54:04 +00:00
if ( isdigit ( sValue [ 0 ]) && sValue . Find ( '-' ) == - 1 && sValue . Find ( '+' ) == - 1 )
2004-11-06 06:46:43 +00:00
{
LOG -> Warn ( "Absolute position metric '%s'-'%s' should contain a SCREEN_* constant" , sClassName . c_str (), sValueName . c_str () );
}
}
#endif
2004-09-28 10:03:49 +00:00
2005-01-19 23:01:53 +00:00
LUA -> PrepareExpression ( sValue );
2004-11-06 06:46:43 +00:00
2005-01-19 23:01:53 +00:00
return LUA -> RunExpressionF ( sValue );
2002-08-13 23:26:46 +00:00
}
2004-05-23 02:27:51 +00:00
// #include "LuaHelpers.h"
2004-11-06 06:34:21 +00:00
bool ThemeManager :: GetMetricB ( const CString & sClassName , const CString & sValueName )
2002-08-13 23:26:46 +00:00
{
2004-11-06 06:46:43 +00:00
CString sValue = GetMetricRaw ( sClassName , sValueName );
2004-12-06 06:41:31 +00:00
/* Watch out: "0" and "1" are not false and true in Lua (all string values are
* true). Make sure that we catch all values that are supposed to be simple
* booleans. */
TrimLeft ( sValue );
TrimRight ( sValue );
if ( sValue . Left ( 1 ) == "0" )
2004-09-22 04:55:31 +00:00
return false ; /* optimization */
2004-12-06 06:41:31 +00:00
if ( sValue . Left ( 1 ) == "1" )
2004-09-22 04:55:31 +00:00
return true ; /* optimization */
2004-11-06 06:00:58 +00:00
2005-01-19 23:01:53 +00:00
LUA -> PrepareExpression ( sValue );
2004-11-06 06:00:58 +00:00
2005-01-19 23:01:53 +00:00
return LUA -> RunExpressionB ( sValue );
2002-08-13 23:26:46 +00:00
}
2004-11-06 06:34:21 +00:00
RageColor ThemeManager :: GetMetricC ( const CString & sClassName , const CString & sValueName )
2002-08-13 23:26:46 +00:00
{
2003-11-18 04:06:39 +00:00
RageColor ret ( 1 , 1 , 1 , 1 );
if ( ! ret . FromString ( GetMetricRaw ( sClassName , sValueName ) ) )
2004-11-06 23:13:47 +00:00
LOG -> Warn ( "The color value '%s' for metric '%s : %s' is invalid." , GetMetricRaw ( sClassName , sValueName ). c_str (), sClassName . c_str (), sValueName . c_str () );
2003-11-18 04:06:39 +00:00
return ret ;
2002-08-13 23:26:46 +00:00
}
2003-04-13 04:50:08 +00:00
2004-12-03 05:19:46 +00:00
Commands ThemeManager :: GetMetricA ( const CString & sClassName , const CString & sValueName )
2004-11-06 23:13:47 +00:00
{
2004-12-03 05:19:46 +00:00
return ParseCommands ( GetMetricRaw ( sClassName , sValueName ) );
2004-11-06 23:13:47 +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 ();
2003-08-19 08:01:15 +00:00
SwitchThemeAndLanguage ( as [ iNewIndex ], m_sCurLanguage );
}
2004-11-06 06:34:21 +00:00
void ThemeManager :: GetLanguagesForTheme ( const CString & sThemeName , CStringArray & asLanguagesOut )
2003-08-19 08:01:15 +00:00
{
CString sLanguageDir = GetThemeDirFromName ( sThemeName ) + LANGUAGES_SUBDIR ;
CStringArray as ;
GetDirListing ( sLanguageDir + "*.ini" , as );
// stip out metrics.ini
for ( int i = as . size () - 1 ; i >= 0 ; i -- )
{
if ( as [ i ]. CompareNoCase ( METRICS_FILE ) == 0 )
as . erase ( as . begin () + i );
// strip ".ini"
as [ i ] = as [ i ]. Left ( as [ i ]. size () - 4 );
}
asLanguagesOut = as ;
}
2004-11-06 06:34:21 +00:00
CString ThemeManager :: GetLanguageIniPath ( const CString & sThemeName , const CString & sLanguage )
2003-08-19 08:01:15 +00:00
{
return GetThemeDirFromName ( sThemeName ) + LANGUAGES_SUBDIR + sLanguage + ".ini" ;
2003-04-14 04:35:19 +00:00
}
2004-01-24 21:16:07 +00:00
// TODO: remove these and update the places that use them
2004-11-06 06:34:21 +00:00
CString ThemeManager :: GetPathToB ( const CString & sFileName , bool bOptional ) { CString sClassName , sElement ; FileNameToClassAndElement ( sFileName , sClassName , sElement ); return GetPathB ( sClassName , sElement , bOptional ); }
CString ThemeManager :: GetPathToF ( const CString & sFileName , bool bOptional ) { CString sClassName , sElement ; FileNameToClassAndElement ( sFileName , sClassName , sElement ); return GetPathF ( sClassName , sElement , bOptional ); }
CString ThemeManager :: GetPathToG ( const CString & sFileName , bool bOptional ) { CString sClassName , sElement ; FileNameToClassAndElement ( sFileName , sClassName , sElement ); return GetPathG ( sClassName , sElement , bOptional ); }
CString ThemeManager :: GetPathToS ( const CString & sFileName , bool bOptional ) { CString sClassName , sElement ; FileNameToClassAndElement ( sFileName , sClassName , sElement ); return GetPathS ( sClassName , sElement , bOptional ); }
CString ThemeManager :: GetPathToO ( const CString & sFileName , bool bOptional ) { CString sClassName , sElement ; FileNameToClassAndElement ( sFileName , sClassName , sElement ); return GetPathO ( sClassName , sElement , bOptional ); }
2004-06-08 01:24:17 +00:00
2004-07-19 08:05:14 +00:00
void ThemeManager :: GetModifierNames ( set < CString >& AddTo )
{
2004-08-10 04:33:36 +00:00
for ( deque < Theme >:: const_iterator iter = g_vThemes . begin ();
iter != g_vThemes . end ();
2004-08-13 02:13:39 +00:00
++ iter )
2004-08-10 04:33:36 +00:00
{
2005-01-07 14:28:00 +00:00
const XNode * cur = iter -> iniMetrics -> GetChild ( "OptionNames" );
2004-08-13 02:13:39 +00:00
if ( cur )
{
2005-01-07 14:28:00 +00:00
FOREACH_CONST_Attr ( cur , p )
AddTo . insert ( p -> m_sName );
2004-08-13 02:13:39 +00:00
}
2004-08-10 04:33:36 +00:00
}
2004-07-19 08:05:14 +00:00
}
2004-12-03 05:19:46 +00:00
void ThemeManager :: GetMetric ( const CString & sClassName , const CString & sValueName , Commands & valueOut )
2004-12-02 05:56:38 +00:00
{
valueOut = GetMetricA ( sClassName , sValueName );
}
2004-06-08 01:24:17 +00:00
/*
* (c) 2001-2004 Chris Danford
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/