aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-08-15 18:40:22 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-08-15 18:40:22 +0000
commit0a2abc145a59bfb2b0e0402a86115fa2da53c681 (patch)
tree8bdbe6a44697096bca21803d7c5ead94c8c12438 /Game/Code
parent0c5f07f9e3b9bcc7bf2ae9c81c7d6525514fef56 (diff)
downloadusdx-0a2abc145a59bfb2b0e0402a86115fa2da53c681.tar.gz
usdx-0a2abc145a59bfb2b0e0402a86115fa2da53c681.tar.xz
usdx-0a2abc145a59bfb2b0e0402a86115fa2da53c681.zip
- Support for multiple song/cover paths.
- Add multiple Song-Paths to the Ini file: [Directories] SongDir1=C:\... SongDir2=D:\... SongDirA=D:\... - UIni.pas clean-up git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1266 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to 'Game/Code')
-rw-r--r--Game/Code/Classes/UCatCovers.pas40
-rw-r--r--Game/Code/Classes/UIni.pas587
-rw-r--r--Game/Code/Classes/UMain.pas161
-rw-r--r--Game/Code/Classes/USongs.pas8
-rw-r--r--Game/Code/Menu/UDisplay.pas4
-rw-r--r--Game/Code/Screens/UScreenOptionsAdvanced.pas2
6 files changed, 470 insertions, 332 deletions
diff --git a/Game/Code/Classes/UCatCovers.pas b/Game/Code/Classes/UCatCovers.pas
index 8e7a2f62..d8f6cdb0 100644
--- a/Game/Code/Classes/UCatCovers.pas
+++ b/Game/Code/Classes/UCatCovers.pas
@@ -13,11 +13,12 @@ uses UIni;
type
TCatCovers = class
protected
- cNames: array [low(ISorting)..high(ISorting)] of array of string;
- cFiles: array [low(ISorting)..high(ISorting)] of array of string;
+ cNames: array [0..high(ISorting)] of array of string;
+ cFiles: array [0..high(ISorting)] of array of string;
public
constructor Create;
procedure Load; //Load Cover aus Cover.ini and Cover Folder
+ procedure LoadPath(const CoversPath: string);
procedure Add(Sorting: integer; Name, Filename: string); //Add a Cover
function CoverExists(Sorting: integer; Name: string): boolean; //Returns True when a cover with the given Name exists
function GetCover(Sorting: integer; Name: string): string; //Returns the Filename of a Cover
@@ -42,9 +43,19 @@ begin
Load;
end;
- //Load Cover aus Cover.ini and Cover Folder
procedure TCatCovers.Load;
var
+ I: integer;
+begin
+ for I := 0 to CoverPaths.Count-1 do
+ LoadPath(CoverPaths[I]);
+end;
+
+(**
+ * Load Cover from Cover.ini and Cover Folder
+ *)
+procedure TCatCovers.LoadPath(const CoversPath: string);
+var
Ini: TMemIniFile;
SR: TSearchRec;
List: TStringlist;
@@ -59,7 +70,7 @@ begin
List := TStringlist.Create;
//Add every Cover in Covers Ini for Every Sorting option
- for I := low(ISorting) to high(ISorting) do
+ for I := 0 to High(ISorting) do
begin
Ini.ReadSection(ISorting[I], List);
@@ -80,7 +91,7 @@ begin
Filename := CoversPath + Name;
Delete (Name, length(Name) - 3, 4);
- for I := low(ISorting) to high(ISorting) do
+ for I := 0 to high(ISorting) do
begin
Temp := Name;
if ((I = sTitle) or (I = sTitle2)) and (Pos ('Title', Temp) <> 0) then
@@ -118,7 +129,7 @@ begin
Result := False;
Name := Uppercase(Name); //Case Insensitiv
- for I := low(cNames[Sorting]) to high(cNames[Sorting]) do
+ for I := 0 to high(cNames[Sorting]) do
begin
if (cNames[Sorting][I] = Name) then //Found Name
begin
@@ -131,12 +142,12 @@ end;
//Returns the Filename of a Cover
function TCatCovers.GetCover(Sorting: integer; Name: string): string;
var
-I: Integer;
+ I: Integer;
begin
Result := '';
Name := Uppercase(Name);
- for I := low(cNames[Sorting]) to high(cNames[Sorting]) do
+ for I := 0 to high(cNames[Sorting]) do
begin
if cNames[Sorting][I] = Name then
begin
@@ -146,8 +157,17 @@ begin
end;
//No Cover
- if (Result = '') AND (FileExists(CoversPath + 'NoCover.jpg')) then
- Result := CoversPath + 'NoCover.jpg';
+ if (Result = '') then
+ begin
+ for I := 0 to CoverPaths.Count-1 do
+ begin
+ if (FileExists(CoverPaths[I] + 'NoCover.jpg')) then
+ begin
+ Result := CoverPaths[I] + 'NoCover.jpg';
+ Break;
+ end;
+ end;
+ end;
end;
end.
diff --git a/Game/Code/Classes/UIni.pas b/Game/Code/Classes/UIni.pas
index 64fa2da3..c7160ac0 100644
--- a/Game/Code/Classes/UIni.pas
+++ b/Game/Code/Classes/UIni.pas
@@ -40,10 +40,19 @@ type
type
TIni = class
private
- function ExtractKeyIndex(const key, prefix, suffix: string): integer;
- function GetMaxKeyIndex(keys: TStringList; const prefix, suffix: string): integer;
+ function RemoveFileExt(FullName: string): string;
+ function ExtractKeyIndex(const Key, Prefix, Suffix: string): integer;
+ function GetMaxKeyIndex(Keys: TStringList; const Prefix, Suffix: string): integer;
+ function GetArrayIndex(const SearchArray: array of string; Value: string; CaseInsensitiv: Boolean = False): integer;
+ function ReadArrayIndex(const SearchArray: array of string; IniFile: TCustomIniFile;
+ IniSection: string; IniProperty: string; Default: integer): integer;
+
procedure LoadInputDeviceCfg(IniFile: TMemIniFile);
procedure SaveInputDeviceCfg(IniFile: TIniFile);
+ procedure LoadThemes(IniFile: TCustomIniFile);
+ procedure LoadPaths(IniFile: TCustomIniFile);
+ procedure LoadScreenModes(IniFile: TCustomIniFile);
+
public
Name: array[0..11] of string;
@@ -106,7 +115,7 @@ type
LoadAnimation: integer;
EffectSing: integer;
ScreenFade: integer;
- AskbeforeDel: integer;
+ AskBeforeDel: integer;
OnSongClick: integer;
LineBonus: integer;
PartyPopup: integer;
@@ -114,12 +123,6 @@ type
// Controller
Joypad: integer;
- // Soundcards
- SoundCard: array[0..7, 1..2] of integer;
-
- // Devices
- LPT: integer;
-
procedure Load();
procedure Save();
procedure SaveNames;
@@ -209,7 +212,6 @@ const
IPartyPopup: array[0..1] of string = ('Off', 'On');
IJoypad: array[0..1] of string = ('Off', 'On');
- ILPT: array[0..2] of string = ('Off', 'LCD', 'Lights');
// Recording options
IChannelPlayer: array[0..6] of string = ('Off', '1', '2', '3', '4', '5', '6');
@@ -227,64 +229,120 @@ uses
URecord,
UCommandLine;
-function TIni.ExtractKeyIndex(const key, prefix, suffix: string): integer;
+(**
+ * Returns the filename without its fileextension
+ *)
+function TIni.RemoveFileExt(FullName: string): string;
+begin
+ Result := ChangeFileExt(FullName, '');
+end;
+
+(**
+ * Extracts an index of a key that is surrounded by a Prefix/Suffix pair.
+ * Example: ExtractKeyIndex('MyKey[1]', '[', ']') will return 1.
+ *)
+function TIni.ExtractKeyIndex(const Key, Prefix, Suffix: string): integer;
var
- value: string;
- start: integer;
+ Value: string;
+ Start: integer;
begin
Result := -1;
-
- if Pos(prefix, key) > -1 then
+
+ if Pos(Prefix, Key) > -1 then
begin
- start := Pos(prefix, key) + Length(prefix);
-
+ Start := Pos(Prefix, Key) + Length(Prefix);
+
// copy all between prefix and suffix
- value := copy (key, start, Pos(suffix, key) - 1 - start);
- Result := StrToIntDef(value, -1);
+ Value := Copy(Key, Start, Pos(Suffix, Key)-1 - Start);
+ Result := StrToIntDef(Value, -1);
end;
end;
-function TIni.GetMaxKeyIndex(keys: TStringList; const prefix, suffix: string): integer;
+(**
+ * Finds the maximum key-index in a key-list.
+ * The indexes of the list are surrounded by Prefix/Suffix,
+ * e.g. MyKey[1] (Prefix='[', Suffix=']')
+ *)
+function TIni.GetMaxKeyIndex(Keys: TStringList; const Prefix, Suffix: string): integer;
var
i: integer;
- keyIndex: integer;
+ KeyIndex: integer;
begin
Result := -1;
-
- for i := 0 to keys.Count-1 do
+
+ for i := 0 to Keys.Count-1 do
begin
- keyIndex := ExtractKeyIndex(keys[i], prefix, suffix);
- if (keyIndex > Result) then
- Result := keyIndex;
+ KeyIndex := ExtractKeyIndex(Keys[i], Prefix, Suffix);
+ if (KeyIndex > Result) then
+ Result := KeyIndex;
end;
end;
+(**
+ * Returns the index of Value in SearchArray
+ * or -1 if Value is not in SearchArray.
+ *)
+function TIni.GetArrayIndex(const SearchArray: array of string; Value: string;
+ CaseInsensitiv: Boolean = False): integer;
+var
+ i: integer;
+begin
+ Result := -1;
+
+ for i := 0 to High(SearchArray) do
+ begin
+ if (SearchArray[i] = Value) or
+ (CaseInsensitiv and (UpperCase(SearchArray[i]) = UpperCase(Value))) then
+ begin
+ Result := i;
+ Break;
+ end;
+ end;
+end;
+
+(**
+ * Reads the property IniSeaction:IniProperty from IniFile and
+ * finds its corresponding index in SearchArray.
+ * If SearchArray does not contain the property value, the default value is
+ * returned.
+ *)
+function TIni.ReadArrayIndex(const SearchArray: array of string; IniFile: TCustomIniFile;
+ IniSection: string; IniProperty: string; Default: integer): integer;
+var
+ StrValue: string;
+begin
+ StrValue := IniFile.ReadString(IniSection, IniProperty, SearchArray[Default]);
+ Result := GetArrayIndex(SearchArray, StrValue);
+ if (Result = -1) then
+ begin
+ Result := Default;
+ end;
+end;
+
+
procedure TIni.LoadInputDeviceCfg(IniFile: TMemIniFile);
var
- deviceCfg: PInputDeviceConfig;
- deviceIniIndex: integer;
- deviceIniStr: string;
- channelCount: integer;
- channelIndex: integer;
- recordKeys: TStringList;
+ DeviceCfg: PInputDeviceConfig;
+ DeviceIndex: integer;
+ ChannelCount: integer;
+ ChannelIndex: integer;
+ RecordKeys: TStringList;
i: integer;
begin
- recordKeys := TStringList.Create();
+ RecordKeys := TStringList.Create();
// read all record-keys for filtering
- IniFile.ReadSection('Record', recordKeys);
+ IniFile.ReadSection('Record', RecordKeys);
SetLength(InputDeviceConfig, 0);
- for i := 0 to recordKeys.Count-1 do
+ for i := 0 to RecordKeys.Count-1 do
begin
// find next device-name
- deviceIniIndex := ExtractKeyIndex(recordKeys[i], 'DeviceName[', ']');
- if (deviceIniIndex >= 0) then
+ DeviceIndex := ExtractKeyIndex(RecordKeys[i], 'DeviceName[', ']');
+ if (DeviceIndex >= 0) then
begin
- deviceIniStr := IntToStr(deviceIniIndex);
-
- if not IniFile.ValueExists('Record', 'DeviceName['+deviceIniStr+']') then
+ if not IniFile.ValueExists('Record', Format('DeviceName[%d]', [DeviceIndex])) then
break;
// resize list
@@ -294,28 +352,28 @@ begin
// Note: All devices are appended to the list whether they exist or not.
// Otherwise an external device's config will be lost if it is not
// connected (e.g. singstar mics or USB-Audio devices).
- deviceCfg := @InputDeviceConfig[High(InputDeviceConfig)];
- deviceCfg.Name := IniFile.ReadString('Record', 'DeviceName['+deviceIniStr+']', '');
- deviceCfg.Input := IniFile.Readinteger('Record', 'Input['+deviceIniStr+']', 0);
+ DeviceCfg := @InputDeviceConfig[High(InputDeviceConfig)];
+ DeviceCfg.Name := IniFile.ReadString('Record', Format('DeviceName[%d]', [DeviceIndex]), '');
+ DeviceCfg.Input := IniFile.ReadInteger('Record', Format('Input[%d]', [DeviceIndex]), 0);
// find the largest channel-number of the current device in the ini-file
- channelCount := GetMaxKeyIndex(recordKeys, 'Channel(', ')['+deviceIniStr+']');
- if (channelCount < 0) then
- channelCount := 0;
+ ChannelCount := GetMaxKeyIndex(RecordKeys, 'Channel', Format('[%d]', [DeviceIndex]));
+ if (ChannelCount < 0) then
+ ChannelCount := 0;
- SetLength(deviceCfg.ChannelToPlayerMap, channelCount);
+ SetLength(DeviceCfg.ChannelToPlayerMap, ChannelCount);
// read channel-to-player mapping for every channel of the current device
// or set non-configured channels to no player (=0).
- for channelIndex := 0 to High(deviceCfg.ChannelToPlayerMap) do
+ for ChannelIndex := 0 to High(DeviceCfg.ChannelToPlayerMap) do
begin
- deviceCfg.ChannelToPlayerMap[channelIndex] :=
- IniFile.Readinteger('Record', 'Channel('+IntToStr(channelIndex+1)+')['+deviceIniStr+']', 0);
+ DeviceCfg.ChannelToPlayerMap[ChannelIndex] :=
+ IniFile.ReadInteger('Record', Format('Channel%d[%d]', [ChannelIndex+1, DeviceIndex]), 0);
end;
end;
end;
- recordKeys.Free();
+ RecordKeys.Free();
// MicBoost
//MicBoost := GetArrayIndex(IMicBoost, IniFile.ReadString('Record', 'MicBoost', 'Off'));
@@ -325,23 +383,23 @@ end;
procedure TIni.SaveInputDeviceCfg(IniFile: TIniFile);
var
- deviceIndex: integer;
- deviceIndexStr: string;
- channelIndex: integer;
+ DeviceIndex: integer;
+ ChannelIndex: integer;
begin
- for deviceIndex := 0 to High(InputDeviceConfig) do
+ for DeviceIndex := 0 to High(InputDeviceConfig) do
begin
- deviceIndexStr := IntToStr(deviceIndex+1);
-
// DeviceName and DeviceInput
- IniFile.WriteString('Record', 'DeviceName['+deviceIndexStr+']', InputDeviceConfig[deviceIndex].Name);
- IniFile.WriteString('Record', 'Input['+deviceIndexStr+']', IntToStr(InputDeviceConfig[deviceIndex].Input));
+ IniFile.WriteString('Record', Format('DeviceName[%d]', [DeviceIndex+1]),
+ InputDeviceConfig[DeviceIndex].Name);
+ IniFile.WriteInteger('Record', Format('Input[%d]', [DeviceIndex+1]),
+ InputDeviceConfig[DeviceIndex].Input);
// Channel-to-Player Mapping
- for channelIndex := 0 to High(InputDeviceConfig[deviceIndex].ChannelToPlayerMap) do
+ for ChannelIndex := 0 to High(InputDeviceConfig[DeviceIndex].ChannelToPlayerMap) do
begin
- IniFile.WriteString('Record', 'Channel('+IntToStr(channelIndex+1)+')['+deviceIndexStr+']',
- IntToStr(InputDeviceConfig[deviceIndex].ChannelToPlayerMap[channelIndex]));
+ IniFile.WriteInteger('Record',
+ Format('Channel%d[%d]', [ChannelIndex+1, DeviceIndex+1]),
+ InputDeviceConfig[DeviceIndex].ChannelToPlayerMap[ChannelIndex]);
end;
end;
@@ -351,50 +409,77 @@ begin
//IniFile.WriteString('Record', 'Threshold', IThreshold[ThresholdIndex]);
end;
-procedure TIni.Load();
+procedure TIni.LoadPaths(IniFile: TCustomIniFile);
var
- IniFile: TMemIniFile;
- ThemeIni: TMemIniFile;
- ThemeName: string;
- I: integer;
- Modes: PPSDL_Rect;
- searchResult: TSearchRec;
- // returns name without fileextension
+ PathStrings: TStringList;
+ I: integer;
+begin
+ PathStrings := TStringList.Create;
+ IniFile.ReadSection('Directories', PathStrings);
- function GetFileName (S: string):string;
+ // Load song-paths
+ for I := 0 to PathStrings.Count-1 do
begin
- //Result := copy (S,0,StrRScan (PChar(S),char('.'))+1);
- Result := copy (S,0,Pos ('.ini',S)-1);
+ if (AnsiStartsText('SongDir', PathStrings[I])) then
+ begin
+ AddSongPath(IniFile.ReadString('Directories', PathStrings[I], ''));
+ end;
end;
- // get index of value V in array a, returns -1 if value is not in array
- function GetArrayIndex(const A: array of string; V: string; caseInsensitiv: Boolean = False): integer;
- var
- i: integer;
- begin
- Result := -1;
+ PathStrings.Free;
+end;
+
+procedure TIni.LoadThemes(IniFile: TCustomIniFile);
+var
+ SearchResult: TSearchRec;
+ ThemeIni: TMemIniFile;
+ ThemeName: string;
+ I: integer;
+begin
+ // Theme
+ SetLength(ITheme, 0);
+ Log.LogStatus('Searching for Theme : ' + ThemePath + '*.ini', 'Theme');
- for i := 0 To High(A) do
- if (A[i] = V) or (caseInsensitiv and (UpperCase(A[i]) = UpperCase(V))) then
+ FindFirst(ThemePath + '*.ini',faAnyFile, SearchResult);
+ Repeat
+ Log.LogStatus('Found Theme: ' + SearchResult.Name, 'Theme');
+
+ //Read Themename from Theme
+ ThemeIni := TMemIniFile.Create(SearchResult.Name);
+ ThemeName := UpperCase(ThemeIni.ReadString('Theme','Name', RemoveFileExt(SearchResult.Name)));
+ ThemeIni.Free;
+
+ //Search for Skins for this Theme
+ for I := Low(Skin.Skin) to High(Skin.Skin) do
+ begin
+ if UpperCase(Skin.Skin[I].Theme) = ThemeName then
begin
- Result := i;
+ SetLength(ITheme, Length(ITheme)+1);
+ ITheme[High(ITheme)] := RemoveFileExt(SearchResult.Name);
break;
end;
- end;
+ end;
+ until FindNext(SearchResult) <> 0;
+ FindClose(SearchResult);
- // get index of IniSeaction:IniProperty in array SearchArray or return the default value
- function ReadArrayIndex(const SearchArray: array of string; IniSection: string; IniProperty: string; Default: integer): integer;
- var
- StrValue: string;
+ // No Theme Found
+ if (Length(ITheme) = 0) then
begin
- StrValue := IniFile.ReadString('Sound', 'AudioOutputBufferSize', SearchArray[Default]);
- Result := GetArrayIndex(SearchArray, StrValue);
- if (Result = -1) then
- begin
- Result := Default;
- end;
+ Log.CriticalError('Could not find any valid Themes.');
end;
+ Theme := GetArrayIndex(ITheme, IniFile.ReadString('Themes', 'Theme', 'DELUXE'), true);
+ if (Theme = -1) then
+ Theme := 0;
+
+ // Skin
+ Skin.onThemeChange;
+
+ SkinNo := GetArrayIndex(ISkin, IniFile.ReadString('Themes', 'Skin', ISkin[0]));
+end;
+
+procedure TIni.LoadScreenModes(IniFile: TCustomIniFile);
+
// swap two strings
procedure swap(var s1, s2: string);
var
@@ -405,53 +490,10 @@ var
s2 := s3;
end;
+var
+ Modes: PPSDL_Rect;
+ I: integer;
begin
- GamePath := Platform.GetGameUserPath;
-
- Log.LogStatus( 'GamePath : ' +GamePath , '' );
-
- if (Params.ConfigFile <> '') then
- try
- FileName := Params.ConfigFile;
- except
- FileName := GamePath + 'config.ini';
- end
- else
- FileName := GamePath + 'config.ini';
-
- Log.LogStatus( 'Using config : ' + FileName , 'Ini');
- IniFile := TMemIniFile.Create( FileName );
-
- // Name
- for I := 0 to 11 do
- Name[I] := IniFile.ReadString('Name', 'P'+IntToStr(I+1), 'Player'+IntToStr(I+1));
-
- // Templates for Names Mod
- for I := 0 to 2 do
- NameTeam[I] := IniFile.ReadString('NameTeam', 'T'+IntToStr(I+1), 'Team'+IntToStr(I+1));
- for I := 0 to 11 do
- NameTemplate[I] := IniFile.ReadString('NameTemplate', 'Name'+IntToStr(I+1), 'Template'+IntToStr(I+1));
-
- // Players
- Players := GetArrayIndex(IPlayers, IniFile.ReadString('Game', 'Players', IPlayers[0]));
-
- // Difficulty
- Difficulty := GetArrayIndex(IDifficulty, IniFile.ReadString('Game', 'Difficulty', 'Easy'));
-
- // Language
- Language := GetArrayIndex(ILanguage, IniFile.ReadString('Game', 'Language', 'English'));
- //Language.ChangeLanguage(ILanguage[Language]);
-
- // Tabs
- Tabs := GetArrayIndex(ITabs, IniFile.ReadString('Game', 'Tabs', ITabs[0]));
- Tabs_at_startup := Tabs; //Tabs at Startup fix
-
- // Song Sorting
- Sorting := GetArrayIndex(ISorting, IniFile.ReadString('Game', 'Sorting', ISorting[0]));
-
- // Debug
- Debug := GetArrayIndex(IDebug, IniFile.ReadString('Game', 'Debug', IDebug[0]));
-
// Screens
Screens := GetArrayIndex(IScreens, IniFile.ReadString('Graphics', 'Screens', IScreens[0]));
@@ -538,7 +580,61 @@ begin
// Depth
Depth := GetArrayIndex(IDepth, IniFile.ReadString('Graphics', 'Depth', '32 bit'));
+end;
+
+procedure TIni.Load();
+var
+ IniFile: TMemIniFile;
+ I: integer;
+begin
+ GamePath := Platform.GetGameUserPath;
+
+ Log.LogStatus( 'GamePath : ' +GamePath , '' );
+
+ if (Params.ConfigFile <> '') then
+ try
+ FileName := Params.ConfigFile;
+ except
+ FileName := GamePath + 'config.ini';
+ end
+ else
+ FileName := GamePath + 'config.ini';
+
+ Log.LogStatus( 'Using config : ' + FileName , 'Ini');
+ IniFile := TMemIniFile.Create( FileName );
+
+ // Name
+ for I := 0 to 11 do
+ Name[I] := IniFile.ReadString('Name', 'P'+IntToStr(I+1), 'Player'+IntToStr(I+1));
+
+ // Templates for Names Mod
+ for I := 0 to 2 do
+ NameTeam[I] := IniFile.ReadString('NameTeam', 'T'+IntToStr(I+1), 'Team'+IntToStr(I+1));
+ for I := 0 to 11 do
+ NameTemplate[I] := IniFile.ReadString('NameTemplate', 'Name'+IntToStr(I+1), 'Template'+IntToStr(I+1));
+
+ // Players
+ Players := GetArrayIndex(IPlayers, IniFile.ReadString('Game', 'Players', IPlayers[0]));
+
+ // Difficulty
+ Difficulty := GetArrayIndex(IDifficulty, IniFile.ReadString('Game', 'Difficulty', 'Easy'));
+
+ // Language
+ Language := GetArrayIndex(ILanguage, IniFile.ReadString('Game', 'Language', 'English'));
+ //Language.ChangeLanguage(ILanguage[Language]);
+
+ // Tabs
+ Tabs := GetArrayIndex(ITabs, IniFile.ReadString('Game', 'Tabs', ITabs[0]));
+ Tabs_at_startup := Tabs; //Tabs at Startup fix
+
+ // Song Sorting
+ Sorting := GetArrayIndex(ISorting, IniFile.ReadString('Game', 'Sorting', ISorting[0]));
+ // Debug
+ Debug := GetArrayIndex(IDebug, IniFile.ReadString('Game', 'Debug', IDebug[0]));
+
+ LoadScreenModes(IniFile);
+
// TextureSize
TextureSize := GetArrayIndex(ITextureSize, IniFile.ReadString('Graphics', 'TextureSize', ITextureSize[1]));
@@ -567,7 +663,7 @@ begin
SavePlayback := GetArrayIndex(ISavePlayback, IniFile.ReadString('Sound', 'SavePlayback', ISavePlayback[0]));
// AudioOutputBufferSize
- AudioOutputBufferSizeIndex := ReadArrayIndex(IAudioOutputBufferSize, 'Sound', 'AudioOutputBufferSize', 0);
+ AudioOutputBufferSizeIndex := ReadArrayIndex(IAudioOutputBufferSize, IniFile, 'Sound', 'AudioOutputBufferSize', 0);
//Preview Volume
PreviewVolume := GetArrayIndex(IPreviewVolume, IniFile.ReadString('Sound', 'PreviewVolume', IPreviewVolume[7]));
@@ -590,45 +686,7 @@ begin
// NoteLines
NoteLines := GetArrayIndex(INoteLines, IniFile.ReadString('Lyrics', 'NoteLines', INoteLines[1]));
- // Theme
- SetLength(ITheme, 0);
- Log.LogStatus('Searching for Theme : ' + ThemePath + '*.ini', 'Theme');
- FindFirst(ThemePath + '*.ini',faAnyFile, searchResult);
- Repeat
- Log.LogStatus('Found Theme: ' + searchResult.Name, 'Theme');
-
- //Read Themename from Theme
- ThemeIni := TMemIniFile.Create(searchResult.Name);
- ThemeName := UpperCase(ThemeIni.ReadString('Theme','Name', GetFileName(searchResult.Name)));
- ThemeIni.Free;
-
- //Search for Skins for this Theme
- for I := Low(Skin.Skin) to High(Skin.Skin) do
- begin
- if UpperCase(Skin.Skin[I].Theme) = ThemeName then
- begin
- SetLength(ITheme, Length(ITheme)+1);
- ITheme[High(ITheme)] := GetFileName(searchResult.Name);
- break;
- end;
- end;
- until FindNext(searchResult) <> 0;
- FindClose(searchResult);
-
- // No Theme Found
- if (Length(ITheme) = 0) then
- begin
- Log.CriticalError('Could not find any valid Themes.');
- end;
-
- Theme := GetArrayIndex(ITheme, IniFile.ReadString('Themes', 'Theme', 'DELUXE'), true);
- if (Theme = -1) then
- Theme := 0;
-
- // Skin
- Skin.onThemeChange;
-
- SkinNo := GetArrayIndex(ISkin, IniFile.ReadString('Themes', 'Skin', ISkin[0]));
+ LoadThemes(IniFile);
// Color
Color := GetArrayIndex(IColor, IniFile.ReadString('Themes', 'Color', IColor[0]));
@@ -645,7 +703,7 @@ begin
EffectSing := GetArrayIndex(IEffectSing, IniFile.ReadString('Advanced', 'EffectSing', 'On'));
// AskbeforeDel
- AskbeforeDel := GetArrayIndex(IAskbeforeDel, IniFile.ReadString('Advanced', 'AskbeforeDel', 'On'));
+ AskBeforeDel := GetArrayIndex(IAskbeforeDel, IniFile.ReadString('Advanced', 'AskbeforeDel', 'On'));
// OnSongClick
OnSongClick := GetArrayIndex(IOnSongClick, IniFile.ReadString('Advanced', 'OnSongClick', 'Sing'));
@@ -659,14 +717,7 @@ begin
// Joypad
Joypad := GetArrayIndex(IJoypad, IniFile.ReadString('Controller', 'Joypad', IJoypad[0]));
- // LCD
- LPT := GetArrayIndex(ILPT, IniFile.ReadString('Devices', 'LPT', ILPT[0]));
-
- // SongPath
- if (Params.SongPath <> '') then
- SongPath := IncludeTrailingPathDelimiter(Params.SongPath)
- else
- SongPath := IncludeTrailingPathDelimiter(IniFile.ReadString('Path', 'Songs', SongPath));
+ LoadPaths(IniFile);
IniFile.Free;
end;
@@ -675,76 +726,79 @@ procedure TIni.Save;
var
IniFile: TIniFile;
begin
- if not (FileExists(Filename) and FileIsReadOnly(Filename)) then
+ if (FileExists(Filename) and FileIsReadOnly(Filename)) then
begin
+ Log.LogError('Config-file is read-only', 'TIni.Save');
+ Exit;
+ end;
- IniFile := TIniFile.Create(Filename);
+ IniFile := TIniFile.Create(Filename);
- // Players
- IniFile.WriteString('Game', 'Players', IPlayers[Players]);
+ // Players
+ IniFile.WriteString('Game', 'Players', IPlayers[Players]);
- // Difficulty
- IniFile.WriteString('Game', 'Difficulty', IDifficulty[Difficulty]);
+ // Difficulty
+ IniFile.WriteString('Game', 'Difficulty', IDifficulty[Difficulty]);
- // Language
- IniFile.WriteString('Game', 'Language', ILanguage[Language]);
+ // Language
+ IniFile.WriteString('Game', 'Language', ILanguage[Language]);
- // Tabs
- IniFile.WriteString('Game', 'Tabs', ITabs[Tabs]);
+ // Tabs
+ IniFile.WriteString('Game', 'Tabs', ITabs[Tabs]);
- // Sorting
- IniFile.WriteString('Game', 'Sorting', ISorting[Sorting]);
+ // Sorting
+ IniFile.WriteString('Game', 'Sorting', ISorting[Sorting]);
- // Debug
- IniFile.WriteString('Game', 'Debug', IDebug[Debug]);
+ // Debug
+ IniFile.WriteString('Game', 'Debug', IDebug[Debug]);
- // Screens
- IniFile.WriteString('Graphics', 'Screens', IScreens[Screens]);
+ // Screens
+ IniFile.WriteString('Graphics', 'Screens', IScreens[Screens]);
- // FullScreen
- IniFile.WriteString('Graphics', 'FullScreen', IFullScreen[FullScreen]);
+ // FullScreen
+ IniFile.WriteString('Graphics', 'FullScreen', IFullScreen[FullScreen]);
- // Resolution
- IniFile.WriteString('Graphics', 'Resolution', IResolution[Resolution]);
+ // Resolution
+ IniFile.WriteString('Graphics', 'Resolution', IResolution[Resolution]);
- // Depth
- IniFile.WriteString('Graphics', 'Depth', IDepth[Depth]);
+ // Depth
+ IniFile.WriteString('Graphics', 'Depth', IDepth[Depth]);
- // TextureSize
- IniFile.WriteString('Graphics', 'TextureSize', ITextureSize[TextureSize]);
+ // TextureSize
+ IniFile.WriteString('Graphics', 'TextureSize', ITextureSize[TextureSize]);
- // Sing Window
- IniFile.WriteString('Graphics', 'SingWindow', ISingWindow[SingWindow]);
+ // Sing Window
+ IniFile.WriteString('Graphics', 'SingWindow', ISingWindow[SingWindow]);
- // Oscilloscope
- IniFile.WriteString('Graphics', 'Oscilloscope', IOscilloscope[Oscilloscope]);
+ // Oscilloscope
+ IniFile.WriteString('Graphics', 'Oscilloscope', IOscilloscope[Oscilloscope]);
- // Spectrum
- IniFile.WriteString('Graphics', 'Spectrum', ISpectrum[Spectrum]);
+ // Spectrum
+ IniFile.WriteString('Graphics', 'Spectrum', ISpectrum[Spectrum]);
- // Spectrograph
- IniFile.WriteString('Graphics', 'Spectrograph', ISpectrograph[Spectrograph]);
+ // Spectrograph
+ IniFile.WriteString('Graphics', 'Spectrograph', ISpectrograph[Spectrograph]);
- // Movie Size
- IniFile.WriteString('Graphics', 'MovieSize', IMovieSize[MovieSize]);
+ // Movie Size
+ IniFile.WriteString('Graphics', 'MovieSize', IMovieSize[MovieSize]);
- // ClickAssist
- IniFile.WriteString('Sound', 'ClickAssist', IClickAssist[ClickAssist]);
+ // ClickAssist
+ IniFile.WriteString('Sound', 'ClickAssist', IClickAssist[ClickAssist]);
- // BeatClick
- IniFile.WriteString('Sound', 'BeatClick', IBeatClick[BeatClick]);
+ // BeatClick
+ IniFile.WriteString('Sound', 'BeatClick', IBeatClick[BeatClick]);
- // AudioOutputBufferSize
- IniFile.WriteString('Sound', 'AudioOutputBufferSize', IAudioOutputBufferSize[AudioOutputBufferSizeIndex]);
+ // AudioOutputBufferSize
+ IniFile.WriteString('Sound', 'AudioOutputBufferSize', IAudioOutputBufferSize[AudioOutputBufferSizeIndex]);
- // Song Preview
- IniFile.WriteString('Sound', 'PreviewVolume', IPreviewVolume[PreviewVolume]);
+ // Song Preview
+ IniFile.WriteString('Sound', 'PreviewVolume', IPreviewVolume[PreviewVolume]);
- // PreviewFading
- IniFile.WriteString('Sound', 'PreviewFading', IPreviewFading[PreviewFading]);
+ // PreviewFading
+ IniFile.WriteString('Sound', 'PreviewFading', IPreviewFading[PreviewFading]);
- // SavePlayback
- IniFile.WriteString('Sound', 'SavePlayback', ISavePlayback[SavePlayback]);
+ // SavePlayback
+ IniFile.WriteString('Sound', 'SavePlayback', ISavePlayback[SavePlayback]);
// NoteLines
IniFile.WriteString('Sound', 'VoicePassthrough', IVoicePassthrough[VoicePassthrough]);
@@ -752,54 +806,55 @@ begin
// Lyrics Font
IniFile.WriteString('Lyrics', 'LyricsFont', ILyricsFont[LyricsFont]);
- // Lyrics Effect
- IniFile.WriteString('Lyrics', 'LyricsEffect', ILyricsEffect[LyricsEffect]);
+ // Lyrics Effect
+ IniFile.WriteString('Lyrics', 'LyricsEffect', ILyricsEffect[LyricsEffect]);
- // Solmization
- IniFile.WriteString('Lyrics', 'Solmization', ISolmization[Solmization]);
+ // Solmization
+ IniFile.WriteString('Lyrics', 'Solmization', ISolmization[Solmization]);
- // NoteLines
- IniFile.WriteString('Lyrics', 'NoteLines', INoteLines[NoteLines]);
+ // NoteLines
+ IniFile.WriteString('Lyrics', 'NoteLines', INoteLines[NoteLines]);
- // Theme
- IniFile.WriteString('Themes', 'Theme', ITheme[Theme]);
+ // Theme
+ IniFile.WriteString('Themes', 'Theme', ITheme[Theme]);
- // Skin
- IniFile.WriteString('Themes', 'Skin', ISkin[SkinNo]);
+ // Skin
+ IniFile.WriteString('Themes', 'Skin', ISkin[SkinNo]);
- // Color
- IniFile.WriteString('Themes', 'Color', IColor[Color]);
+ // Color
+ IniFile.WriteString('Themes', 'Color', IColor[Color]);
- SaveInputDeviceCfg(IniFile);
- //Log.LogError(InttoStr(Length(CardList)) + ' Cards Saved');
+ SaveInputDeviceCfg(IniFile);
- //LoadAnimation
- IniFile.WriteString('Advanced', 'LoadAnimation', ILoadAnimation[LoadAnimation]);
+ //LoadAnimation
+ IniFile.WriteString('Advanced', 'LoadAnimation', ILoadAnimation[LoadAnimation]);
- //EffectSing
- IniFile.WriteString('Advanced', 'EffectSing', IEffectSing[EffectSing]);
+ //EffectSing
+ IniFile.WriteString('Advanced', 'EffectSing', IEffectSing[EffectSing]);
- //ScreenFade
- IniFile.WriteString('Advanced', 'ScreenFade', IScreenFade[ScreenFade]);
+ //ScreenFade
+ IniFile.WriteString('Advanced', 'ScreenFade', IScreenFade[ScreenFade]);
- //AskbeforeDel
- IniFile.WriteString('Advanced', 'AskbeforeDel', IAskbeforeDel[AskbeforeDel]);
+ //AskbeforeDel
+ IniFile.WriteString('Advanced', 'AskbeforeDel', IAskbeforeDel[AskBeforeDel]);
- //OnSongClick
- IniFile.WriteString('Advanced', 'OnSongClick', IOnSongClick[OnSongClick]);
+ //OnSongClick
+ IniFile.WriteString('Advanced', 'OnSongClick', IOnSongClick[OnSongClick]);
- //Line Bonus
- IniFile.WriteString('Advanced', 'LineBonus', ILineBonus[LineBonus]);
+ //Line Bonus
+ IniFile.WriteString('Advanced', 'LineBonus', ILineBonus[LineBonus]);
- //Party Popup
- IniFile.WriteString('Advanced', 'PartyPopup', IPartyPopup[PartyPopup]);
+ //Party Popup
+ IniFile.WriteString('Advanced', 'PartyPopup', IPartyPopup[PartyPopup]);
- // Joypad
- IniFile.WriteString('Controller', 'Joypad', IJoypad[Joypad]);
+ // Joypad
+ IniFile.WriteString('Controller', 'Joypad', IJoypad[Joypad]);
- IniFile.Free;
+ // Directories (add a template if section is missing)
+ if (not IniFile.SectionExists('Directories')) then
+ IniFile.WriteString('Directories', 'SongDir1', '');
- end;
+ IniFile.Free;
end;
procedure TIni.SaveNames;
diff --git a/Game/Code/Classes/UMain.pas b/Game/Code/Classes/UMain.pas
index d0c702b9..da9df6d3 100644
--- a/Game/Code/Classes/UMain.pas
+++ b/Game/Code/Classes/UMain.pas
@@ -9,11 +9,12 @@ interface
{$I switches.inc}
uses
+ SysUtils,
+ Classes,
SDL,
UMusic,
URecord,
UTime,
- SysUtils,
UDisplay,
UIni,
ULog,
@@ -67,22 +68,18 @@ var
// Absolute Paths
GamePath: string;
SoundPath: string;
- SongPath: string;
+ SongPaths: TStringList;
LogPath: string;
ThemePath: string;
SkinsPath: string;
ScreenshotsPath: string;
- CoversPath: string;
+ CoverPaths: TStringList;
LanguagesPath: string;
PluginPath: string;
VisualsPath: string;
ResourcesPath: string;
PlayListPath: string;
- UserSongPath: string = '';
- UserCoversPath: string = '';
- UserPlaylistPath: string = '';
-
Done: Boolean;
Event: TSDL_event;
// FIXME: ConversionFileName should not be global
@@ -99,7 +96,9 @@ const
MAX_SONG_SCORE = 10000; // max. achievable points per song
MAX_SONG_LINE_BONUS = 1000; // max. achievable line bonus per song
+function FindPath(out PathResult: string; const RequestedPath: string; NeedsWritePermission: boolean): boolean;
procedure InitializePaths;
+procedure AddSongPath(const Path: string);
Procedure Main;
procedure MainLoop;
@@ -116,9 +115,10 @@ procedure ClearScores(PlayerNum: integer);
implementation
uses
+ Math,
+ StrUtils,
USongs,
UJoystick,
- math,
UCommandLine,
ULanguage,
//SDL_ttf,
@@ -989,60 +989,119 @@ begin
end;
end;
-//--------------------
-// Function sets all absolute paths e.g. song path and makes sure the directorys exist
-//--------------------
-procedure InitializePaths;
+procedure AddSpecialPath(var PathList: TStringList; const Path: string);
+var
+ I: integer;
+ PathAbs, OldPathAbs: string;
+begin
+ if (PathList = nil) then
+ PathList := TStringList.Create;
- // Initialize a path variable
- // After setting paths, make sure that paths exist
- {$WARN SYMBOL_PLATFORM OFF}
- function initialize_path( out aPathVar : string; const aLocation : string ): boolean;
- var
- lWriteable: Boolean;
- lAttrib : integer;
- begin
- lWriteable := false;
- aPathVar := aLocation;
+ if (Path = '') or not DirectoryExists(Path) then
+ Exit;
- // Make sure the directory is needex
- ForceDirectories(aPathVar);
+ PathAbs := IncludeTrailingPathDelimiter(ExpandFileName(Path));
- if DirectoryExists(aPathVar) then
+ // check if path or a part of the path was already added
+ for I := 0 to PathList.Count-1 do
+ begin
+ OldPathAbs := IncludeTrailingPathDelimiter(ExpandFileName(PathList[I]));
+ // check if the new directory is a sub-directory of a previously added one.
+ // This is also true, if both paths point to the same directories.
+ if (AnsiStartsText(OldPathAbs, PathAbs)) then
begin
- lAttrib := fileGetAttr(aPathVar);
+ // ignore the new path
+ Exit;
+ end;
- lWriteable := (lAttrib and faDirectory <> 0) and
- not (lAttrib and faReadOnly <> 0)
+ // check if a previously added directory is a sub-directory of the new one.
+ if (AnsiStartsText(PathAbs, OldPathAbs)) then
+ begin
+ // replace the old with the new one.
+ PathList[I] := PathAbs;
+ Exit;
end;
+ end;
+
+ PathList.Add(PathAbs);
+end;
+
+procedure AddSongPath(const Path: string);
+begin
+ AddSpecialPath(SongPaths, Path);
+end;
+
+procedure AddCoverPath(const Path: string);
+begin
+ AddSpecialPath(CoverPaths, Path);
+end;
+
+(**
+ * Initialize a path variable
+ * After setting paths, make sure that paths exist
+ *)
+function FindPath(out PathResult: string; const RequestedPath: string; NeedsWritePermission: boolean): boolean;
+begin
+ Result := false;
- if not lWriteable then
- Log.LogWarn('Dir ('+ aLocation +') is Readonly', 'initialize_path');
+ if (RequestedPath = '') then
+ Exit;
- result := lWriteable;
+ // Make sure the directory exists
+ if (not ForceDirectories(RequestedPath)) then
+ begin
+ PathResult := '';
+ Exit;
+ end;
+
+ PathResult := IncludeTrailingPathDelimiter(RequestedPath);
+
+ if (NeedsWritePermission) and
+ (FileIsReadOnly(RequestedPath)) then
+ begin
+ Exit;
end;
- {$WARN SYMBOL_PLATFORM ON}
+ Result := true;
+end;
+
+(**
+ * Function sets all absolute paths e.g. song path and makes sure the directorys exist
+ *)
+procedure InitializePaths;
begin
- initialize_path( LogPath , Platform.GetLogPath );
- initialize_path( SoundPath , Platform.GetGameSharedPath + 'Sounds' + PathDelim );
- initialize_path( ThemePath , Platform.GetGameSharedPath + 'Themes' + PathDelim );
- initialize_path( SkinsPath , Platform.GetGameSharedPath + 'Themes' + PathDelim );
- initialize_path( LanguagesPath , Platform.GetGameSharedPath + 'Languages' + PathDelim );
- initialize_path( PluginPath , Platform.GetGameSharedPath + 'Plugins' + PathDelim );
- initialize_path( VisualsPath , Platform.GetGameSharedPath + 'Visuals' + PathDelim );
- initialize_path( ResourcesPath , Platform.GetGameSharedPath + 'Resources' + PathDelim );
- initialize_path( ScreenshotsPath , Platform.GetGameUserPath + 'Screenshots' + PathDelim );
-
- // Users Song Path ....
- initialize_path( UserSongPath , Platform.GetGameUserPath + 'Songs' + PathDelim );
- initialize_path( UserCoversPath , Platform.GetGameUserPath + 'Covers' + PathDelim );
- initialize_path( UserPlaylistPath , Platform.GetGameUserPath + 'Playlists' + PathDelim );
-
- // Shared Song Path ....
- initialize_path( SongPath , Platform.GetGameSharedPath + 'Songs' + PathDelim );
- initialize_path( CoversPath , Platform.GetGameSharedPath + 'Covers' + PathDelim );
- initialize_path( PlaylistPath , Platform.GetGameSharedPath + 'Playlists' + PathDelim );
+ // Log directory (must be writable)
+ if (not FindPath(LogPath, Platform.GetLogPath, true)) then
+ begin
+ Log.FileOutputEnabled := false;
+ Log.LogWarn('Log directory "'+ Platform.GetLogPath +'" not available', 'InitializePaths');
+ end;
+
+ FindPath(SoundPath, Platform.GetGameSharedPath + 'Sounds', false);
+ FindPath(ThemePath, Platform.GetGameSharedPath + 'Themes', false);
+ FindPath(SkinsPath, Platform.GetGameSharedPath + 'Themes', false);
+ FindPath(LanguagesPath, Platform.GetGameSharedPath + 'Languages', false);
+ FindPath(PluginPath, Platform.GetGameSharedPath + 'Plugins', false);
+ FindPath(VisualsPath, Platform.GetGameSharedPath + 'Visuals', false);
+ FindPath(ResourcesPath, Platform.GetGameSharedPath + 'Resources', false);
+
+ // Playlists are not shared as we need one directory to write too
+ FindPath(PlaylistPath, Platform.GetGameUserPath + 'Playlists', true);
+
+ // Screenshot directory (must be writable)
+ if (not FindPath(ScreenshotsPath, Platform.GetGameUserPath + 'Screenshots', true)) then
+ begin
+ Log.LogWarn('Screenshot directory "'+ Platform.GetGameUserPath +'" not available', 'InitializePaths');
+ end;
+
+ // Add song paths
+ AddSongPath(Params.SongPath);
+ AddSongPath(Platform.GetGameSharedPath + 'Songs');
+ AddSongPath(Platform.GetGameUserPath + 'Songs');
+
+ // Add category cover paths
+ AddCoverPath(Platform.GetGameSharedPath + 'Covers');
+ AddCoverPath(Platform.GetGameUserPath + 'Covers');
end;
end.
diff --git a/Game/Code/Classes/USongs.pas b/Game/Code/Classes/USongs.pas
index 2c8e3587..710cd44f 100644
--- a/Game/Code/Classes/USongs.pas
+++ b/Game/Code/Classes/USongs.pas
@@ -195,6 +195,8 @@ begin
end;
procedure TSongs.int_LoadSongList;
+var
+ I: integer;
begin
try
fProcessing := true;
@@ -202,10 +204,8 @@ begin
Log.LogStatus('Searching For Songs', 'SongList');
// browse directories
- BrowseDir(SongPath);
-
- if UserSongPath <> SongPath then
- BrowseDir(UserSongPath);
+ for I := 0 to SongPaths.Count-1 do
+ BrowseDir(SongPaths[I]);
if assigned( CatSongs ) then
CatSongs.Refresh;
diff --git a/Game/Code/Menu/UDisplay.pas b/Game/Code/Menu/UDisplay.pas
index 67eed740..2b10b2c6 100644
--- a/Game/Code/Menu/UDisplay.pas
+++ b/Game/Code/Menu/UDisplay.pas
@@ -290,6 +290,10 @@ var
Align: integer;
RowSize: integer;
begin
+ // Exit if Screenshot-path does not exist or read-only
+ if (ScreenshotsPath = '') then
+ Exit;
+
for Num := 1 to 9999 do
begin
FileName := IntToStr(Num);
diff --git a/Game/Code/Screens/UScreenOptionsAdvanced.pas b/Game/Code/Screens/UScreenOptionsAdvanced.pas
index 711414aa..be8895e1 100644
--- a/Game/Code/Screens/UScreenOptionsAdvanced.pas
+++ b/Game/Code/Screens/UScreenOptionsAdvanced.pas
@@ -93,7 +93,7 @@ begin
AddSelectSlide(Theme.OptionsAdvanced.SelectEffectSing, Ini.EffectSing, IEffectSing);
AddSelectSlide(Theme.OptionsAdvanced.SelectLineBonus, Ini.LineBonus, ILineBonus);
AddSelectSlide(Theme.OptionsAdvanced.SelectOnSongClick, Ini.OnSongClick, IOnSongClick);
- AddSelectSlide(Theme.OptionsAdvanced.SelectAskbeforeDel, Ini.AskbeforeDel, IAskbeforeDel);
+ AddSelectSlide(Theme.OptionsAdvanced.SelectAskbeforeDel, Ini.AskBeforeDel, IAskbeforeDel);
AddSelectSlide(Theme.OptionsAdvanced.SelectPartyPopup, Ini.PartyPopup, IPartyPopup);
AddButton(Theme.OptionsAdvanced.ButtonExit);