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-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"
2002-08-13 23:26:46 +00:00
# include "IniFile.h"
2002-10-28 19:05:05 +00:00
# include "RageTimer.h"
2003-01-18 09:18:30 +00:00
# include "FontCharAliases.h"
2005-10-28 17:25:22 +00:00
# include "arch/ArchHooks/ArchHooks.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"
2005-12-29 05:52:50 +00:00
# if !defined(SMPACKAGE)
2003-11-05 04:56:41 +00:00
# include "ScreenManager.h"
2005-12-29 05:52:50 +00:00
# endif
2004-09-21 06:07:12 +00:00
# include "Foreach.h"
# include "ThemeMetric.h"
2006-01-05 07:38:31 +00:00
# include "SubscriptionManager.h"
2005-01-24 02:26:55 +00:00
# include "LuaManager.h"
2005-01-09 01:31:06 +00:00
# include "ScreenDimensions.h"
2005-01-26 11:21:43 +00:00
# include "Command.h"
2005-12-22 03:10:04 +00:00
# include "LocalizedString.h"
2005-12-28 04:22:24 +00:00
# include "SpecialFiles.h"
2005-12-29 05:52:50 +00:00
# include "EnumHelper.h"
2002-01-16 10:01:32 +00:00
ThemeManager * THEME = NULL ; // global object accessable from anywhere in the program
2006-01-04 22:30:51 +00:00
static const char * ElementCategoryNames [ ] = {
2003-04-12 17:39:27 +00:00
" BGAnimations " ,
" Fonts " ,
" Graphics " ,
2003-09-11 23:21:33 +00:00
" Sounds " ,
" Other "
2003-04-12 17:39:27 +00:00
} ;
2005-05-27 09:22:57 +00:00
XToString ( ElementCategory , NUM_ElementCategory ) ;
2005-02-28 04:10:01 +00:00
StringToX ( ElementCategory ) ;
2004-08-10 04:33:36 +00:00
struct Theme
{
2005-12-27 17:45:07 +00:00
RString sThemeName ;
2004-08-10 04:33:36 +00:00
} ;
// When looking for a metric or an element, search these from head to tail.
2005-09-02 22:37:19 +00:00
static deque < Theme > g_vThemes ;
2005-11-03 17:24:56 +00:00
static IniFile * g_pIniMetrics ;
2004-08-10 04:33:36 +00:00
2004-09-21 06:07:12 +00:00
//
// For self-registering metrics
//
2005-02-05 11:21:13 +00:00
# include "SubscriptionManager.h"
2006-01-06 20:19:30 +00:00
static SubscriptionManager < IThemeMetric > g_Subscribers ;
2004-09-21 06:07:12 +00:00
2006-01-13 00:40:12 +00:00
class LocalizedStringImplThemeMetric : public ILocalizedStringImpl , public ThemeMetric < RString >
{
public :
static ILocalizedStringImpl * Create ( ) { return new LocalizedStringImplThemeMetric ; }
2006-01-13 06:34:12 +00:00
void Load ( const RString & sGroup , const RString & sName )
2006-01-13 00:40:12 +00:00
{
ThemeMetric < RString > : : Load ( sGroup , sName ) ;
}
virtual void Read ( )
{
if ( m_sName ! = " " & & THEME & & THEME - > IsThemeLoaded ( ) )
{
THEME - > GetString ( m_sGroup , m_sName , m_currentValue ) ;
m_bIsLoaded = true ;
}
}
const RString & GetLocalized ( ) const
{
if ( IsLoaded ( ) )
return GetValue ( ) ;
else
return m_sName ;
}
} ;
2004-09-21 06:07:12 +00:00
void ThemeManager : : Subscribe ( IThemeMetric * p )
{
2005-12-30 07:59:59 +00:00
g_Subscribers . Subscribe ( 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 )
{
2005-12-30 07:59:59 +00:00
g_Subscribers . Unsubscribe ( p ) ;
2004-11-06 20:36:04 +00:00
}
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. */
2005-12-27 17:45:07 +00:00
static map < RString , RString > g_ThemePathCache [ NUM_ElementCategory ] ;
2005-03-20 05:39:32 +00:00
void ThemeManager : : ClearThemePathCache ( )
{
2005-05-27 09:22:57 +00:00
for ( int i = 0 ; i < NUM_ElementCategory ; + + i )
2005-03-20 05:39:32 +00:00
g_ThemePathCache [ i ] . clear ( ) ;
}
2003-10-22 07:56:08 +00:00
2005-12-27 17:45:07 +00:00
static void FileNameToClassAndElement ( const RString & sFileName , RString & sClassNameOut , RString & sElementOut )
2004-01-24 21:16:07 +00:00
{
// split into class name and file name
2005-09-06 20:33:55 +00:00
unsigned iIndexOfFirstSpace = sFileName . find ( " " ) ;
if ( iIndexOfFirstSpace = = string : : npos ) // no space
2004-01-24 21:16:07 +00:00
{
sClassNameOut = " " ;
sElementOut = sFileName ;
}
else
{
sClassNameOut = sFileName . Left ( iIndexOfFirstSpace ) ;
2005-09-06 20:33:55 +00:00
sElementOut = sFileName . Right ( sFileName . size ( ) - iIndexOfFirstSpace - 1 ) ;
2004-01-24 21:16:07 +00:00
}
}
2005-12-27 17:45:07 +00:00
static RString ClassAndElementToFileName ( const RString & sClassName , const RString & sElement )
2004-01-24 21:16:07 +00:00
{
if ( sClassName . empty ( ) )
return sElement ;
else
return sClassName + " " + sElement ;
}
2002-03-19 07:09:49 +00:00
ThemeManager : : ThemeManager ( )
{
2005-02-12 22:52:24 +00:00
THEME = this ; // so that we can Register THEME on construction
2004-09-16 22:53:40 +00:00
/* We don't have any theme loaded until SwitchThemeAndLanguage is called. */
m_sCurThemeName = " " ;
2006-01-08 05:32:05 +00:00
m_bPseudoLocalize = false ;
2003-03-11 19:02:09 +00:00
2005-12-27 17:45:07 +00:00
vector < RString > 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 ( )
{
2005-11-03 17:24:56 +00:00
g_vThemes . clear ( ) ;
SAFE_DELETE ( g_pIniMetrics ) ;
2002-08-13 23:26:46 +00:00
}
2005-12-27 17:45:07 +00:00
void ThemeManager : : GetThemeNames ( vector < RString > & AddTo )
2002-03-19 07:09:49 +00:00
{
2005-12-28 04:22:24 +00:00
GetDirListing ( SpecialFiles : : THEMES_DIR + " * " , AddTo , true ) ;
2005-06-23 08:05:09 +00:00
StripCvs ( AddTo ) ;
2002-03-19 07:09:49 +00:00
}
2005-12-27 17:45:07 +00:00
bool ThemeManager : : DoesThemeExist ( const RString & sThemeName )
2002-03-19 07:09:49 +00:00
{
2005-12-27 17:45:07 +00:00
vector < RString > 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
}
2005-12-27 17:45:07 +00:00
void ThemeManager : : GetLanguages ( vector < RString > & AddTo )
2003-08-19 08:01:15 +00:00
{
AddTo . clear ( ) ;
2005-11-01 17:05:07 +00:00
for ( unsigned i = 0 ; i < g_vThemes . size ( ) ; + + i )
GetLanguagesForTheme ( g_vThemes [ i ] . sThemeName , AddTo ) ;
2003-08-19 08:01:15 +00:00
// remove dupes
sort ( AddTo . begin ( ) , AddTo . end ( ) ) ;
2005-12-27 17:45:07 +00:00
vector < RString > : : iterator it = unique ( AddTo . begin ( ) , AddTo . end ( ) ) ;
2003-08-19 08:01:15 +00:00
AddTo . erase ( it , AddTo . end ( ) ) ;
}
2005-12-27 17:45:07 +00:00
bool ThemeManager : : DoesLanguageExist ( const RString & sLanguage )
2003-08-19 08:01:15 +00:00
{
2005-12-27 17:45:07 +00:00
vector < RString > asLanguages ;
2003-08-19 08:01:15 +00:00
GetLanguages ( asLanguages ) ;
for ( unsigned i = 0 ; i < asLanguages . size ( ) ; i + + )
if ( sLanguage . CompareNoCase ( asLanguages [ i ] ) = = 0 )
return true ;
return false ;
}
2005-11-03 17:24:56 +00:00
/* Move nodes from pFrom into pTo which don't already exist in pTo. For
* efficiency, nodes will be moved, not copied, so pFrom will be modified.
* On return, the contents of pFrom will be undefined and should be deleted. */
static void MergeIniUnder ( XNode * pFrom , XNode * pTo )
{
/* Iterate over each section in pFrom. */
XNodes : : iterator it = pFrom - > m_childs . begin ( ) ;
while ( it ! = pFrom - > m_childs . end ( ) )
{
XNodes : : iterator next = it ;
+ + next ;
/* If this node doesn't exist in pTo, just move the whole node. */
XNode * pChildNode = pTo - > GetChild ( it - > first ) ;
if ( pChildNode = = NULL )
{
/* We're moving the XNode without copying it, so don't use RemoveChild--it'll
* delete it. */
pTo - > AppendChild ( it - > second ) ;
pFrom - > m_childs . erase ( it ) ;
}
else
{
/* map::insert will not overwrite existing nodes. */
XNode * pFrom = it - > second ;
2005-12-27 17:45:07 +00:00
FOREACHM ( RString , RString , pFrom - > m_attrs , it )
2005-11-03 17:24:56 +00:00
pChildNode - > m_attrs . insert ( * it ) ;
}
it = next ;
}
}
2005-12-27 17:45:07 +00:00
void ThemeManager : : LoadThemeMetrics ( deque < Theme > & theme , const RString & sThemeName_ , const RString & sLanguage_ )
2004-08-10 04:33:36 +00:00
{
2005-11-03 17:24:56 +00:00
delete g_pIniMetrics ;
g_pIniMetrics = new IniFile ;
2005-11-01 17:42:48 +00:00
g_vThemes . clear ( ) ;
2005-12-27 17:45:07 +00:00
RString sThemeName ( sThemeName_ ) ;
RString sLanguage ( sLanguage_ ) ;
2005-11-01 18:04:59 +00:00
m_sCurThemeName = sThemeName ;
m_sCurLanguage = sLanguage ;
2005-10-28 19:07:19 +00:00
bool bLoadedBase = false ;
while ( 1 )
2004-08-13 02:13:39 +00:00
{
2005-10-28 19:07:19 +00:00
ASSERT_M ( theme . size ( ) < 20 , " Circular theme fallback references detected. " ) ;
g_vThemes . push_back ( Theme ( ) ) ;
Theme & t = g_vThemes . back ( ) ;
t . sThemeName = sThemeName ;
2005-11-03 17:24:56 +00:00
IniFile ini ;
ini . ReadFile ( GetMetricsIniPath ( sThemeName ) ) ;
2005-12-29 02:34:27 +00:00
ini . ReadFile ( GetLanguageIniPath ( sThemeName , SpecialFiles : : BASE_LANGUAGE ) ) ;
if ( sLanguage . CompareNoCase ( SpecialFiles : : BASE_LANGUAGE ) )
2005-11-03 17:24:56 +00:00
ini . ReadFile ( GetLanguageIniPath ( sThemeName , sLanguage ) ) ;
2005-10-28 19:07:19 +00:00
2006-01-05 07:38:31 +00:00
bool bIsBaseTheme = ! sThemeName . CompareNoCase ( SpecialFiles : : BASE_THEME_NAME ) ;
2005-11-03 17:24:56 +00:00
ini . GetValue ( " Global " , " IsBaseTheme " , bIsBaseTheme ) ;
2005-10-28 19:07:19 +00:00
if ( bIsBaseTheme )
bLoadedBase = true ;
/* Read the fallback theme. If no fallback theme is specified, and we havn't
2006-01-05 07:38:31 +00:00
* already loaded it, fall back on SpecialFiles::BASE_THEME_NAME. That way, default theme
2005-10-28 19:07:19 +00:00
* fallbacks can be disabled with "FallbackTheme=". */
2005-12-27 17:45:07 +00:00
RString sFallback ;
2005-11-03 17:24:56 +00:00
if ( ! ini . GetValue ( " Global " , " FallbackTheme " , sFallback ) )
2005-10-28 19:07:19 +00:00
{
2006-01-05 07:38:31 +00:00
if ( sThemeName . CompareNoCase ( SpecialFiles : : BASE_THEME_NAME ) & & ! bLoadedBase )
sFallback = SpecialFiles : : BASE_THEME_NAME ;
2005-10-28 19:07:19 +00:00
}
2005-11-03 17:24:56 +00:00
/* We actually want to load themes bottom-to-top, loading fallback themes
* first, so derived themes overwrite metrics in fallback themes. But, we
* need to load the derived theme first, to find out the name of the fallback
* theme. Avoid having to load IniFile twice, merging the fallback theme
* into the derived theme that we've already loaded. */
MergeIniUnder ( & ini , g_pIniMetrics ) ;
2005-10-28 19:07:19 +00:00
if ( sFallback . empty ( ) )
2005-11-01 17:46:48 +00:00
break ;
2005-10-28 19:07:19 +00:00
sThemeName = sFallback ;
2004-08-13 02:13:39 +00:00
}
2005-11-01 17:46:48 +00:00
/* Overlay metrics from the command line. */
2005-12-27 17:45:07 +00:00
RString sMetric ;
2005-11-01 17:46:48 +00:00
for ( int i = 0 ; GetCommandlineArgument ( " metric " , & sMetric , i ) ; + + i )
{
/* 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 ( " ^([^=]+) : : ( [ ^ = ] + ) = ( . * ) $ " ) ;
2005-12-27 17:45:07 +00:00
vector < RString > sBits ;
2005-11-01 17:46:48 +00:00
if ( ! re . Compare ( sMetric , sBits ) )
RageException : : Throw ( " Invalid argument \" --metric=%s \" " , sMetric . c_str ( ) ) ;
2005-11-03 17:24:56 +00:00
g_pIniMetrics - > SetValue ( sBits [ 0 ] , sBits [ 1 ] , sBits [ 2 ] ) ;
2005-11-01 17:46:48 +00:00
}
2005-11-01 18:08:56 +00:00
2005-11-01 19:12:13 +00:00
LOG - > MapLog ( " theme " , " Theme: %s " , m_sCurThemeName . c_str ( ) ) ;
LOG - > MapLog ( " language " , " Language: %s " , m_sCurLanguage . c_str ( ) ) ;
2004-08-10 04:33:36 +00:00
}
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetDefaultLanguage ( )
2005-10-28 17:25:22 +00:00
{
2005-12-27 17:45:07 +00:00
RString sLangCode = HOOKS - > GetPreferredLanguage ( ) ;
2005-12-28 05:43:26 +00:00
return sLangCode ;
2005-10-28 17:25:22 +00:00
}
2006-01-08 05:32:05 +00:00
void ThemeManager : : SwitchThemeAndLanguage ( const RString & sThemeName_ , const RString & sLanguage_ , bool bPseudoLocalize )
2002-07-23 01:41:40 +00:00
{
2005-12-27 17:45:07 +00:00
RString sThemeName = sThemeName_ ;
RString sLanguage = sLanguage_ ;
2005-10-28 16:34:58 +00:00
if ( ! DoesThemeExist ( sThemeName ) )
2006-01-05 07:38:31 +00:00
sThemeName = SpecialFiles : : BASE_THEME_NAME ;
2005-11-01 18:13:32 +00:00
/* We havn't actually loaded the theme yet, so we can't check whether sLanguage
* exists. Just check for empty. */
if ( sLanguage . empty ( ) )
2005-10-28 17:25:22 +00:00
sLanguage = GetDefaultLanguage ( ) ;
2004-09-16 22:45:55 +00:00
LOG - > Trace ( " ThemeManager::SwitchThemeAndLanguage: \" %s \" , \" %s \" " ,
2005-10-28 16:34:58 +00:00
sThemeName . c_str ( ) , sLanguage . c_str ( ) ) ;
2004-09-16 22:45:55 +00:00
2006-01-08 05:32:05 +00:00
bool bNothingChanging = sThemeName = = m_sCurThemeName & & sLanguage = = m_sCurLanguage & & m_bPseudoLocalize = = bPseudoLocalize ;
if ( bNothingChanging )
2004-09-16 22:45:55 +00:00
return ;
2006-01-08 05:32:05 +00:00
m_bPseudoLocalize = bPseudoLocalize ;
2005-11-01 19:15:26 +00:00
// Load theme metrics. If only the language is changing, this is all
// we need to reload.
bool bThemeChanging = ( sThemeName ! = m_sCurThemeName ) ;
2005-11-01 18:04:59 +00:00
LoadThemeMetrics ( g_vThemes , sThemeName , sLanguage ) ;
2004-08-10 04:33:36 +00:00
2005-11-01 19:15:26 +00:00
if ( bThemeChanging )
{
// clear theme path cache
for ( int i = 0 ; i < NUM_ElementCategory ; + + i )
g_ThemePathCache [ i ] . clear ( ) ;
2005-12-29 05:52:50 +00:00
# if !defined(SMPACKAGE)
2005-11-01 19:15:26 +00:00
// reload common sounds
if ( SCREENMAN ! = NULL )
SCREENMAN - > ThemeChanged ( ) ;
2005-12-29 05:52:50 +00:00
# endif
2005-11-01 19:15:26 +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. */
UpdateLuaGlobals ( ) ;
}
2004-11-11 22:31:55 +00:00
2006-01-13 00:40:12 +00:00
// Use theme metrics for localization.
LocalizedString : : RegisterLocalizer ( LocalizedStringImplThemeMetric : : Create ) ;
2004-09-21 06:07:12 +00:00
// reload subscribers
2006-01-06 20:18:15 +00:00
if ( g_Subscribers . m_pSubscribers )
2006-01-05 07:38:31 +00:00
{
FOREACHS_CONST ( IThemeMetric * , * g_Subscribers . m_pSubscribers , p )
( * p ) - > Read ( ) ;
}
2002-03-19 07:09:49 +00:00
}
2005-12-27 17:45:07 +00:00
void ThemeManager : : RunLuaScripts ( const RString & sMask )
2005-03-18 22:09:10 +00:00
{
/* Run all script files with the given mask in Lua for all themes. Start
* from the deepest fallback theme and work outwards. */
deque < Theme > : : const_iterator iter = g_vThemes . end ( ) ;
do
{
- - iter ;
2005-12-27 17:45:07 +00:00
const RString & sThemeDir = GetThemeDirFromName ( iter - > sThemeName ) ;
vector < RString > asElementPaths ;
2005-03-18 22:09:10 +00:00
GetDirListing ( sThemeDir + " Scripts/ " + sMask , asElementPaths , false , true ) ;
for ( unsigned i = 0 ; i < asElementPaths . size ( ) ; + + i )
{
2005-12-27 17:45:07 +00:00
const RString & sPath = asElementPaths [ i ] ;
2005-03-18 22:09:10 +00:00
LOG - > Trace ( " Loading \" %s \" ... " , sPath . c_str ( ) ) ;
2005-06-16 06:10:50 +00:00
LuaHelpers : : RunScriptFile ( sPath ) ;
2005-03-18 22:09:10 +00:00
}
}
while ( iter ! = g_vThemes . begin ( ) ) ;
}
2005-01-09 01:31:06 +00:00
void ThemeManager : : UpdateLuaGlobals ( )
{
2005-03-18 22:09:10 +00:00
/* Ugly: we need Serialize.lua to be loaded in order to be able to ResetState,
* but everything else should be able to depend on globals being set. */
RunLuaScripts ( " Serialize.lua " ) ;
2005-01-24 03:05:37 +00:00
LUA - > ResetState ( ) ;
2005-12-29 05:52:50 +00:00
# if !defined(SMPACKAGE)
/* explicitly refresh cached metrics that we use. */
2006-01-21 01:15:09 +00:00
ScreenDimensions : : ReloadScreenDimensions ( ) ;
2005-01-19 23:36:15 +00:00
2005-03-18 22:09:10 +00:00
RunLuaScripts ( " *.lua " ) ;
2006-01-06 17:57:26 +00:00
# endif
2005-01-09 01:31:06 +00:00
}
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetThemeDirFromName ( const RString & sThemeName )
2002-03-19 07:09:49 +00:00
{
2005-12-28 04:22:24 +00:00
return SpecialFiles : : THEMES_DIR + sThemeName + " / " ;
2002-03-19 07:09:49 +00:00
}
2005-11-03 18:11:40 +00:00
struct CompareLanguageTag
{
2005-12-27 17:45:07 +00:00
RString m_sLanguageString ;
CompareLanguageTag ( const RString & sLang )
2005-11-03 18:11:40 +00:00
{
2005-12-27 17:45:07 +00:00
m_sLanguageString = RString ( " (lang " ) + sLang + " ) " ;
2005-11-03 18:11:40 +00:00
LOG - > Trace ( " try \" %s \" " , sLang . c_str ( ) ) ;
m_sLanguageString . ToLower ( ) ;
}
2005-12-27 17:45:07 +00:00
bool operator ( ) ( const RString & sFile ) const
2005-11-03 18:11:40 +00:00
{
2005-12-27 17:45:07 +00:00
RString sLower ( sFile ) ;
2005-11-03 18:11:40 +00:00
sLower . ToLower ( ) ;
size_t iPos = sLower . find ( m_sLanguageString ) ;
2005-12-27 17:45:07 +00:00
return iPos ! = RString : : npos ;
2005-11-03 18:11:40 +00:00
}
} ;
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetPathToRaw ( const RString & sThemeName_ , ElementCategory category , const RString & sClassName_ , const RString & sElement_ )
2003-01-05 07:55:31 +00:00
{
2005-01-20 19:16:47 +00:00
/* Ugly: the parameters to this function may be a reference into g_vThemes, or something
* else that might suddenly go away when we call ReloadMetrics. */
2005-12-27 17:45:07 +00:00
const RString sThemeName = sThemeName_ ;
const RString sClassName = sClassName_ ;
const RString sElement = sElement_ ;
2005-01-20 19:16:47 +00:00
2003-02-22 21:47:42 +00:00
try_element_again :
2003-01-05 07:55:31 +00:00
2005-12-27 17:45:07 +00:00
const RString sThemeDir = GetThemeDirFromName ( sThemeName ) ;
const RString & sCategory = ElementCategoryToString ( category ) ;
2003-01-05 07:55:31 +00:00
2005-12-27 17:45:07 +00:00
vector < RString > 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
2004-01-24 21:16:07 +00:00
bool bLookingForSpecificFile = sElement . find_last_of ( ' . ' ) ! = sElement . npos ;
2003-04-12 17:39:27 +00:00
if ( bLookingForSpecificFile )
{
2005-03-12 21:21:36 +00:00
GetDirListing ( sThemeDir + sCategory + " / " + ClassAndElementToFileName ( sClassName , sElement ) , asElementPaths , false , true ) ;
2003-04-12 17:39:27 +00:00
}
else // look for all files starting with sFileName that have types we can use
{
2005-12-27 17:45:07 +00:00
vector < RString > asPaths ;
2005-03-12 21:21:36 +00:00
GetDirListing ( sThemeDir + sCategory + " / " + ClassAndElementToFileName ( sClassName , sElement ) + " * " ,
asPaths , false , true ) ;
2003-10-19 23:00:11 +00:00
for ( unsigned p = 0 ; p < asPaths . size ( ) ; + + p )
{
2005-07-07 01:21:45 +00:00
static const char * masks [ NUM_ElementCategory ] [ 14 ] = {
2005-10-18 04:09:29 +00:00
{ " redir " , " xml " , " png " , " jpg " , " bmp " , " gif " , " avi " , " mpg " , " mpeg " , " txt " , " " , NULL } ,
2005-07-07 01:21:45 +00:00
{ " redir " , " ini " , NULL } ,
2005-09-02 00:09:21 +00:00
{ " redir " , " xml " , " png " , " jpg " , " bmp " , " gif " , " avi " , " mpg " , " mpeg " , " txt " , " " , NULL } ,
2005-07-07 01:21:45 +00:00
{ " redir " , " mp3 " , " ogg " , " wav " , NULL } ,
2005-03-13 08:40:11 +00:00
{ " * " , NULL } ,
2003-10-19 23:00:11 +00:00
} ;
const char * * asset_masks = masks [ category ] ;
2005-12-27 17:45:07 +00:00
const RString ext = GetExtension ( asPaths [ p ] ) ;
2003-10-19 23:00:11 +00:00
for ( int i = 0 ; asset_masks [ i ] ; + + i )
{
2003-10-31 02:06:48 +00:00
/* No extension means directories. */
2005-10-15 00:35:54 +00:00
if ( asset_masks [ i ] [ 0 ] = = 0 )
{
if ( ! IsADirectory ( asPaths [ p ] ) )
continue ;
2005-10-15 01:05:27 +00:00
# ifdef DEBUG
// Ignore empty directories so that we don't have to wait to test changes until
// CVS is updated and prunes the empties.
2005-12-27 17:45:07 +00:00
vector < RString > vs ;
2005-10-15 00:35:54 +00:00
GetDirListing ( asPaths [ p ] + " /* " , vs , false , false ) ;
StripCvs ( vs ) ;
bool bDirIsEmpty = vs . empty ( ) ;
if ( bDirIsEmpty )
continue ;
2005-10-15 01:05:27 +00:00
# endif
2005-10-15 00:35:54 +00:00
}
2003-10-31 02:06:48 +00:00
2005-03-13 08:40:11 +00:00
if ( ext = = asset_masks [ i ] | | ! strcmp ( asset_masks [ i ] , " * " ) )
2003-10-19 23:00:11 +00:00
{
asElementPaths . push_back ( asPaths [ p ] ) ;
break ;
}
}
}
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 )
2005-09-02 00:09:21 +00:00
return NULL ; // This isn't fatal.
2003-11-17 03:37:51 +00:00
2005-11-03 18:11:40 +00:00
/*
* If there's more than one result, check for language tags. For example,
*
* ScreenCompany graphic (lang English).png
* ScreenCompany graphic (lang French).png
*
* We still want to warn for ambiguous results. Use std::unique to filter
* files with the current language tag to the top.
*/
if ( asElementPaths . size ( ) > 1 )
{
2005-12-27 17:45:07 +00:00
vector < RString > : : iterator it =
2005-11-03 18:11:40 +00:00
partition ( asElementPaths . begin ( ) , asElementPaths . end ( ) , CompareLanguageTag ( m_sCurLanguage ) ) ;
int iDist = distance ( asElementPaths . begin ( ) , it ) ;
if ( iDist = = 0 )
{
/* We didn't find any for the current language. Try BASE_LANGUAGE. */
2005-12-29 02:34:27 +00:00
it = partition ( asElementPaths . begin ( ) , asElementPaths . end ( ) , CompareLanguageTag ( SpecialFiles : : BASE_LANGUAGE ) ) ;
2005-11-03 18:11:40 +00:00
iDist = distance ( asElementPaths . begin ( ) , it ) ;
}
/* If there's more than one match (or no language matches), warn about it below.
* If "ignore" is picked, then it'll default to the first entry, so it'll still
* use a preferred language match if there were any (since partition() put them
* at the top). */
if ( iDist = = 1 )
asElementPaths . erase ( it , asElementPaths . end ( ) ) ;
}
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
2005-12-27 17:45:07 +00:00
RString message = ssprintf (
2003-07-11 03:15:28 +00:00
" ThemeManager: There is more than one theme element element that matches "
2005-04-14 07:23:35 +00:00
" '%s/%s/%s'. Please remove all but one of these matches. " ,
sThemeName . c_str ( ) , sCategory . c_str ( ) , ClassAndElementToFileName ( sClassName , 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
2005-12-27 17:45:07 +00:00
RString sPath = asElementPaths [ 0 ] ;
2003-11-17 03:37:51 +00:00
bool bIsARedirect = GetExtension ( sPath ) . CompareNoCase ( " redir " ) = = 0 ;
if ( ! bIsARedirect )
return sPath ;
2005-09-02 22:37:19 +00:00
2005-12-27 17:45:07 +00:00
RString sNewFileName ;
2005-09-02 22:37:19 +00:00
GetFileContents ( sPath , sNewFileName , true ) ;
2005-12-27 17:45:07 +00:00
RString sNewClassName , sNewFile ;
2005-09-02 22:37:19 +00:00
FileNameToClassAndElement ( sNewFileName , sNewClassName , sNewFile ) ;
/* 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. */
2005-12-27 17:45:07 +00:00
RString sNewPath = GetPath ( category , sNewClassName , sNewFile , true ) ;
2005-09-02 22:37:19 +00:00
if ( ! sNewPath . empty ( ) )
{
return sNewPath ;
2003-03-15 00:16:45 +00:00
}
2005-09-02 22:37:19 +00:00
else
2003-03-15 00:16:45 +00:00
{
2005-12-27 17:45:07 +00:00
RString message = ssprintf (
2005-09-02 22:37:19 +00:00
" 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 ( ) ) ;
2004-01-24 21:16:07 +00:00
2005-09-02 22:37:19 +00:00
if ( Dialog : : AbortRetryIgnore ( message ) = = Dialog : : retry )
2005-01-13 09:53:36 +00:00
{
2005-09-02 22:37:19 +00:00
FlushDirCache ( ) ;
ReloadMetrics ( ) ;
goto try_element_again ;
2005-01-13 09:53:36 +00:00
}
2003-03-15 00:16:45 +00:00
2005-09-02 22:37:19 +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
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetPathToAndFallback ( ElementCategory category , const RString & sClassName_ , const RString & sElement )
2005-09-06 22:14:06 +00:00
{
2005-12-27 17:45:07 +00:00
RString sClassName ( sClassName_ ) ;
2005-09-06 22:14:06 +00:00
int n = 100 ;
while ( n - - )
{
2005-09-07 01:42:47 +00:00
FOREACHD_CONST ( Theme , g_vThemes , iter )
{
// search with requested name
2005-12-27 17:45:07 +00:00
RString sRet = GetPathToRaw ( iter - > sThemeName , category , sClassName , sElement ) ;
2005-09-07 01:42:47 +00:00
if ( ! sRet . empty ( ) )
return sRet ;
}
2005-09-06 22:14:06 +00:00
2005-09-07 00:35:16 +00:00
if ( sClassName . empty ( ) )
2005-12-27 17:45:07 +00:00
return RString ( ) ;
2005-09-07 00:35:16 +00:00
2005-09-06 22:14:06 +00:00
// search fallback name (if any)
2005-12-27 17:45:07 +00:00
RString sFallback ;
2005-09-07 00:34:02 +00:00
if ( ! GetMetricRawRecursive ( sClassName , " Fallback " , sFallback ) )
2005-12-27 17:45:07 +00:00
return RString ( ) ;
2005-09-07 00:07:53 +00:00
sClassName = sFallback ;
2005-09-06 22:14:06 +00:00
}
2005-09-07 01:42:47 +00:00
RageException : : Throw ( " Infinite recursion looking up theme element \" %s \" " ,
ClassAndElementToFileName ( sClassName , sElement ) . c_str ( ) ) ;
2005-09-06 22:14:06 +00:00
}
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetPath ( ElementCategory category , const RString & sClassName_ , const RString & sElement_ , bool bOptional )
2003-01-19 03:26:05 +00:00
{
2005-01-20 19:16:47 +00:00
/* Ugly: the parameters to this function may be a reference into g_vThemes, or something
* else that might suddenly go away when we call ReloadMetrics. */
2005-12-27 17:45:07 +00:00
const RString sClassName = sClassName_ ;
const RString sElement = sElement_ ;
2005-01-20 19:16:47 +00:00
2005-12-27 17:45:07 +00:00
RString sFileName = ClassAndElementToFileName ( sClassName , sElement ) ;
2004-01-24 21:16:07 +00:00
2005-12-27 17:45:07 +00:00
map < RString , RString > & Cache = g_ThemePathCache [ category ] ;
2003-10-22 07:56:08 +00:00
{
2005-12-27 17:45:07 +00:00
map < RString , RString > : : const_iterator i ;
2003-10-22 07:56:08 +00:00
i = Cache . find ( sFileName ) ;
if ( i ! = Cache . end ( ) )
return i - > second ;
}
2003-01-19 03:26:05 +00:00
try_element_again :
2004-08-10 04:33:36 +00:00
2005-09-07 01:42:47 +00:00
// search the current theme
2005-12-27 17:45:07 +00:00
RString ret = GetPathToAndFallback ( category , sClassName , sElement ) ;
2005-09-07 01:42:47 +00:00
if ( ! ret . empty ( ) ) // we found something
2003-10-22 07:56:08 +00:00
{
2005-09-07 01:42:47 +00:00
Cache [ sFileName ] = ret ;
return ret ;
2003-10-22 07:56:08 +00:00
}
2003-07-22 07:47:27 +00:00
2004-01-24 21:16:07 +00:00
if ( bOptional )
2003-10-22 07:56:08 +00:00
{
Cache [ sFileName ] = " " ;
2005-09-02 00:09:21 +00:00
return NULL ;
2003-10-22 07:56:08 +00:00
}
2003-01-19 03:26:05 +00:00
2005-12-27 17:45:07 +00:00
const RString & sCategory = ElementCategoryToString ( 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. */
2005-12-27 17:45:07 +00:00
RString sMessage = " The theme element \" " + sCategory + " / " + sFileName + " \" is missing. " ;
2004-06-10 22:47:51 +00:00
Dialog : : Result res ;
2005-05-27 09:22:57 +00:00
if ( category ! = EC_OTHER )
2004-06-10 22:47:51 +00:00
res = Dialog : : AbortRetryIgnore ( sMessage , " MissingThemeElement " ) ;
2003-12-28 22:27:27 +00:00
else
2005-01-20 19:40:24 +00:00
res = Dialog : : AbortRetry ( 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 ( ) ,
2006-01-05 07:38:31 +00:00
GetThemeDirFromName ( SpecialFiles : : BASE_THEME_NAME ) . c_str ( ) ) ;
2003-11-17 03:37:51 +00:00
/* Err? */
if ( sFileName = = " _missing " )
2006-01-05 07:38:31 +00:00
RageException : : Throw ( " '_missing' isn't present in '%s%s' " , GetThemeDirFromName ( SpecialFiles : : BASE_THEME_NAME ) . c_str ( ) , sCategory . c_str ( ) ) ;
2003-11-17 03:37:51 +00:00
2004-02-14 01:16:04 +00:00
Cache [ sFileName ] = GetPath ( category , " " , " _missing " ) ;
2003-11-17 03:37:51 +00:00
return Cache [ sFileName ] ;
2004-06-10 22:47:51 +00:00
case Dialog : : abort :
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 ( ) ,
2006-01-05 07:38:31 +00:00
GetThemeDirFromName ( SpecialFiles : : BASE_THEME_NAME ) . c_str ( ) ) ;
2003-11-15 06:08:13 +00:00
default :
ASSERT ( 0 ) ;
2005-09-02 00:09:21 +00:00
return NULL ;
2003-01-19 03:26:05 +00:00
}
}
2002-08-13 23:26:46 +00:00
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetMetricsIniPath ( const RString & sThemeName )
2002-08-13 23:26:46 +00:00
{
2005-12-29 02:34:27 +00:00
return GetThemeDirFromName ( sThemeName ) + SpecialFiles : : METRICS_FILE ;
2002-08-13 23:26:46 +00:00
}
2005-12-27 17:45:07 +00:00
bool ThemeManager : : HasMetric ( const RString & sClassName , const RString & sValueName )
2003-03-24 21:37:13 +00:00
{
2005-12-27 17:45:07 +00:00
RString sThrowAway ;
2005-09-04 05:26:44 +00:00
return GetMetricRawRecursive ( sClassName , sValueName , sThrowAway ) ;
2003-03-24 21:37:13 +00:00
}
2005-12-20 08:35:47 +00:00
static LocalizedString RELOADED_METRICS ( " ThemeManager " , " Reloaded metrics " ) ;
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
2005-11-03 19:53:27 +00:00
LoadThemeMetrics ( g_vThemes , m_sCurThemeName , m_sCurLanguage ) ;
2004-09-16 22:45:55 +00:00
2005-12-29 05:52:50 +00:00
# if !defined(SMPACKAGE)
2003-12-10 06:07:15 +00:00
if ( SCREENMAN )
2005-12-20 08:35:47 +00:00
SCREENMAN - > SystemMessage ( RELOADED_METRICS ) ;
2005-12-29 05:52:50 +00:00
# endif
2003-11-05 04:56:41 +00:00
//
// clear theme path cache
//
2005-05-27 09:22:57 +00:00
for ( int i = 0 ; i < NUM_ElementCategory ; + + i )
2003-11-05 04:56:41 +00:00
g_ThemePathCache [ i ] . clear ( ) ;
}
2005-12-27 17:45:07 +00:00
bool ThemeManager : : GetMetricRawRecursive ( const RString & sClassName_ , const RString & sValueName , RString & sOut )
2004-02-15 01:32:12 +00:00
{
2005-10-28 16:33:28 +00:00
ASSERT ( sValueName ! = " " ) ;
2005-12-27 17:45:07 +00:00
RString sClassName ( sClassName_ ) ;
2004-03-20 03:02:12 +00:00
2006-01-05 07:38:31 +00:00
ASSERT ( g_pIniMetrics ) ;
2005-09-07 00:52:31 +00:00
int n = 100 ;
while ( n - - )
2004-02-15 21:39:15 +00:00
{
2005-11-03 17:24:56 +00:00
if ( g_pIniMetrics - > GetValue ( sClassName , sValueName , sOut ) )
return true ;
2005-09-07 00:52:31 +00:00
if ( ! sValueName . compare ( " Fallback " ) )
return false ;
2005-09-06 22:57:37 +00:00
2005-12-27 17:45:07 +00:00
RString sFallback ;
2005-09-07 00:52:31 +00:00
if ( ! GetMetricRawRecursive ( sClassName , " Fallback " , sFallback ) )
return false ;
sClassName = sFallback ;
2004-07-23 04:33:05 +00:00
}
2005-09-07 00:52:31 +00:00
RageException : : Throw ( " Infinite recursion looking up theme metric \" %s::%s \" " , sClassName . c_str ( ) , sValueName . c_str ( ) ) ;
2004-02-15 01:32:12 +00:00
}
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetMetricRaw ( const RString & sClassName_ , const RString & sValueName_ )
2002-08-13 23:26:46 +00:00
{
2005-01-20 19:16:47 +00:00
/* Ugly: the parameters to this function may be a reference into g_vThemes, or something
* else that might suddenly go away when we call ReloadMetrics. */
2005-12-27 17:45:07 +00:00
const RString sClassName = sClassName_ ;
const RString sValueName = sValueName_ ;
2005-01-20 19:16:47 +00:00
2002-08-22 09:31:32 +00:00
try_metric_again :
2003-11-05 05:17:56 +00:00
2005-12-27 17:45:07 +00:00
RString ret ;
2005-09-04 05:26:44 +00:00
if ( ThemeManager : : GetMetricRawRecursive ( sClassName , sValueName , ret ) )
2004-02-15 01:32:12 +00:00
return ret ;
2002-08-13 23:26:46 +00:00
2004-01-24 21:16:07 +00:00
2006-01-08 18:40:20 +00:00
RString 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 :
2005-09-02 00:09:21 +00:00
return NULL ;
2003-11-15 06:08:13 +00:00
default :
ASSERT ( 0 ) ;
2003-11-05 05:17:56 +00:00
}
2002-08-22 09:31:32 +00:00
2005-12-27 17:45:07 +00:00
RString sCurMetricPath = GetMetricsIniPath ( m_sCurThemeName ) ;
2006-01-05 07:38:31 +00:00
RString sDefaultMetricPath = GetMetricsIniPath ( SpecialFiles : : BASE_THEME_NAME ) ;
2004-02-15 01:32:12 +00:00
2005-12-27 17:45:07 +00:00
RString sError = ssprintf ( " 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
) ;
2005-02-16 23:00:09 +00:00
RageException : : Throw ( sError ) ;
2002-08-13 23:26:46 +00:00
}
2003-03-18 21:41:17 +00:00
/* Get a string metric. */
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetMetric ( const RString & sClassName , const RString & sValueName )
2003-03-18 21:41:17 +00:00
{
2005-12-27 17:45:07 +00:00
RString sValue = GetMetricRaw ( sClassName , sValueName ) ;
2003-03-18 21:41:17 +00:00
2005-01-16 20:36:27 +00:00
EvaluateString ( sValue ) ;
2003-03-18 21:41:17 +00:00
return sValue ;
}
2005-12-27 17:45:07 +00:00
void ThemeManager : : EvaluateString ( RString & sText )
2005-01-16 20:36:27 +00:00
{
2005-07-25 07:39:29 +00:00
/* If the string begins with an @, then this is a Lua expression
* that should be evaluated immediately.
2005-04-06 23:01:56 +00:00
* Still do font aliases on the resulting string. */
2005-06-16 22:17:45 +00:00
LuaHelpers : : RunAtExpressionS ( sText ) ;
2005-01-16 20:36:27 +00:00
FontCharAliases : : ReplaceMarkers ( sText ) ;
}
2005-12-27 17:45:07 +00:00
int ThemeManager : : GetMetricI ( const RString & sClassName , const RString & sValueName )
2002-08-13 23:26:46 +00:00
{
2005-12-27 17:45:07 +00:00
RString sValue = GetMetric ( sClassName , sValueName ) ; // Use non-raw so that Lua expressions are allowed
2005-06-25 04:31:28 +00:00
LuaHelpers : : PrepareExpression ( sValue ) ;
return LuaHelpers : : RunExpressionI ( sValue ) ;
2002-08-13 23:26:46 +00:00
}
2005-12-27 17:45:07 +00:00
float ThemeManager : : GetMetricF ( const RString & sClassName , const RString & sValueName )
2002-08-13 23:26:46 +00:00
{
2005-12-27 17:45:07 +00:00
RString sValue = GetMetric ( sClassName , sValueName ) ; // Use non-raw so that Lua expressions are allowed
2004-09-28 10:03:49 +00:00
2005-06-16 22:22:37 +00:00
LuaHelpers : : PrepareExpression ( sValue ) ;
2004-09-28 10:03:49 +00:00
2005-06-16 06:30:33 +00:00
return LuaHelpers : : RunExpressionF ( sValue ) ;
2002-08-13 23:26:46 +00:00
}
2005-01-24 02:26:55 +00:00
// #include "LuaManager.h"
2005-12-27 17:45:07 +00:00
bool ThemeManager : : GetMetricB ( const RString & sClassName , const RString & sValueName )
2002-08-13 23:26:46 +00:00
{
2005-12-27 17:45:07 +00:00
RString sValue = GetMetric ( sClassName , sValueName ) ; // Use non-raw so that Lua expressions are allowed
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-06-16 22:22:37 +00:00
LuaHelpers : : PrepareExpression ( sValue ) ;
2004-11-06 06:00:58 +00:00
2005-06-16 06:23:59 +00:00
return LuaHelpers : : RunExpressionB ( sValue ) ;
2002-08-13 23:26:46 +00:00
}
2005-12-27 17:45:07 +00:00
RageColor ThemeManager : : GetMetricC ( const RString & sClassName , const RString & sValueName )
2002-08-13 23:26:46 +00:00
{
2005-12-27 17:45:07 +00:00
RString sValue = GetMetric ( sClassName , sValueName ) ; // Use non-raw so that Lua expressions are allowed
2005-02-12 22:52:24 +00:00
2003-11-18 04:06:39 +00:00
RageColor ret ( 1 , 1 , 1 , 1 ) ;
2005-02-12 22:52:24 +00:00
if ( ! ret . FromString ( sValue ) )
2005-02-15 01:53:30 +00:00
LOG - > Warn ( " The color value '%s' for metric '%s : %s' is invalid. " , sValue . 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
2005-12-27 17:45:07 +00:00
Commands ThemeManager : : GetMetricM ( const RString & sClassName , const RString & sValueName )
2004-11-06 23:13:47 +00:00
{
2005-12-27 17:45:07 +00:00
RString sValue = GetMetric ( sClassName , sValueName ) ; // Use non-raw so that Lua expressions are allowed
2005-02-09 09:44:16 +00:00
return ParseCommands ( sValue ) ;
2004-11-06 23:13:47 +00:00
}
2005-12-29 05:52:50 +00:00
# if !defined(SMPACKAGE)
2005-12-27 17:45:07 +00:00
apActorCommands ThemeManager : : GetMetricA ( const RString & sClassName , const RString & sValueName )
2005-01-26 11:21:43 +00:00
{
2005-12-27 17:45:07 +00:00
RString sValue = GetMetricRaw ( sClassName , sValueName ) ;
2005-02-23 19:15:24 +00:00
return apActorCommands ( new ActorCommands ( sValue ) ) ;
2005-01-26 11:21:43 +00:00
}
2005-12-29 05:52:50 +00:00
# endif
2005-01-26 11:21:43 +00:00
2003-04-13 04:50:08 +00:00
void ThemeManager : : NextTheme ( )
{
2005-12-27 17:45:07 +00:00
vector < RString > as ;
2003-04-13 04:50:08 +00:00
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 ( ) ;
2006-01-08 05:32:05 +00:00
SwitchThemeAndLanguage ( as [ iNewIndex ] , m_sCurLanguage , m_bPseudoLocalize ) ;
2003-08-19 08:01:15 +00:00
}
2005-12-27 17:45:07 +00:00
void ThemeManager : : GetLanguagesForTheme ( const RString & sThemeName , vector < RString > & asLanguagesOut )
2003-08-19 08:01:15 +00:00
{
2005-12-28 04:22:24 +00:00
RString sLanguageDir = GetThemeDirFromName ( sThemeName ) + SpecialFiles : : LANGUAGES_SUBDIR ;
2005-12-27 17:45:07 +00:00
vector < RString > as ;
2003-08-19 08:01:15 +00:00
GetDirListing ( sLanguageDir + " *.ini " , as ) ;
2006-01-14 04:21:54 +00:00
// strip out metrics.ini
2003-08-19 08:01:15 +00:00
for ( int i = as . size ( ) - 1 ; i > = 0 ; i - - )
{
2005-12-29 02:34:27 +00:00
if ( as [ i ] . CompareNoCase ( SpecialFiles : : METRICS_FILE ) = = 0 )
2003-08-19 08:01:15 +00:00
as . erase ( as . begin ( ) + i ) ;
// strip ".ini"
as [ i ] = as [ i ] . Left ( as [ i ] . size ( ) - 4 ) ;
}
2005-11-01 17:05:07 +00:00
asLanguagesOut . insert ( asLanguagesOut . end ( ) , as . begin ( ) , as . end ( ) ) ;
2003-08-19 08:01:15 +00:00
}
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetLanguageIniPath ( const RString & sThemeName , const RString & sLanguage )
2003-08-19 08:01:15 +00:00
{
2005-12-28 04:22:24 +00:00
return GetThemeDirFromName ( sThemeName ) + SpecialFiles : : LANGUAGES_SUBDIR + sLanguage + " .ini " ;
2003-04-14 04:35:19 +00:00
}
2004-01-24 21:16:07 +00:00
2005-12-27 17:45:07 +00:00
void ThemeManager : : GetModifierNames ( vector < RString > & AddTo )
2004-07-19 08:05:14 +00:00
{
2005-11-03 17:24:56 +00:00
const XNode * cur = g_pIniMetrics - > GetChild ( " OptionNames " ) ;
if ( cur )
2004-08-10 04:33:36 +00:00
{
2005-11-03 17:24:56 +00:00
FOREACH_CONST_Attr ( cur , p )
AddTo . push_back ( p - > first ) ;
2004-08-10 04:33:36 +00:00
}
2004-07-19 08:05:14 +00:00
}
2005-12-29 05:52:50 +00:00
# if !defined(SMPACKAGE)
2005-12-27 17:45:07 +00:00
void ThemeManager : : GetMetric ( const RString & sClassName , const RString & sValueName , apActorCommands & valueOut )
2004-12-02 05:56:38 +00:00
{
valueOut = GetMetricA ( sClassName , sValueName ) ;
}
2005-12-29 05:52:50 +00:00
# endif
2004-12-02 05:56:38 +00:00
2005-12-27 17:45:07 +00:00
void ThemeManager : : GetMetric ( const RString & sClassName , const RString & sValueName , LuaExpression & valueOut )
2005-02-17 06:09:08 +00:00
{
2005-12-27 17:45:07 +00:00
RString sValue = GetMetricRaw ( sClassName , sValueName ) ;
2005-02-17 08:41:13 +00:00
valueOut . SetFromExpression ( " function(self) " + sValue + " end " ) ;
2005-02-17 06:09:08 +00:00
}
2005-02-12 22:52:24 +00:00
2006-01-18 04:09:14 +00:00
static RString PseudoLocalize ( RString s )
{
s . Replace ( " a " , " àá " ) ;
s . Replace ( " A " , " ÀÀ " ) ;
s . Replace ( " e " , " éé " ) ;
s . Replace ( " E " , " ÉÉ " ) ;
s . Replace ( " i " , " íí " ) ;
s . Replace ( " I " , " ÍÍ " ) ;
s . Replace ( " o " , " óó " ) ;
s . Replace ( " O " , " ÓÓ " ) ;
s . Replace ( " u " , " üü " ) ;
s . Replace ( " U " , " ÜÜ " ) ;
s . Replace ( " n " , " ñ " ) ;
s . Replace ( " N " , " Ñ " ) ;
s . Replace ( " c " , " ç " ) ;
s . Replace ( " C " , " Ç " ) ;
// transformations that helpexpose punctuation assumptions
//s.Replace( ":", " :" ); // this messes up "::" help text tip separator markers
s . Replace ( " ? " , " ? " ) ;
s . Replace ( " ! " , " ! " ) ;
return s ;
}
2006-01-06 17:57:26 +00:00
RString ThemeManager : : GetString ( const RString & sClassName , const RString & sValueName_ )
2005-12-25 19:01:13 +00:00
{
2006-01-06 17:57:26 +00:00
RString sValueName = sValueName_ ;
2006-01-14 00:40:55 +00:00
// TODO: Are there escape rules for this?
2006-01-05 07:38:31 +00:00
DEBUG_ASSERT ( sValueName . find ( ' = ' ) = = sValueName . npos ) ;
2006-01-06 17:57:26 +00:00
// TODO: Move this escaping into IniFile?
sValueName . Replace ( " \n " , " \\ n " ) ;
2006-01-05 07:38:31 +00:00
2006-01-06 17:57:26 +00:00
// Write stubs for missing strings into every language file.
2006-01-17 20:47:43 +00:00
RString s = ThemeManager : : GetMetric ( sClassName , sValueName ) ;
/*
2006-01-14 04:31:56 +00:00
if( !ThemeManager::GetMetricRawRecursive( sClassName, sValueName, s ) )
2006-01-05 07:38:31 +00:00
{
2006-01-08 20:11:34 +00:00
RString sFile = GetLanguageIniPath( SpecialFiles::BASE_THEME_NAME, m_sCurLanguage );
2006-01-05 07:38:31 +00:00
IniFile ini;
ini.ReadFile( sFile );
s = sValueName;
ini.SetValue( sClassName, sValueName, s );
ini.WriteFile( sFile );
2006-01-08 20:11:34 +00:00
LoadThemeMetrics( g_vThemes, m_sCurThemeName, m_sCurLanguage );
2006-01-05 07:38:31 +00:00
}
2006-01-17 20:47:43 +00:00
*/
2006-01-14 04:31:56 +00:00
EvaluateString ( s ) ;
2006-01-06 17:57:26 +00:00
s . Replace ( " \\ n " , " \n " ) ;
2006-01-08 05:32:05 +00:00
if ( m_bPseudoLocalize )
2006-01-06 20:18:15 +00:00
{
2006-01-18 04:09:14 +00:00
// pseudolocalize ignoring replace markers. e.g.: "%{steps} steps: %{author}"
RString sTranslated ;
for ( ; true ; )
{
RString : : size_type pos = s . find ( " %{ " ) ;
if ( pos = = s . npos )
{
sTranslated + = PseudoLocalize ( s ) ;
s = RString ( ) ;
break ;
}
else
{
sTranslated + = PseudoLocalize ( s . substr ( 0 , pos ) ) ;
s . erase ( s . begin ( ) , s . begin ( ) + pos ) ;
}
pos = s . find ( " } " ) ;
sTranslated + = s . substr ( 0 , pos + 1 ) ;
s . erase ( s . begin ( ) , s . begin ( ) + pos + 1 ) ;
}
s = sTranslated ;
2006-01-06 20:18:15 +00:00
}
2006-01-05 07:38:31 +00:00
return s ;
2005-12-25 19:01:13 +00:00
}
2005-12-27 17:45:07 +00:00
void ThemeManager : : GetMetricsThatBeginWith ( const RString & sClassName_ , const RString & sValueName , set < RString > & vsValueNamesOut )
2005-03-13 20:44:26 +00:00
{
2005-12-27 17:45:07 +00:00
RString sClassName ( sClassName_ ) ;
2005-03-13 20:44:26 +00:00
while ( ! sClassName . empty ( ) )
{
2005-11-03 17:24:56 +00:00
const XNode * cur = g_pIniMetrics - > GetChild ( sClassName ) ;
if ( cur ! = NULL )
2005-03-13 20:44:26 +00:00
{
// Iterate over all metrics that match.
2005-09-04 05:26:44 +00:00
for ( XAttrs : : const_iterator j = cur - > m_attrs . lower_bound ( sValueName ) ; j ! = cur - > m_attrs . end ( ) ; + + j )
2005-03-13 20:44:26 +00:00
{
2005-12-27 17:45:07 +00:00
const RString & sv = j - > first ;
2005-03-13 20:44:26 +00:00
if ( sv . Left ( sValueName . size ( ) ) = = sValueName )
vsValueNamesOut . insert ( sv ) ;
2005-03-14 00:38:51 +00:00
else // we passed the last metric that matched sValueName
2005-03-13 20:44:26 +00:00
break ;
}
}
// put the fallback (if any) in sClassName
2005-12-27 17:45:07 +00:00
RString sFallback ;
2005-09-04 05:26:44 +00:00
if ( GetMetricRawRecursive ( sClassName , " Fallback " , sFallback ) )
2005-03-13 20:44:26 +00:00
sClassName = sFallback ;
else
sClassName = " " ;
}
}
2005-12-27 17:45:07 +00:00
RString ThemeManager : : GetBlankGraphicPath ( )
2005-05-27 09:22:57 +00:00
{
2006-01-05 07:38:31 +00:00
return SpecialFiles : : THEMES_DIR + SpecialFiles : : BASE_THEME_NAME + " / " + ElementCategoryToString ( EC_GRAPHICS ) + " /_blank.png " ;
2005-05-27 09:22:57 +00:00
}
2005-02-12 22:52:24 +00:00
// lua start
# include "LuaBinding.h"
2005-06-20 05:02:03 +00:00
class LunaThemeManager : public Luna < ThemeManager >
2005-02-12 22:52:24 +00:00
{
public :
LunaThemeManager ( ) { LUA - > Register ( Register ) ; }
2005-05-22 15:03:03 +00:00
static int GetMetric ( T * p , lua_State * L ) { lua_pushstring ( L , p - > GetMetric ( SArg ( 1 ) , SArg ( 2 ) ) ) ; return 1 ; }
2005-12-25 19:27:31 +00:00
static int GetString ( T * p , lua_State * L ) { lua_pushstring ( L , p - > GetString ( SArg ( 1 ) , SArg ( 2 ) ) ) ; return 1 ; }
2005-07-26 19:39:58 +00:00
static int GetPathG ( T * p , lua_State * L ) { lua_pushstring ( L , p - > GetPathG ( SArg ( 1 ) , SArg ( 2 ) ) ) ; return 1 ; }
static int GetPathB ( T * p , lua_State * L ) { lua_pushstring ( L , p - > GetPathB ( SArg ( 1 ) , SArg ( 2 ) ) ) ; return 1 ; }
2005-07-30 21:12:54 +00:00
static int GetPathS ( T * p , lua_State * L ) { lua_pushstring ( L , p - > GetPathS ( SArg ( 1 ) , SArg ( 2 ) ) ) ; return 1 ; }
2005-02-12 22:52:24 +00:00
static void Register ( lua_State * L )
{
2005-09-10 02:47:04 +00:00
ADD_METHOD ( GetMetric ) ;
2005-12-25 19:01:13 +00:00
ADD_METHOD ( GetString ) ;
2005-09-10 02:47:04 +00:00
ADD_METHOD ( GetPathG ) ;
ADD_METHOD ( GetPathB ) ;
ADD_METHOD ( GetPathS ) ;
2005-05-22 15:03:03 +00:00
2005-02-12 22:52:24 +00:00
Luna < T > : : Register ( L ) ;
2005-03-18 22:44:47 +00:00
// Add global singleton if constructed already. If it's not constructed yet,
// then we'll register it later when we reinit Lua just before
// initializing the display.
if ( THEME )
{
lua_pushstring ( L , " THEME " ) ;
2005-06-15 02:21:24 +00:00
THEME - > PushSelf ( L ) ;
2005-03-18 22:44:47 +00:00
lua_settable ( L , LUA_GLOBALSINDEX ) ;
}
2005-02-12 22:52:24 +00:00
}
} ;
LUA_REGISTER_CLASS ( ThemeManager )
// lua end
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.
*/