Added: tracking of Modified state
Added: saving / reverting changes Fixed: access violation on preview with an empty map list
This commit is contained in:
parent
47129c0b32
commit
12eba2e832
@ -14,7 +14,8 @@ uses
|
||||
Game.List in 'source\model\Game.List.pas',
|
||||
Persist.GameList in 'source\persist\Persist.GameList.pas',
|
||||
Forms.Map in 'source\view\Forms.Map.pas' {MapForm},
|
||||
Frame.MapPreview in 'source\view\Frame.MapPreview.pas' {MapPreviewFrame: TFrame};
|
||||
Frame.MapPreview in 'source\view\Frame.MapPreview.pas' {MapPreviewFrame: TFrame},
|
||||
UDKIniFile in 'source\UDKIniFile.pas';
|
||||
|
||||
{$R *.res}
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
||||
<VerInfo_Keys>CompanyName=X²Software;FileDescription=Chivalry Server Launcher;FileVersion=0.1.0.0;InternalName=;LegalCopyright=Copyright (c) 2014 X²Software;LegalTrademarks=;OriginalFilename=ChivalryServerLauncher.exe;ProductName=Chivalry Server Launcher;ProductVersion=0.1</VerInfo_Keys>
|
||||
<VerInfo_MinorVer>1</VerInfo_MinorVer>
|
||||
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
|
||||
<VerInfo_Locale>1043</VerInfo_Locale>
|
||||
<VerInfo_Locale>1033</VerInfo_Locale>
|
||||
<VerInfo_MajorVer>0</VerInfo_MajorVer>
|
||||
<Icon_MainIcon>resources\icons\MainIcon.ico</Icon_MainIcon>
|
||||
<Manifest_File>None</Manifest_File>
|
||||
@ -69,6 +69,7 @@
|
||||
<DCC_RemoteDebug>true</DCC_RemoteDebug>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
|
||||
<DCC_DebugDCUs>true</DCC_DebugDCUs>
|
||||
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
|
||||
@ -104,6 +105,7 @@
|
||||
<FormType>dfm</FormType>
|
||||
<DesignClass>TFrame</DesignClass>
|
||||
</DCCReference>
|
||||
<DCCReference Include="source\UDKIniFile.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
|
Binary file not shown.
444
source/UDKIniFile.pas
Normal file
444
source/UDKIniFile.pas
Normal file
@ -0,0 +1,444 @@
|
||||
unit UDKIniFile;
|
||||
|
||||
interface
|
||||
uses
|
||||
System.Classes,
|
||||
System.IniFiles,
|
||||
System.SysUtils;
|
||||
|
||||
|
||||
type
|
||||
{ Basically a TMemIniFile with benefits (TMemIniFile isn't virtual enough
|
||||
to allow simple additions to it). Adds helpers to handle multiple keys
|
||||
with the same name. }
|
||||
TUDKIniFile = class(TCustomIniFile)
|
||||
private
|
||||
FSections: TStringList;
|
||||
FEncoding: TEncoding;
|
||||
|
||||
function GetCaseSensitive: Boolean;
|
||||
procedure SetCaseSensitive(Value: Boolean);
|
||||
protected
|
||||
function AddSection(const ASection: string): TStrings;
|
||||
procedure LoadValues;
|
||||
|
||||
property Sections: TStringList read FSections;
|
||||
public
|
||||
constructor Create(const AFileName: string); overload;
|
||||
constructor Create(const AFileName: string; AEncoding: TEncoding); overload;
|
||||
destructor Destroy; override;
|
||||
|
||||
procedure Clear;
|
||||
|
||||
function ReadString(const ASection, AIdent, ADefault: string): string; override;
|
||||
procedure DeleteKey(const ASection, AIdent: string); override;
|
||||
procedure EraseSection(const ASection: string); override;
|
||||
procedure ReadSection(const ASection: string; AStrings: TStrings); override;
|
||||
procedure ReadSections(AStrings: TStrings); override;
|
||||
procedure ReadSectionValues(const ASection: string; AStrings: TStrings); override;
|
||||
procedure UpdateFile; override;
|
||||
procedure WriteString(const ASection, AIdent, AValue: string); override;
|
||||
|
||||
procedure GetStrings(AList: TStrings);
|
||||
procedure SetStrings(AList: TStrings);
|
||||
|
||||
{ Helpers for duplicate keys }
|
||||
procedure ReadDuplicateStrings(const ASection, AIdent: string; AList: TStrings);
|
||||
procedure WriteDuplicateString(const ASection, AIdent, AValue: string);
|
||||
procedure DeleteDuplicateKeys(const ASection, AIdent: string);
|
||||
|
||||
property CaseSensitive: Boolean read GetCaseSensitive write SetCaseSensitive;
|
||||
property Encoding: TEncoding read FEncoding write FEncoding;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
type
|
||||
{ Added here so we can access the protected members }
|
||||
TUDKHashedStringList = class(THashedStringList);
|
||||
|
||||
|
||||
{ TUDKIniFile }
|
||||
constructor TUDKIniFile.Create(const AFileName: string);
|
||||
begin
|
||||
Create(AFilename, nil);
|
||||
end;
|
||||
|
||||
|
||||
constructor TUDKIniFile.Create(const AFileName: string; AEncoding: TEncoding);
|
||||
begin
|
||||
inherited Create(AFileName);
|
||||
|
||||
FEncoding := AEncoding;
|
||||
FSections := TUDKHashedStringList.Create;
|
||||
LoadValues;
|
||||
end;
|
||||
|
||||
|
||||
destructor TUDKIniFile.Destroy;
|
||||
begin
|
||||
Clear;
|
||||
FreeAndNil(FSections);
|
||||
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.Clear;
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
|
||||
begin
|
||||
for sectionIndex := 0 to Pred(Sections.Count) do
|
||||
Sections.Objects[sectionIndex].Free;
|
||||
|
||||
Sections.Clear;
|
||||
end;
|
||||
|
||||
|
||||
function TUDKIniFile.ReadString(const ASection, AIdent, ADefault: string): string;
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
strings: TStrings;
|
||||
|
||||
begin
|
||||
sectionIndex := Sections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
begin
|
||||
strings := TStrings(Sections.Objects[sectionIndex]);
|
||||
sectionIndex := strings.IndexOfName(AIdent);
|
||||
|
||||
if sectionIndex > -1 then
|
||||
begin
|
||||
Result := Copy(strings[sectionIndex], Length(AIdent) + 2, MaxInt);
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := ADefault;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.DeleteKey(const ASection, AIdent: string);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
keyIndex: Integer;
|
||||
Strings: TStrings;
|
||||
|
||||
begin
|
||||
sectionIndex := FSections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
begin
|
||||
strings := TStrings(FSections.Objects[sectionIndex]);
|
||||
keyIndex := strings.IndexOfName(AIdent);
|
||||
|
||||
if keyIndex > -1 then
|
||||
strings.Delete(keyIndex);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.EraseSection(const ASection: string);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
|
||||
begin
|
||||
sectionIndex := Sections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
begin
|
||||
TStrings(Sections.Objects[sectionIndex]).Free;
|
||||
Sections.Delete(sectionIndex);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.ReadSection(const ASection: string; AStrings: TStrings);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
keyIndex: Integer;
|
||||
sectionStrings: TStrings;
|
||||
|
||||
begin
|
||||
AStrings.BeginUpdate;
|
||||
try
|
||||
AStrings.Clear;
|
||||
|
||||
sectionIndex := Sections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
begin
|
||||
sectionStrings := TStrings(Sections.Objects[sectionIndex]);
|
||||
for keyIndex := 0 to Pred(sectionStrings.Count) do
|
||||
AStrings.Add(sectionStrings.Names[keyIndex]);
|
||||
end;
|
||||
finally
|
||||
AStrings.EndUpdate;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.ReadSections(AStrings: TStrings);
|
||||
begin
|
||||
AStrings.Assign(Sections);
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.ReadSectionValues(const ASection: string; AStrings: TStrings);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
|
||||
begin
|
||||
AStrings.BeginUpdate;
|
||||
try
|
||||
AStrings.Clear;
|
||||
|
||||
sectionIndex := Sections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
AStrings.Assign(TStrings(Sections.Objects[sectionIndex]));
|
||||
finally
|
||||
AStrings.EndUpdate;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.UpdateFile;
|
||||
var
|
||||
list: TStringList;
|
||||
|
||||
begin
|
||||
list := TStringList.Create;
|
||||
try
|
||||
GetStrings(list);
|
||||
list.SaveToFile(FileName, FEncoding);
|
||||
finally
|
||||
FreeAndNil(list);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.WriteString(const ASection, AIdent, AValue: string);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
keyIndex: Integer;
|
||||
value: string;
|
||||
strings: TStrings;
|
||||
|
||||
begin
|
||||
sectionIndex := Sections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
strings := TStrings(Sections.Objects[sectionIndex])
|
||||
else
|
||||
strings := AddSection(ASection);
|
||||
|
||||
value := AIdent + '=' + AValue;
|
||||
keyIndex := strings.IndexOfName(AIdent);
|
||||
|
||||
if keyIndex > -1 then
|
||||
strings[keyIndex] := value
|
||||
else
|
||||
strings.Add(value);
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.GetStrings(AList: TStrings);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
strings: TStrings;
|
||||
value: string;
|
||||
|
||||
begin
|
||||
AList.BeginUpdate;
|
||||
try
|
||||
for sectionIndex := 0 to Pred(Sections.Count) do
|
||||
begin
|
||||
AList.Add('[' + Sections[sectionIndex] + ']');
|
||||
|
||||
strings := TStrings(Sections.Objects[sectionIndex]);
|
||||
for value in strings do
|
||||
AList.Add(value);
|
||||
|
||||
AList.Add('');
|
||||
end;
|
||||
finally
|
||||
AList.EndUpdate;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.SetStrings(AList: TStrings);
|
||||
var
|
||||
lineIndex: Integer;
|
||||
line: string;
|
||||
separatorPos: Integer;
|
||||
strings: TStrings;
|
||||
|
||||
begin
|
||||
Clear;
|
||||
strings := nil;
|
||||
|
||||
for lineIndex := 0 to Pred(AList.Count) do
|
||||
begin
|
||||
line := Trim(AList[lineIndex]);
|
||||
|
||||
if (Length(line) > 0) and (line[1] <> ';') then
|
||||
begin
|
||||
if (line[1] = '[') and (line[Length(line)] = ']') then
|
||||
begin
|
||||
Delete(line, 1, 1);
|
||||
SetLength(line, Length(line) - 1);
|
||||
|
||||
strings := AddSection(Trim(line));
|
||||
end
|
||||
else
|
||||
if Assigned(strings) then
|
||||
begin
|
||||
separatorPos := Pos('=', line);
|
||||
if separatorPos > 0 then
|
||||
strings.Add(Trim(Copy(line, 1, Pred(separatorPos))) + '=' + Trim(Copy(line, Succ(separatorPos), MaxInt)))
|
||||
else
|
||||
strings.Add(line);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.ReadDuplicateStrings(const ASection, AIdent: string; AList: TStrings);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
keyIndex: Integer;
|
||||
Strings: TStrings;
|
||||
|
||||
begin
|
||||
sectionIndex := FSections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
begin
|
||||
strings := TStrings(FSections.Objects[sectionIndex]);
|
||||
|
||||
for keyIndex := 0 to Pred(strings.Count) do
|
||||
begin
|
||||
if (CaseSensitive and (strings.Names[keyIndex] = AIdent)) or
|
||||
((not CaseSensitive) and SameText(strings.Names[keyIndex], AIdent)) then
|
||||
begin
|
||||
AList.Add(strings.ValueFromIndex[keyIndex]);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.WriteDuplicateString(const ASection, AIdent, AValue: string);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
strings: TStrings;
|
||||
|
||||
begin
|
||||
sectionIndex := Sections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
strings := TStrings(Sections.Objects[sectionIndex])
|
||||
else
|
||||
strings := AddSection(ASection);
|
||||
|
||||
strings.Add(AIdent + '=' + AValue);
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.DeleteDuplicateKeys(const ASection, AIdent: string);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
keyIndex: Integer;
|
||||
Strings: TStrings;
|
||||
|
||||
begin
|
||||
sectionIndex := Sections.IndexOf(ASection);
|
||||
if sectionIndex > -1 then
|
||||
begin
|
||||
strings := TStrings(Sections.Objects[sectionIndex]);
|
||||
|
||||
for keyIndex := Pred(strings.Count) downto 0 do
|
||||
begin
|
||||
if (CaseSensitive and (strings.Names[keyIndex] = AIdent)) or
|
||||
((not CaseSensitive) and SameText(strings.Names[keyIndex], AIdent)) then
|
||||
begin
|
||||
strings.Delete(keyIndex)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TUDKIniFile.AddSection(const ASection: string): TStrings;
|
||||
begin
|
||||
Result := THashedStringList.Create;
|
||||
try
|
||||
THashedStringList(Result).CaseSensitive := CaseSensitive;
|
||||
FSections.AddObject(ASection, Result);
|
||||
except
|
||||
FreeAndNil(Result);
|
||||
raise;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.LoadValues;
|
||||
var
|
||||
size: Integer;
|
||||
buffer: TBytes;
|
||||
list: TStringList;
|
||||
stream: TFileStream;
|
||||
|
||||
begin
|
||||
if (Length(FileName) > 0) and FileExists(FileName) then
|
||||
begin
|
||||
stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
|
||||
try
|
||||
{ Load file into buffer and detect encoding }
|
||||
size := stream.Size - stream.Position;
|
||||
SetLength(buffer, size);
|
||||
stream.Read(buffer[0], size);
|
||||
|
||||
size := TEncoding.GetBufferEncoding(buffer, FEncoding);
|
||||
|
||||
{ Load strings from buffer }
|
||||
list := TStringList.Create;
|
||||
try
|
||||
list.Text := FEncoding.GetString(buffer, size, Length(buffer) - size);
|
||||
SetStrings(list);
|
||||
finally
|
||||
FreeAndNil(list);
|
||||
end;
|
||||
finally
|
||||
FreeAndNil(stream);
|
||||
end;
|
||||
end else
|
||||
Clear;
|
||||
end;
|
||||
|
||||
|
||||
function TUDKIniFile.GetCaseSensitive: Boolean;
|
||||
begin
|
||||
Result := Sections.CaseSensitive;
|
||||
end;
|
||||
|
||||
|
||||
procedure TUDKIniFile.SetCaseSensitive(Value: Boolean);
|
||||
var
|
||||
sectionIndex: Integer;
|
||||
sectionList: TUDKHashedStringList;
|
||||
|
||||
begin
|
||||
if Value <> Sections.CaseSensitive then
|
||||
begin
|
||||
Sections.CaseSensitive := Value;
|
||||
|
||||
for sectionIndex := 0 to Pred(Sections.Count) do
|
||||
begin
|
||||
sectionList := TUDKHashedStringList(Sections.Objects[sectionIndex]);
|
||||
sectionList.CaseSensitive := Value;
|
||||
sectionList.Changed;
|
||||
end;
|
||||
|
||||
TUDKHashedStringList(Sections).Changed;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
@ -10,8 +10,9 @@ type
|
||||
private
|
||||
FLocation: string;
|
||||
FLoaded: Boolean;
|
||||
FModified: Boolean;
|
||||
protected
|
||||
procedure Notify(const APropertyName: string); virtual;
|
||||
procedure PropertyChanged(const APropertyName: string); virtual;
|
||||
|
||||
procedure DoLoad; virtual; abstract;
|
||||
procedure DoSave; virtual; abstract;
|
||||
@ -26,6 +27,7 @@ type
|
||||
|
||||
property Loaded: Boolean read FLoaded;
|
||||
property Location: string read FLocation;
|
||||
property Modified: Boolean read FModified;
|
||||
end;
|
||||
|
||||
TCustomGameClass = class of TCustomGame;
|
||||
@ -53,20 +55,44 @@ end;
|
||||
|
||||
|
||||
procedure TCustomGame.Load;
|
||||
var
|
||||
wasModified: Boolean;
|
||||
|
||||
begin
|
||||
wasModified := FModified;
|
||||
|
||||
DoLoad;
|
||||
FLoaded := True;
|
||||
|
||||
FModified := False;
|
||||
if wasModified then
|
||||
TBindings.Notify(Self, 'Modified');
|
||||
end;
|
||||
|
||||
|
||||
procedure TCustomGame.Save;
|
||||
var
|
||||
wasModified: Boolean;
|
||||
|
||||
begin
|
||||
wasModified := FModified;
|
||||
|
||||
DoSave;
|
||||
|
||||
FModified := False;
|
||||
if wasModified then
|
||||
TBindings.Notify(Self, 'Modified');
|
||||
end;
|
||||
|
||||
|
||||
procedure TCustomGame.Notify(const APropertyName: string);
|
||||
procedure TCustomGame.PropertyChanged(const APropertyName: string);
|
||||
begin
|
||||
if not FModified then
|
||||
begin
|
||||
FModified := True;
|
||||
TBindings.Notify(Self, 'Modified');
|
||||
end;
|
||||
|
||||
TBindings.Notify(Self, APropertyName);
|
||||
end;
|
||||
|
||||
|
@ -15,7 +15,7 @@ type
|
||||
in the base Chivalry class. }
|
||||
TChivalryMedievalWarfareGame = class(TChivalryGame)
|
||||
protected
|
||||
procedure LoadSupportedMapList(AList: TList<TGameMap>); override;
|
||||
procedure LoadPredefinedMapList(AList: TList<TGameMap>); override;
|
||||
public
|
||||
class function GameName: string; override;
|
||||
class function AutoDetect: TCustomGame; override;
|
||||
@ -81,7 +81,7 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TChivalryMedievalWarfareGame.LoadSupportedMapList(AList: TList<TGameMap>);
|
||||
procedure TChivalryMedievalWarfareGame.LoadPredefinedMapList(AList: TList<TGameMap>);
|
||||
var
|
||||
mapListFileName: string;
|
||||
mapList: TMemIniFile;
|
||||
|
@ -18,13 +18,13 @@ type
|
||||
FServerName: string;
|
||||
FMessageOfTheDay: string;
|
||||
|
||||
FSupportedMapList: TObjectList<TGameMap>;
|
||||
FPredefinedMapList: TObjectList<TGameMap>;
|
||||
FMapList: TObjectList<TGameMap>;
|
||||
protected
|
||||
procedure DoLoad; override;
|
||||
procedure DoSave; override;
|
||||
|
||||
procedure LoadSupportedMapList(AList: TList<TGameMap>); virtual; abstract;
|
||||
procedure LoadPredefinedMapList(AList: TList<TGameMap>); virtual; abstract;
|
||||
|
||||
function CreateGameMap(const AMapName: string): TGameMap; virtual;
|
||||
public
|
||||
@ -55,8 +55,22 @@ type
|
||||
property MessageOfTheDay: string read GetMessageOfTheDay write SetMessageOfTheDay;
|
||||
|
||||
{ IGameMapList }
|
||||
function GetSupportedMapList: TList<TGameMap>;
|
||||
function GetMapList: TList<TGameMap>;
|
||||
function GetPredefinedMapList: TEnumerable<TGameMap>;
|
||||
function GetMapList: TEnumerable<TGameMap>;
|
||||
|
||||
function GetMapCount: Integer;
|
||||
function GetMap(Index: Integer): TGameMap;
|
||||
|
||||
procedure AddMap(AMap: TGameMap);
|
||||
procedure InsertMap(Index: Integer; AMap: TGameMap);
|
||||
procedure DeleteMap(AIndex: Integer);
|
||||
procedure RemoveMap(AMap: TGameMap);
|
||||
procedure MoveMap(ASourceIndex, ATargetIndex: Integer);
|
||||
|
||||
property PredefinedMapList: TEnumerable<TGameMap> read GetPredefinedMapList;
|
||||
property MapList: TEnumerable<TGameMap> read GetMapList;
|
||||
property MapCount: Integer read GetMapCount;
|
||||
property Map[Index: Integer]: TGameMap read GetMap;
|
||||
end;
|
||||
|
||||
|
||||
@ -64,7 +78,9 @@ implementation
|
||||
uses
|
||||
System.Classes,
|
||||
System.IniFiles,
|
||||
System.SysUtils;
|
||||
System.SysUtils,
|
||||
|
||||
UDKIniFile;
|
||||
|
||||
|
||||
const
|
||||
@ -92,17 +108,17 @@ constructor TChivalryGame.Create(const ALocation: string);
|
||||
begin
|
||||
inherited Create(ALocation);
|
||||
|
||||
FSupportedMapList := TObjectList<TGameMap>.Create(True);
|
||||
FPredefinedMapList := TObjectList<TGameMap>.Create(True);
|
||||
FMapList := TObjectList<TGameMap>.Create(True);
|
||||
|
||||
LoadSupportedMapList(FSupportedMapList);
|
||||
LoadPredefinedMapList(FPredefinedMapList);
|
||||
end;
|
||||
|
||||
|
||||
destructor TChivalryGame.Destroy;
|
||||
begin
|
||||
FreeAndNil(FMapList);
|
||||
FreeAndNil(FSupportedMapList);
|
||||
FreeAndNil(FPredefinedMapList);
|
||||
|
||||
inherited Destroy;
|
||||
end;
|
||||
@ -110,40 +126,45 @@ end;
|
||||
|
||||
procedure TChivalryGame.DoLoad;
|
||||
var
|
||||
gameSettings: TMemIniFile;
|
||||
engineSettings: TMemIniFile;
|
||||
gameSettings: TUDKIniFile;
|
||||
engineSettings: TUDKIniFile;
|
||||
mapListValues: TStringList;
|
||||
valueIndex: Integer;
|
||||
mapName: string;
|
||||
mapListChanged: Boolean;
|
||||
|
||||
begin
|
||||
gameSettings := TMemIniFile.Create(Location + GameSettingsFileName);
|
||||
gameSettings := TUDKIniFile.Create(Location + GameSettingsFileName);
|
||||
try
|
||||
FServerPort := gameSettings.ReadInteger(GameURL, GameURLPort, GameURLPortDefault);
|
||||
FPeerPort := gameSettings.ReadInteger(GameURL, GameURLPeerPort, GameURLPeerPortDefault);
|
||||
SetServerPort(gameSettings.ReadInteger(GameURL, GameURLPort, GameURLPortDefault));
|
||||
SetPeerPort(gameSettings.ReadInteger(GameURL, GameURLPeerPort, GameURLPeerPortDefault));
|
||||
|
||||
FServerName := gameSettings.ReadString(GameReplicationInfo, GameReplicationInfoServerName, '');
|
||||
FMessageOfTheDay := gameSettings.ReadString(GameReplicationInfo, GameReplicationInfoMessageOfTheDay, '');
|
||||
SetServerName(gameSettings.ReadString(GameReplicationInfo, GameReplicationInfoServerName, ''));
|
||||
SetMessageOfTheDay(gameSettings.ReadString(GameReplicationInfo, GameReplicationInfoMessageOfTheDay, ''));
|
||||
|
||||
mapListChanged := (FMapList.Count > 0);
|
||||
|
||||
{ Maplist is special; it occurs multiple times and order matters }
|
||||
mapListValues := TStringList.Create;
|
||||
try
|
||||
gameSettings.ReadSectionValues(GameAOCGame, mapListValues);
|
||||
|
||||
for valueIndex := 0 to Pred(mapListValues.Count) do
|
||||
gameSettings.ReadDuplicateStrings(GameAOCGame, GameAOCGameMapList, mapListValues);
|
||||
for mapName in mapListValues do
|
||||
begin
|
||||
if SameText(mapListValues.Names[valueIndex], GameAOCGameMapList) then
|
||||
FMapList.Add(CreateGameMap(mapListValues.ValueFromIndex[valueIndex]));
|
||||
FMapList.Add(CreateGameMap(mapName));
|
||||
mapListChanged := True;
|
||||
end;
|
||||
finally
|
||||
FreeAndNil(mapListValues);
|
||||
end;
|
||||
|
||||
if mapListChanged then
|
||||
PropertyChanged('MapList');
|
||||
finally
|
||||
FreeAndNil(gameSettings);
|
||||
end;
|
||||
|
||||
engineSettings := TMemIniFile.Create(Location + EngineSettingsFileName);
|
||||
engineSettings := TUDKIniFile.Create(Location + EngineSettingsFileName);
|
||||
try
|
||||
FQueryPort := engineSettings.ReadInteger(EngineSteam, EngineSteamQueryPort, EngineSteamQueryPortDefault);
|
||||
SetQueryPort(engineSettings.ReadInteger(EngineSteam, EngineSteamQueryPort, EngineSteamQueryPortDefault));
|
||||
finally
|
||||
FreeAndNil(engineSettings);
|
||||
end;
|
||||
@ -151,8 +172,39 @@ end;
|
||||
|
||||
|
||||
procedure TChivalryGame.DoSave;
|
||||
var
|
||||
gameSettings: TUDKIniFile;
|
||||
engineSettings: TUDKIniFile;
|
||||
map: TGameMap;
|
||||
|
||||
begin
|
||||
// #ToDo1 -oMvR: 30-6-2014: save to INI files
|
||||
gameSettings := TUDKIniFile.Create(Location + GameSettingsFileName);
|
||||
try
|
||||
gameSettings.WriteInteger(GameURL, GameURLPort, ServerPort);
|
||||
gameSettings.WriteInteger(GameURL, GameURLPeerPort, PeerPort);
|
||||
|
||||
gameSettings.WriteString(GameReplicationInfo, GameReplicationInfoServerName, ServerName);
|
||||
gameSettings.WriteString(GameReplicationInfo, GameReplicationInfoMessageOfTheDay, MessageOfTheDay);
|
||||
|
||||
{ Remove all Maplist references before rewriting the list }
|
||||
gameSettings.DeleteDuplicateKeys(GameAOCGame, GameAOCGameMapList);
|
||||
|
||||
for map in MapList do
|
||||
gameSettings.WriteDuplicateString(GameAOCGame, GameAOCGameMapList, map.Name);
|
||||
|
||||
gameSettings.UpdateFile;
|
||||
finally
|
||||
FreeAndNil(gameSettings);
|
||||
end;
|
||||
|
||||
engineSettings := TUDKIniFile.Create(Location + EngineSettingsFileName);
|
||||
try
|
||||
engineSettings.WriteInteger(EngineSteam, EngineSteamQueryPort, QueryPort);
|
||||
|
||||
engineSettings.UpdateFile;
|
||||
finally
|
||||
FreeAndNil(engineSettings);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
@ -163,7 +215,7 @@ var
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
for map in GetSupportedMapList do
|
||||
for map in PredefinedMapList do
|
||||
if SameText(map.Name, AMapName) then
|
||||
begin
|
||||
Result := TGameMap.Create(map);
|
||||
@ -199,7 +251,7 @@ begin
|
||||
if Value <> FServerPort then
|
||||
begin
|
||||
FServerPort := Value;
|
||||
Notify('ServerPort');
|
||||
PropertyChanged('ServerPort');
|
||||
end;
|
||||
end;
|
||||
|
||||
@ -209,7 +261,7 @@ begin
|
||||
if Value <> FPeerPort then
|
||||
begin
|
||||
FPeerPort := Value;
|
||||
Notify('PeerPort');
|
||||
PropertyChanged('PeerPort');
|
||||
end;
|
||||
end;
|
||||
|
||||
@ -219,7 +271,7 @@ begin
|
||||
if Value <> FQueryPort then
|
||||
begin
|
||||
FQueryPort := Value;
|
||||
Notify('QueryPort');
|
||||
PropertyChanged('QueryPort');
|
||||
end;
|
||||
end;
|
||||
|
||||
@ -241,7 +293,7 @@ begin
|
||||
if Value <> FServerName then
|
||||
begin
|
||||
FServerName := Value;
|
||||
Notify('ServerName');
|
||||
PropertyChanged('ServerName');
|
||||
end;
|
||||
end;
|
||||
|
||||
@ -251,20 +303,70 @@ begin
|
||||
if Value <> FMessageOfTheDay then
|
||||
begin
|
||||
FMessageOfTheDay := Value;
|
||||
Notify('MessageOfTheDay');
|
||||
PropertyChanged('MessageOfTheDay');
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TChivalryGame.GetMapList: TList<TGameMap>;
|
||||
function TChivalryGame.GetPredefinedMapList: TEnumerable<TGameMap>;
|
||||
begin
|
||||
Result := FPredefinedMapList;
|
||||
end;
|
||||
|
||||
|
||||
function TChivalryGame.GetMapList: TEnumerable<TGameMap>;
|
||||
begin
|
||||
Result := FMapList;
|
||||
end;
|
||||
|
||||
|
||||
function TChivalryGame.GetSupportedMapList: TList<TGameMap>;
|
||||
function TChivalryGame.GetMapCount: Integer;
|
||||
begin
|
||||
Result := FSupportedMapList;
|
||||
Result := FMapList.Count;
|
||||
end;
|
||||
|
||||
|
||||
function TChivalryGame.GetMap(Index: Integer): TGameMap;
|
||||
begin
|
||||
Result := FMapList[Index];
|
||||
end;
|
||||
|
||||
|
||||
procedure TChivalryGame.AddMap(AMap: TGameMap);
|
||||
begin
|
||||
FMapList.Add(AMap);
|
||||
PropertyChanged('MapList');
|
||||
end;
|
||||
|
||||
|
||||
procedure TChivalryGame.InsertMap(Index: Integer; AMap: TGameMap);
|
||||
begin
|
||||
FMapList.Insert(Index, AMap);
|
||||
PropertyChanged('MapList');
|
||||
end;
|
||||
|
||||
|
||||
procedure TChivalryGame.DeleteMap(AIndex: Integer);
|
||||
begin
|
||||
FMapList.Delete(AIndex);
|
||||
PropertyChanged('MapList');
|
||||
end;
|
||||
|
||||
|
||||
procedure TChivalryGame.RemoveMap(AMap: TGameMap);
|
||||
begin
|
||||
if FMapList.Remove(AMap) > -1 then
|
||||
PropertyChanged('MapList');
|
||||
end;
|
||||
|
||||
|
||||
procedure TChivalryGame.MoveMap(ASourceIndex, ATargetIndex: Integer);
|
||||
begin
|
||||
if ATargetIndex <> ASourceIndex then
|
||||
begin
|
||||
FMapList.Move(ASourceIndex, ATargetIndex);
|
||||
PropertyChanged('MapList');
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
@ -54,8 +54,23 @@ type
|
||||
|
||||
IGameMapList = interface
|
||||
['{E8552B4C-9447-4FAD-BB20-C5EB3AF07B0E}']
|
||||
function GetSupportedMapList: TList<TGameMap>;
|
||||
function GetMapList: TList<TGameMap>;
|
||||
function GetPredefinedMapList: TEnumerable<TGameMap>;
|
||||
function GetMapList: TEnumerable<TGameMap>;
|
||||
|
||||
function GetMapCount: Integer;
|
||||
function GetMap(Index: Integer): TGameMap;
|
||||
|
||||
procedure AddMap(AMap: TGameMap);
|
||||
procedure InsertMap(Index: Integer; AMap: TGameMap);
|
||||
procedure DeleteMap(AIndex: Integer);
|
||||
procedure RemoveMap(AMap: TGameMap);
|
||||
procedure MoveMap(ASourceIndex, ATargetIndex: Integer);
|
||||
|
||||
property PredefinedMapList: TEnumerable<TGameMap> read GetPredefinedMapList;
|
||||
property MapList: TEnumerable<TGameMap> read GetMapList;
|
||||
|
||||
property MapCount: Integer read GetMapCount;
|
||||
property Map[Index: Integer]: TGameMap read GetMap;
|
||||
end;
|
||||
|
||||
|
||||
|
@ -15,6 +15,7 @@ object MainForm: TMainForm
|
||||
OldCreateOrder = False
|
||||
Position = poScreenCenter
|
||||
ShowHint = True
|
||||
OnCloseQuery = FormCloseQuery
|
||||
OnCreate = FormCreate
|
||||
OnDestroy = FormDestroy
|
||||
PixelsPerInch = 96
|
||||
@ -65,7 +66,7 @@ object MainForm: TMainForm
|
||||
Top = 76
|
||||
Width = 565
|
||||
Height = 428
|
||||
ActivePage = tsMapList
|
||||
ActivePage = tsAbout
|
||||
Align = alClient
|
||||
Style = tsButtons
|
||||
TabOrder = 2
|
||||
@ -175,7 +176,6 @@ object MainForm: TMainForm
|
||||
Align = alRight
|
||||
BevelOuter = bvNone
|
||||
TabOrder = 2
|
||||
ExplicitLeft = 415
|
||||
inline frmMapPreview: TMapPreviewFrame
|
||||
Left = 0
|
||||
Top = 0
|
||||
@ -183,7 +183,6 @@ object MainForm: TMainForm
|
||||
Height = 130
|
||||
Align = alTop
|
||||
TabOrder = 0
|
||||
ExplicitTop = 216
|
||||
end
|
||||
object btnMapListSave: TButton
|
||||
AlignWithMargins = True
|
||||
@ -198,9 +197,6 @@ object MainForm: TMainForm
|
||||
Action = actMapListSave
|
||||
Align = alTop
|
||||
TabOrder = 2
|
||||
ExplicitLeft = 28
|
||||
ExplicitTop = 168
|
||||
ExplicitWidth = 75
|
||||
end
|
||||
object btnMapListLoad: TButton
|
||||
AlignWithMargins = True
|
||||
@ -215,9 +211,6 @@ object MainForm: TMainForm
|
||||
Action = actMapListLoad
|
||||
Align = alTop
|
||||
TabOrder = 1
|
||||
ExplicitLeft = 28
|
||||
ExplicitTop = 168
|
||||
ExplicitWidth = 75
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -600,135 +593,167 @@ object MainForm: TMainForm
|
||||
ImageIndex = 4
|
||||
object lblJCL: TLabel
|
||||
Left = 12
|
||||
Top = 222
|
||||
Top = 262
|
||||
Width = 319
|
||||
Height = 13
|
||||
Caption = 'Uses JEDI Code Library (JCL) and Visual Component Library (JVCL)'
|
||||
end
|
||||
object lblVirtualTreeview: TLabel
|
||||
Left = 12
|
||||
Top = 334
|
||||
Top = 358
|
||||
Width = 179
|
||||
Height = 13
|
||||
Caption = 'Uses Virtual Treeview by Mike Lischke'
|
||||
end
|
||||
object lblChivalry: TLabel
|
||||
Left = 12
|
||||
Top = 16
|
||||
Top = 80
|
||||
Width = 246
|
||||
Height = 13
|
||||
Caption = 'Chivalry: Medieval Warfare by Torn Banner Studios'
|
||||
end
|
||||
object lblPixelophilia: TLabel
|
||||
Left = 12
|
||||
Top = 72
|
||||
Top = 128
|
||||
Width = 293
|
||||
Height = 13
|
||||
Caption = 'Various icons are part of the Pixelophilia packs by '#214'mer '#199'etin'
|
||||
end
|
||||
object lblGentleface: TLabel
|
||||
Left = 12
|
||||
Top = 147
|
||||
Top = 195
|
||||
Width = 217
|
||||
Height = 13
|
||||
Caption = 'Toolbar icons are part of the Gentleface pack'
|
||||
end
|
||||
object lblSuperObject: TLabel
|
||||
Left = 12
|
||||
Top = 278
|
||||
Top = 310
|
||||
Width = 176
|
||||
Height = 13
|
||||
Caption = 'Uses SuperObject by Henri Gourvest'
|
||||
end
|
||||
object lblProductName: TLabel
|
||||
Left = 12
|
||||
Top = 12
|
||||
Width = 148
|
||||
Height = 13
|
||||
Caption = '<runtime: product name>'
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = [fsBold]
|
||||
ParentFont = False
|
||||
end
|
||||
object lblCopyright: TLabel
|
||||
Left = 12
|
||||
Top = 27
|
||||
Width = 104
|
||||
Height = 13
|
||||
Caption = '<runtime: copyright>'
|
||||
end
|
||||
object llJCL: TLinkLabel
|
||||
Left = 12
|
||||
Top = 241
|
||||
Top = 277
|
||||
Width = 101
|
||||
Height = 17
|
||||
Caption = '<a href="http://www.delphi-jedi.org/">www.delphi-jedi.org</a>'
|
||||
TabOrder = 5
|
||||
TabOrder = 6
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
object llVirtualTreeview: TLinkLabel
|
||||
Left = 12
|
||||
Top = 353
|
||||
Top = 373
|
||||
Width = 274
|
||||
Height = 17
|
||||
Caption =
|
||||
'<a href="http://www.soft-gems.net/index.php/controls/virtual-tre' +
|
||||
'eview">www.soft-gems.net/index.php/controls/virtual-treeview</a>'
|
||||
TabOrder = 7
|
||||
TabOrder = 8
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
object llChivalry: TLinkLabel
|
||||
Left = 12
|
||||
Top = 35
|
||||
Top = 95
|
||||
Width = 109
|
||||
Height = 17
|
||||
Caption = '<a href="http://www.tornbanner.com/">www.tornbanner.com</a>'
|
||||
TabOrder = 0
|
||||
TabOrder = 1
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
object llPixelophilia: TLinkLabel
|
||||
Left = 12
|
||||
Top = 91
|
||||
Top = 143
|
||||
Width = 128
|
||||
Height = 17
|
||||
Caption =
|
||||
'<a href="http://omercetin.deviantart.com/">omercetin.deviantart.' +
|
||||
'com</a>'
|
||||
TabOrder = 1
|
||||
TabOrder = 2
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
object llGentleface: TLinkLabel
|
||||
Left = 12
|
||||
Top = 166
|
||||
Top = 210
|
||||
Width = 172
|
||||
Height = 17
|
||||
Caption =
|
||||
'<a href="http://gentleface.com/free_icon_set.html">gentleface.co' +
|
||||
'm/free_icon_set.html</a>'
|
||||
TabOrder = 3
|
||||
TabOrder = 4
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
object llPixelophiliaCC: TLinkLabel
|
||||
Left = 12
|
||||
Top = 110
|
||||
Top = 158
|
||||
Width = 303
|
||||
Height = 17
|
||||
Caption =
|
||||
'<a href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Crea' +
|
||||
'tive Commons Attribution-Noncommercial-No Derivate 3.0</a>'
|
||||
TabOrder = 2
|
||||
TabOrder = 3
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
object llGentlefaceCC: TLinkLabel
|
||||
Left = 12
|
||||
Top = 185
|
||||
Top = 225
|
||||
Width = 242
|
||||
Height = 17
|
||||
Caption =
|
||||
'<a href="http://creativecommons.org/licenses/by-nc/3.0/">Creativ' +
|
||||
'e Commons Attribution-Noncommercial 3.0</a>'
|
||||
TabOrder = 4
|
||||
TabOrder = 5
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
object llSuperObject: TLinkLabel
|
||||
Left = 12
|
||||
Top = 297
|
||||
Top = 325
|
||||
Width = 157
|
||||
Height = 17
|
||||
Caption =
|
||||
'<a href="https://code.google.com/p/superobject/">code.google.com' +
|
||||
'/p/superobject</a>'
|
||||
TabOrder = 6
|
||||
TabOrder = 7
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
object llWebsite: TLinkLabel
|
||||
Left = 12
|
||||
Top = 43
|
||||
Width = 213
|
||||
Height = 17
|
||||
Caption =
|
||||
'<a href="http://wiki.x2software.net/chivalryserverlauncher">wiki' +
|
||||
'.x2software.net/chivalryserverlauncher</a>'
|
||||
TabOrder = 0
|
||||
TabStop = True
|
||||
OnLinkClick = llLinkClick
|
||||
end
|
||||
@ -1689,7 +1714,7 @@ object MainForm: TMainForm
|
||||
end
|
||||
object btnSave: TButton
|
||||
AlignWithMargins = True
|
||||
Left = 533
|
||||
Left = 434
|
||||
Top = 0
|
||||
Width = 91
|
||||
Height = 25
|
||||
@ -1701,6 +1726,20 @@ object MainForm: TMainForm
|
||||
Align = alRight
|
||||
TabOrder = 2
|
||||
end
|
||||
object btnRevert: TButton
|
||||
AlignWithMargins = True
|
||||
Left = 533
|
||||
Top = 0
|
||||
Width = 91
|
||||
Height = 25
|
||||
Margins.Left = 0
|
||||
Margins.Top = 0
|
||||
Margins.Right = 8
|
||||
Margins.Bottom = 0
|
||||
Action = actRevert
|
||||
Align = alRight
|
||||
TabOrder = 3
|
||||
end
|
||||
end
|
||||
object mbpMenuPainter: TX2MenuBarmusikCubePainter
|
||||
Left = 44
|
||||
@ -2469,11 +2508,16 @@ object MainForm: TMainForm
|
||||
end
|
||||
object actSave: TAction
|
||||
Caption = '&Save changes'
|
||||
OnExecute = actSaveExecute
|
||||
end
|
||||
object actClose: TAction
|
||||
Caption = '&Close'
|
||||
OnExecute = actCloseExecute
|
||||
end
|
||||
object actRevert: TAction
|
||||
Caption = '&Revert changes'
|
||||
OnExecute = actRevertExecute
|
||||
end
|
||||
end
|
||||
object pmnLaunch: TPopupMenu
|
||||
Left = 144
|
||||
|
@ -134,9 +134,15 @@ type
|
||||
btnMapListLoad: TButton;
|
||||
actMapListLoad: TAction;
|
||||
actMapListSave: TAction;
|
||||
btnRevert: TButton;
|
||||
actRevert: TAction;
|
||||
llWebsite: TLinkLabel;
|
||||
lblProductName: TLabel;
|
||||
lblCopyright: TLabel;
|
||||
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure FormDestroy(Sender: TObject);
|
||||
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
||||
procedure mbMenuCollapsing(Sender: TObject; Group: TX2MenuBarGroup; var Allowed: Boolean);
|
||||
procedure mbMenuSelectedChanged(Sender: TObject; Item: TX2CustomMenuBarItem);
|
||||
procedure llLinkClick(Sender: TObject; const Link: string; LinkType: TSysLinkType);
|
||||
@ -159,6 +165,8 @@ type
|
||||
procedure actMapRemoveExecute(Sender: TObject);
|
||||
procedure actMapUpExecute(Sender: TObject);
|
||||
procedure actMapDownExecute(Sender: TObject);
|
||||
procedure actSaveExecute(Sender: TObject);
|
||||
procedure actRevertExecute(Sender: TObject);
|
||||
private type
|
||||
TBindingExpressionList = TList<TBindingExpression>;
|
||||
TPageMenuDictionary = TDictionary<TTabSheet, TX2MenuBarItem>;
|
||||
@ -167,10 +175,14 @@ type
|
||||
FPageMenuMap: TPageMenuDictionary;
|
||||
FUIBindings: TBindingExpressionList;
|
||||
|
||||
function GetModified: Boolean;
|
||||
procedure SetActiveGame(const Value: TCustomGame);
|
||||
procedure SetModified(const Value: Boolean);
|
||||
protected
|
||||
procedure EnablePageActions;
|
||||
procedure ActiveGameChanged;
|
||||
procedure SaveActiveGame;
|
||||
procedure RevertActiveGame;
|
||||
|
||||
procedure ClearUIBindings;
|
||||
procedure Bind(const APropertyName: string; ADestObject: TObject; const ADestPropertyName: string);
|
||||
@ -181,6 +193,8 @@ type
|
||||
procedure UpdateGameList;
|
||||
procedure UpdateMapList;
|
||||
|
||||
procedure SaveGameList;
|
||||
|
||||
function FindMapNode(AMap: TGameMap): PVirtualNode;
|
||||
|
||||
procedure HandleMapSelection(ANodes: TNodeArray; ATargetIndex: Integer; ACopy: Boolean);
|
||||
@ -190,6 +204,8 @@ type
|
||||
property ActiveGame: TCustomGame read FActiveGame write SetActiveGame;
|
||||
property PageMenuMap: TPageMenuDictionary read FPageMenuMap;
|
||||
property UIBindings: TBindingExpressionList read FUIBindings;
|
||||
public
|
||||
property Modified: Boolean read GetModified write SetModified;
|
||||
end;
|
||||
|
||||
|
||||
@ -204,6 +220,7 @@ uses
|
||||
Winapi.ShellAPI,
|
||||
Winapi.Windows,
|
||||
|
||||
X2UtApp,
|
||||
X2UtGraphics,
|
||||
|
||||
Forms.Game,
|
||||
@ -280,6 +297,9 @@ begin
|
||||
lightBtnFace := BlendColors(clBtnFace, clWindow, 196);
|
||||
pnlGamesWarning.Color := lightBtnFace;
|
||||
|
||||
lblProductName.Caption := App.Version.FormatVersion(False, True);
|
||||
lblCopyright.Caption := App.Version.Strings.LegalCopyright;
|
||||
|
||||
|
||||
{ Load games }
|
||||
userGamesFileName := Resources.GetUserDataPath(Resources.UserGamesFileName);
|
||||
@ -310,6 +330,13 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
||||
begin
|
||||
if Modified then
|
||||
CanClose := (MessageBox(Self.Handle, 'Your changes will not be saved. Do you want to exit?', 'Close', MB_YESNO or MB_ICONQUESTION) = ID_YES);
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.EnablePageActions;
|
||||
const
|
||||
ActionListState: array[Boolean] of TActionListState = (asSuspended, asNormal);
|
||||
@ -329,7 +356,14 @@ begin
|
||||
if Assigned(ActiveGame) and (not ActiveGame.Loaded) then
|
||||
ActiveGame.Load;
|
||||
|
||||
// #ToDo1 -oMvR: 30-6-2014: attach observer to monitor changes
|
||||
{ Bind Modified property }
|
||||
UIBindings.Add(TBindings.CreateManagedBinding(
|
||||
[TBindings.CreateAssociationScope([Associate(ActiveGame, 'src')])],
|
||||
'src.Modified',
|
||||
[TBindings.CreateAssociationScope([Associate(Self, 'dst')])],
|
||||
'dst.Modified',
|
||||
nil, nil, [coNotifyOutput, coEvaluate]));
|
||||
|
||||
|
||||
if Supports(ActiveGame, IGameNetwork) then
|
||||
BindGameNetwork;
|
||||
@ -344,6 +378,26 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.SaveActiveGame;
|
||||
begin
|
||||
if Assigned(ActiveGame) then
|
||||
ActiveGame.Save;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.RevertActiveGame;
|
||||
begin
|
||||
if Assigned(ActiveGame) then
|
||||
begin
|
||||
ActiveGame.Load;
|
||||
|
||||
// #ToDo2 -oMvR: 2-7-2014: This should be based on the observer pattern used by TBindings,
|
||||
// but I haven't figured out how that works yet. For now, manually refresh.
|
||||
UpdateMapList;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.ClearUIBindings;
|
||||
var
|
||||
binding: TBindingExpression;
|
||||
@ -425,12 +479,19 @@ var
|
||||
|
||||
begin
|
||||
if Supports(ActiveGame, IGameMapList, gameMapList) then
|
||||
vstMapList.RootNodeCount := gameMapList.GetMapList.Count
|
||||
vstMapList.RootNodeCount := gameMapList.MapCount
|
||||
else
|
||||
vstMapList.Clear;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.SaveGameList;
|
||||
begin
|
||||
TGameListPersist.Save(Resources.GetUserDataPath(Resources.UserGamesFileName), TGameList.Instance);
|
||||
UpdateGameList;
|
||||
end;
|
||||
|
||||
|
||||
function TMainForm.FindMapNode(AMap: TGameMap): PVirtualNode;
|
||||
var
|
||||
gameMapList: IGameMapList;
|
||||
@ -443,7 +504,7 @@ begin
|
||||
Result := vstMapList.IterateSubtree(nil,
|
||||
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
|
||||
begin
|
||||
Abort := (gameMapList.GetMapList[Node^.Index] = AMap);
|
||||
Abort := (gameMapList.Map[Node^.Index] = AMap);
|
||||
end,
|
||||
nil);
|
||||
end;
|
||||
@ -483,8 +544,8 @@ begin
|
||||
{ Copy map nodes }
|
||||
Inc(sourceIndex, sourceShift);
|
||||
|
||||
map := TGameMap.Create(gameMapList.GetMapList[sourceIndex]);
|
||||
gameMapList.GetMapList.Insert(targetIndex, map);
|
||||
map := TGameMap.Create(gameMapList.Map[sourceIndex]);
|
||||
gameMapList.InsertMap(targetIndex, map);
|
||||
selectedMaps.Add(map);
|
||||
|
||||
Inc(targetIndex);
|
||||
@ -507,8 +568,8 @@ begin
|
||||
Inc(targetIndex);
|
||||
end;
|
||||
|
||||
selectedMaps.Add(gameMapList.GetMapList[sourceIndex]);
|
||||
gameMapList.GetMapList.Move(sourceIndex, newIndex);
|
||||
selectedMaps.Add(gameMapList.Map[sourceIndex]);
|
||||
gameMapList.MoveMap(sourceIndex, newIndex);
|
||||
end;
|
||||
end;
|
||||
|
||||
@ -517,7 +578,7 @@ begin
|
||||
vstMapList.IterateSubtree(nil,
|
||||
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
|
||||
begin
|
||||
if selectedMaps.Contains(gameMapList.GetMapList[Node^.Index]) then
|
||||
if selectedMaps.Contains(gameMapList.Map[Node^.Index]) then
|
||||
Sender.Selected[Node] := True;
|
||||
end,
|
||||
nil);
|
||||
@ -542,6 +603,12 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
function TMainForm.GetModified: Boolean;
|
||||
begin
|
||||
Result := actSave.Enabled;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.SetActiveGame(const Value: TCustomGame);
|
||||
begin
|
||||
if Value <> FActiveGame then
|
||||
@ -552,6 +619,13 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.SetModified(const Value: Boolean);
|
||||
begin
|
||||
actSave.Enabled := Value;
|
||||
actRevert.Enabled := Value;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.mbMenuCollapsing(Sender: TObject; Group: TX2MenuBarGroup; var Allowed: Boolean);
|
||||
begin
|
||||
Allowed := False;
|
||||
@ -589,6 +663,21 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.actSaveExecute(Sender: TObject);
|
||||
begin
|
||||
SaveActiveGame;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.actRevertExecute(Sender: TObject);
|
||||
begin
|
||||
if MessageBox(Self.Handle, 'Do you want to revert your changes?', 'Revert', MB_YESNO or MB_ICONQUESTION) = ID_NO then
|
||||
exit;
|
||||
|
||||
RevertActiveGame;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.actGameAddExecute(Sender: TObject);
|
||||
var
|
||||
game: TCustomGame;
|
||||
@ -597,10 +686,7 @@ begin
|
||||
if TGameForm.Insert(Self, game) then
|
||||
begin
|
||||
TGameList.Instance.Add(game);
|
||||
|
||||
// #ToDo1 -oMvR: 30-6-2014: move to shared spot
|
||||
TGameListPersist.Save(Resources.GetUserDataPath(Resources.UserGamesFileName), TGameList.Instance);
|
||||
UpdateGameList;
|
||||
SaveGameList;
|
||||
|
||||
ActiveGame := game;
|
||||
end;
|
||||
@ -633,10 +719,7 @@ begin
|
||||
ActiveGame := nil;
|
||||
end;
|
||||
|
||||
// #ToDo1 -oMvR: 30-6-2014: move to shared spot
|
||||
TGameListPersist.Save(Resources.GetUserDataPath(Resources.UserGamesFileName), TGameList.Instance);
|
||||
|
||||
UpdateGameList;
|
||||
SaveGameList;
|
||||
finally
|
||||
vstGames.EndUpdate;
|
||||
end;
|
||||
@ -648,6 +731,22 @@ procedure TMainForm.actGameActiveExecute(Sender: TObject);
|
||||
begin
|
||||
if Assigned(vstGames.FocusedNode) then
|
||||
begin
|
||||
{ For the sake of clarity, always save or revert changes when switching }
|
||||
if Modified then
|
||||
begin
|
||||
case MessageBox(Self.Handle, 'Do you want to save your changes before switching the active game?', 'Set active game',
|
||||
MB_YESNOCANCEL or MB_ICONQUESTION) of
|
||||
ID_YES:
|
||||
SaveActiveGame;
|
||||
|
||||
ID_NO:
|
||||
RevertActiveGame;
|
||||
|
||||
ID_CANCEL:
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
vstGames.BeginUpdate;
|
||||
try
|
||||
ActiveGame := TGameList.Instance[vstGames.FocusedNode^.Index];
|
||||
@ -698,7 +797,7 @@ begin
|
||||
if not Supports(ActiveGame, IGameMapList, gameMapList) then
|
||||
exit;
|
||||
|
||||
map := gameMapList.GetMapList[Node^.Index];
|
||||
map := gameMapList.Map[Node^.Index];
|
||||
|
||||
case Column of
|
||||
MapColumnName:
|
||||
@ -782,7 +881,10 @@ begin
|
||||
if not Supports(ActiveGame, IGameMapList, gameMapList) then
|
||||
exit;
|
||||
|
||||
frmMapPreview.Load(gameMapList.GetMapList[Node^.Index].Name);
|
||||
if Assigned(Node) then
|
||||
frmMapPreview.Load(gameMapList.Map[Node^.Index].Name)
|
||||
else
|
||||
frmMapPreview.Clear;
|
||||
end;
|
||||
|
||||
|
||||
@ -798,7 +900,7 @@ begin
|
||||
|
||||
if TMapForm.Insert(Self, gameMapList, map) then
|
||||
begin
|
||||
gameMapList.GetMapList.Add(map);
|
||||
gameMapList.AddMap(map);
|
||||
UpdateMapList;
|
||||
|
||||
node := FindMapNode(map);
|
||||
@ -832,7 +934,7 @@ begin
|
||||
selectedNodes := vstMapList.GetSortedSelection(True);
|
||||
|
||||
for nodeIndex := High(selectedNodes) downto Low(selectedNodes) do
|
||||
gameMapList.GetMapList.Delete(selectedNodes[nodeIndex]^.Index);
|
||||
gameMapList.DeleteMap(selectedNodes[nodeIndex]^.Index);
|
||||
|
||||
UpdateMapList;
|
||||
finally
|
||||
|
@ -87,8 +87,6 @@ object MapForm: TMapForm
|
||||
OnFocusChanging = vstMapFocusChanging
|
||||
OnGetText = vstMapGetText
|
||||
OnPaintText = vstMapPaintText
|
||||
ExplicitLeft = 8
|
||||
ExplicitWidth = 561
|
||||
Columns = <>
|
||||
end
|
||||
object pnlMapName: TPanel
|
||||
@ -105,7 +103,6 @@ object MapForm: TMapForm
|
||||
AutoSize = True
|
||||
BevelOuter = bvNone
|
||||
TabOrder = 2
|
||||
ExplicitTop = 475
|
||||
DesignSize = (
|
||||
561
|
||||
21)
|
||||
@ -173,8 +170,6 @@ object MapForm: TMapForm
|
||||
Align = alLeft
|
||||
BevelOuter = bvNone
|
||||
TabOrder = 4
|
||||
ExplicitLeft = 0
|
||||
ExplicitHeight = 342
|
||||
inline frmMapPreview: TMapPreviewFrame
|
||||
AlignWithMargins = True
|
||||
Left = 0
|
||||
@ -187,7 +182,7 @@ object MapForm: TMapForm
|
||||
Margins.Bottom = 0
|
||||
Align = alTop
|
||||
TabOrder = 0
|
||||
ExplicitWidth = 342
|
||||
ExplicitTop = 8
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -47,7 +47,7 @@ type
|
||||
function GetMapName: string;
|
||||
procedure SetMapName(const Value: string);
|
||||
protected
|
||||
procedure LoadSupportedMapList(AGame: IGameMapList);
|
||||
procedure LoadPredefinedMapList(AGame: IGameMapList);
|
||||
function CreateMap: TGameMap;
|
||||
|
||||
function FindMapNode(const AMapName: string): PVirtualNode;
|
||||
@ -82,7 +82,7 @@ class function TMapForm.Insert(AOwner: TComponent; AGame: IGameMapList; out AMap
|
||||
begin
|
||||
with Self.Create(AOwner) do
|
||||
try
|
||||
LoadSupportedMapList(AGame);
|
||||
LoadPredefinedMapList(AGame);
|
||||
|
||||
Result := (ShowModal = mrOk);
|
||||
if Result then
|
||||
@ -99,7 +99,7 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TMapForm.LoadSupportedMapList(AGame: IGameMapList);
|
||||
procedure TMapForm.LoadPredefinedMapList(AGame: IGameMapList);
|
||||
var
|
||||
map: TGameMap;
|
||||
categoryNodes: TDictionary<string, PVirtualNode>;
|
||||
@ -113,7 +113,7 @@ begin
|
||||
|
||||
categoryNodes := TDictionary<string, PVirtualNode>.Create;
|
||||
try
|
||||
for map in AGame.GetSupportedMapList do
|
||||
for map in AGame.PredefinedMapList do
|
||||
begin
|
||||
if categoryNodes.ContainsKey(map.Category) then
|
||||
parentNode := categoryNodes[map.Category]
|
||||
@ -130,6 +130,7 @@ begin
|
||||
end;
|
||||
finally
|
||||
vstMap.FullExpand;
|
||||
vstMap.EndUpdate;
|
||||
|
||||
node := vstMap.GetFirstLevel(1);
|
||||
if Assigned(node) then
|
||||
@ -137,8 +138,6 @@ begin
|
||||
vstMap.FocusedNode := node;
|
||||
vstMap.Selected[node] := True;
|
||||
end;
|
||||
|
||||
vstMap.EndUpdate;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user