Added: tracking of Modified state

Added: saving / reverting changes
Fixed: access violation on preview with an empty map list
This commit is contained in:
Mark van Renswoude 2014-07-02 14:54:01 +00:00
parent 47129c0b32
commit 12eba2e832
12 changed files with 835 additions and 105 deletions

View File

@ -14,7 +14,8 @@ uses
Game.List in 'source\model\Game.List.pas', Game.List in 'source\model\Game.List.pas',
Persist.GameList in 'source\persist\Persist.GameList.pas', Persist.GameList in 'source\persist\Persist.GameList.pas',
Forms.Map in 'source\view\Forms.Map.pas' {MapForm}, 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} {$R *.res}

View File

@ -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_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_MinorVer>1</VerInfo_MinorVer>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1043</VerInfo_Locale> <VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_MajorVer>0</VerInfo_MajorVer> <VerInfo_MajorVer>0</VerInfo_MajorVer>
<Icon_MainIcon>resources\icons\MainIcon.ico</Icon_MainIcon> <Icon_MainIcon>resources\icons\MainIcon.ico</Icon_MainIcon>
<Manifest_File>None</Manifest_File> <Manifest_File>None</Manifest_File>
@ -69,6 +69,7 @@
<DCC_RemoteDebug>true</DCC_RemoteDebug> <DCC_RemoteDebug>true</DCC_RemoteDebug>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''"> <PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<DCC_DebugDCUs>true</DCC_DebugDCUs>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File> <Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''"> <PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
@ -104,6 +105,7 @@
<FormType>dfm</FormType> <FormType>dfm</FormType>
<DesignClass>TFrame</DesignClass> <DesignClass>TFrame</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="source\UDKIniFile.pas"/>
<BuildConfiguration Include="Base"> <BuildConfiguration Include="Base">
<Key>Base</Key> <Key>Base</Key>
</BuildConfiguration> </BuildConfiguration>

Binary file not shown.

444
source/UDKIniFile.pas Normal file
View 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.

View File

@ -10,8 +10,9 @@ type
private private
FLocation: string; FLocation: string;
FLoaded: Boolean; FLoaded: Boolean;
FModified: Boolean;
protected protected
procedure Notify(const APropertyName: string); virtual; procedure PropertyChanged(const APropertyName: string); virtual;
procedure DoLoad; virtual; abstract; procedure DoLoad; virtual; abstract;
procedure DoSave; virtual; abstract; procedure DoSave; virtual; abstract;
@ -26,6 +27,7 @@ type
property Loaded: Boolean read FLoaded; property Loaded: Boolean read FLoaded;
property Location: string read FLocation; property Location: string read FLocation;
property Modified: Boolean read FModified;
end; end;
TCustomGameClass = class of TCustomGame; TCustomGameClass = class of TCustomGame;
@ -53,20 +55,44 @@ end;
procedure TCustomGame.Load; procedure TCustomGame.Load;
var
wasModified: Boolean;
begin begin
wasModified := FModified;
DoLoad; DoLoad;
FLoaded := True; FLoaded := True;
FModified := False;
if wasModified then
TBindings.Notify(Self, 'Modified');
end; end;
procedure TCustomGame.Save; procedure TCustomGame.Save;
var
wasModified: Boolean;
begin begin
wasModified := FModified;
DoSave; DoSave;
FModified := False;
if wasModified then
TBindings.Notify(Self, 'Modified');
end; end;
procedure TCustomGame.Notify(const APropertyName: string); procedure TCustomGame.PropertyChanged(const APropertyName: string);
begin begin
if not FModified then
begin
FModified := True;
TBindings.Notify(Self, 'Modified');
end;
TBindings.Notify(Self, APropertyName); TBindings.Notify(Self, APropertyName);
end; end;

View File

@ -15,7 +15,7 @@ type
in the base Chivalry class. } in the base Chivalry class. }
TChivalryMedievalWarfareGame = class(TChivalryGame) TChivalryMedievalWarfareGame = class(TChivalryGame)
protected protected
procedure LoadSupportedMapList(AList: TList<TGameMap>); override; procedure LoadPredefinedMapList(AList: TList<TGameMap>); override;
public public
class function GameName: string; override; class function GameName: string; override;
class function AutoDetect: TCustomGame; override; class function AutoDetect: TCustomGame; override;
@ -81,7 +81,7 @@ begin
end; end;
procedure TChivalryMedievalWarfareGame.LoadSupportedMapList(AList: TList<TGameMap>); procedure TChivalryMedievalWarfareGame.LoadPredefinedMapList(AList: TList<TGameMap>);
var var
mapListFileName: string; mapListFileName: string;
mapList: TMemIniFile; mapList: TMemIniFile;

View File

@ -18,13 +18,13 @@ type
FServerName: string; FServerName: string;
FMessageOfTheDay: string; FMessageOfTheDay: string;
FSupportedMapList: TObjectList<TGameMap>; FPredefinedMapList: TObjectList<TGameMap>;
FMapList: TObjectList<TGameMap>; FMapList: TObjectList<TGameMap>;
protected protected
procedure DoLoad; override; procedure DoLoad; override;
procedure DoSave; 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; function CreateGameMap(const AMapName: string): TGameMap; virtual;
public public
@ -55,8 +55,22 @@ type
property MessageOfTheDay: string read GetMessageOfTheDay write SetMessageOfTheDay; property MessageOfTheDay: string read GetMessageOfTheDay write SetMessageOfTheDay;
{ IGameMapList } { IGameMapList }
function GetSupportedMapList: TList<TGameMap>; function GetPredefinedMapList: TEnumerable<TGameMap>;
function GetMapList: TList<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; end;
@ -64,7 +78,9 @@ implementation
uses uses
System.Classes, System.Classes,
System.IniFiles, System.IniFiles,
System.SysUtils; System.SysUtils,
UDKIniFile;
const const
@ -92,17 +108,17 @@ constructor TChivalryGame.Create(const ALocation: string);
begin begin
inherited Create(ALocation); inherited Create(ALocation);
FSupportedMapList := TObjectList<TGameMap>.Create(True); FPredefinedMapList := TObjectList<TGameMap>.Create(True);
FMapList := TObjectList<TGameMap>.Create(True); FMapList := TObjectList<TGameMap>.Create(True);
LoadSupportedMapList(FSupportedMapList); LoadPredefinedMapList(FPredefinedMapList);
end; end;
destructor TChivalryGame.Destroy; destructor TChivalryGame.Destroy;
begin begin
FreeAndNil(FMapList); FreeAndNil(FMapList);
FreeAndNil(FSupportedMapList); FreeAndNil(FPredefinedMapList);
inherited Destroy; inherited Destroy;
end; end;
@ -110,40 +126,45 @@ end;
procedure TChivalryGame.DoLoad; procedure TChivalryGame.DoLoad;
var var
gameSettings: TMemIniFile; gameSettings: TUDKIniFile;
engineSettings: TMemIniFile; engineSettings: TUDKIniFile;
mapListValues: TStringList; mapListValues: TStringList;
valueIndex: Integer; mapName: string;
mapListChanged: Boolean;
begin begin
gameSettings := TMemIniFile.Create(Location + GameSettingsFileName); gameSettings := TUDKIniFile.Create(Location + GameSettingsFileName);
try try
FServerPort := gameSettings.ReadInteger(GameURL, GameURLPort, GameURLPortDefault); SetServerPort(gameSettings.ReadInteger(GameURL, GameURLPort, GameURLPortDefault));
FPeerPort := gameSettings.ReadInteger(GameURL, GameURLPeerPort, GameURLPeerPortDefault); SetPeerPort(gameSettings.ReadInteger(GameURL, GameURLPeerPort, GameURLPeerPortDefault));
FServerName := gameSettings.ReadString(GameReplicationInfo, GameReplicationInfoServerName, ''); SetServerName(gameSettings.ReadString(GameReplicationInfo, GameReplicationInfoServerName, ''));
FMessageOfTheDay := gameSettings.ReadString(GameReplicationInfo, GameReplicationInfoMessageOfTheDay, ''); SetMessageOfTheDay(gameSettings.ReadString(GameReplicationInfo, GameReplicationInfoMessageOfTheDay, ''));
mapListChanged := (FMapList.Count > 0);
{ Maplist is special; it occurs multiple times and order matters } { Maplist is special; it occurs multiple times and order matters }
mapListValues := TStringList.Create; mapListValues := TStringList.Create;
try try
gameSettings.ReadSectionValues(GameAOCGame, mapListValues); gameSettings.ReadDuplicateStrings(GameAOCGame, GameAOCGameMapList, mapListValues);
for mapName in mapListValues do
for valueIndex := 0 to Pred(mapListValues.Count) do
begin begin
if SameText(mapListValues.Names[valueIndex], GameAOCGameMapList) then FMapList.Add(CreateGameMap(mapName));
FMapList.Add(CreateGameMap(mapListValues.ValueFromIndex[valueIndex])); mapListChanged := True;
end; end;
finally finally
FreeAndNil(mapListValues); FreeAndNil(mapListValues);
end; end;
if mapListChanged then
PropertyChanged('MapList');
finally finally
FreeAndNil(gameSettings); FreeAndNil(gameSettings);
end; end;
engineSettings := TMemIniFile.Create(Location + EngineSettingsFileName); engineSettings := TUDKIniFile.Create(Location + EngineSettingsFileName);
try try
FQueryPort := engineSettings.ReadInteger(EngineSteam, EngineSteamQueryPort, EngineSteamQueryPortDefault); SetQueryPort(engineSettings.ReadInteger(EngineSteam, EngineSteamQueryPort, EngineSteamQueryPortDefault));
finally finally
FreeAndNil(engineSettings); FreeAndNil(engineSettings);
end; end;
@ -151,8 +172,39 @@ end;
procedure TChivalryGame.DoSave; procedure TChivalryGame.DoSave;
var
gameSettings: TUDKIniFile;
engineSettings: TUDKIniFile;
map: TGameMap;
begin 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; end;
@ -163,7 +215,7 @@ var
begin begin
Result := nil; Result := nil;
for map in GetSupportedMapList do for map in PredefinedMapList do
if SameText(map.Name, AMapName) then if SameText(map.Name, AMapName) then
begin begin
Result := TGameMap.Create(map); Result := TGameMap.Create(map);
@ -199,7 +251,7 @@ begin
if Value <> FServerPort then if Value <> FServerPort then
begin begin
FServerPort := Value; FServerPort := Value;
Notify('ServerPort'); PropertyChanged('ServerPort');
end; end;
end; end;
@ -209,7 +261,7 @@ begin
if Value <> FPeerPort then if Value <> FPeerPort then
begin begin
FPeerPort := Value; FPeerPort := Value;
Notify('PeerPort'); PropertyChanged('PeerPort');
end; end;
end; end;
@ -219,7 +271,7 @@ begin
if Value <> FQueryPort then if Value <> FQueryPort then
begin begin
FQueryPort := Value; FQueryPort := Value;
Notify('QueryPort'); PropertyChanged('QueryPort');
end; end;
end; end;
@ -241,7 +293,7 @@ begin
if Value <> FServerName then if Value <> FServerName then
begin begin
FServerName := Value; FServerName := Value;
Notify('ServerName'); PropertyChanged('ServerName');
end; end;
end; end;
@ -251,20 +303,70 @@ begin
if Value <> FMessageOfTheDay then if Value <> FMessageOfTheDay then
begin begin
FMessageOfTheDay := Value; FMessageOfTheDay := Value;
Notify('MessageOfTheDay'); PropertyChanged('MessageOfTheDay');
end; end;
end; end;
function TChivalryGame.GetMapList: TList<TGameMap>; function TChivalryGame.GetPredefinedMapList: TEnumerable<TGameMap>;
begin
Result := FPredefinedMapList;
end;
function TChivalryGame.GetMapList: TEnumerable<TGameMap>;
begin begin
Result := FMapList; Result := FMapList;
end; end;
function TChivalryGame.GetSupportedMapList: TList<TGameMap>; function TChivalryGame.GetMapCount: Integer;
begin 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;
end. end.

View File

@ -54,8 +54,23 @@ type
IGameMapList = interface IGameMapList = interface
['{E8552B4C-9447-4FAD-BB20-C5EB3AF07B0E}'] ['{E8552B4C-9447-4FAD-BB20-C5EB3AF07B0E}']
function GetSupportedMapList: TList<TGameMap>; function GetPredefinedMapList: TEnumerable<TGameMap>;
function GetMapList: TList<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; end;

View File

@ -15,6 +15,7 @@ object MainForm: TMainForm
OldCreateOrder = False OldCreateOrder = False
Position = poScreenCenter Position = poScreenCenter
ShowHint = True ShowHint = True
OnCloseQuery = FormCloseQuery
OnCreate = FormCreate OnCreate = FormCreate
OnDestroy = FormDestroy OnDestroy = FormDestroy
PixelsPerInch = 96 PixelsPerInch = 96
@ -65,7 +66,7 @@ object MainForm: TMainForm
Top = 76 Top = 76
Width = 565 Width = 565
Height = 428 Height = 428
ActivePage = tsMapList ActivePage = tsAbout
Align = alClient Align = alClient
Style = tsButtons Style = tsButtons
TabOrder = 2 TabOrder = 2
@ -175,7 +176,6 @@ object MainForm: TMainForm
Align = alRight Align = alRight
BevelOuter = bvNone BevelOuter = bvNone
TabOrder = 2 TabOrder = 2
ExplicitLeft = 415
inline frmMapPreview: TMapPreviewFrame inline frmMapPreview: TMapPreviewFrame
Left = 0 Left = 0
Top = 0 Top = 0
@ -183,7 +183,6 @@ object MainForm: TMainForm
Height = 130 Height = 130
Align = alTop Align = alTop
TabOrder = 0 TabOrder = 0
ExplicitTop = 216
end end
object btnMapListSave: TButton object btnMapListSave: TButton
AlignWithMargins = True AlignWithMargins = True
@ -198,9 +197,6 @@ object MainForm: TMainForm
Action = actMapListSave Action = actMapListSave
Align = alTop Align = alTop
TabOrder = 2 TabOrder = 2
ExplicitLeft = 28
ExplicitTop = 168
ExplicitWidth = 75
end end
object btnMapListLoad: TButton object btnMapListLoad: TButton
AlignWithMargins = True AlignWithMargins = True
@ -215,9 +211,6 @@ object MainForm: TMainForm
Action = actMapListLoad Action = actMapListLoad
Align = alTop Align = alTop
TabOrder = 1 TabOrder = 1
ExplicitLeft = 28
ExplicitTop = 168
ExplicitWidth = 75
end end
end end
end end
@ -600,135 +593,167 @@ object MainForm: TMainForm
ImageIndex = 4 ImageIndex = 4
object lblJCL: TLabel object lblJCL: TLabel
Left = 12 Left = 12
Top = 222 Top = 262
Width = 319 Width = 319
Height = 13 Height = 13
Caption = 'Uses JEDI Code Library (JCL) and Visual Component Library (JVCL)' Caption = 'Uses JEDI Code Library (JCL) and Visual Component Library (JVCL)'
end end
object lblVirtualTreeview: TLabel object lblVirtualTreeview: TLabel
Left = 12 Left = 12
Top = 334 Top = 358
Width = 179 Width = 179
Height = 13 Height = 13
Caption = 'Uses Virtual Treeview by Mike Lischke' Caption = 'Uses Virtual Treeview by Mike Lischke'
end end
object lblChivalry: TLabel object lblChivalry: TLabel
Left = 12 Left = 12
Top = 16 Top = 80
Width = 246 Width = 246
Height = 13 Height = 13
Caption = 'Chivalry: Medieval Warfare by Torn Banner Studios' Caption = 'Chivalry: Medieval Warfare by Torn Banner Studios'
end end
object lblPixelophilia: TLabel object lblPixelophilia: TLabel
Left = 12 Left = 12
Top = 72 Top = 128
Width = 293 Width = 293
Height = 13 Height = 13
Caption = 'Various icons are part of the Pixelophilia packs by '#214'mer '#199'etin' Caption = 'Various icons are part of the Pixelophilia packs by '#214'mer '#199'etin'
end end
object lblGentleface: TLabel object lblGentleface: TLabel
Left = 12 Left = 12
Top = 147 Top = 195
Width = 217 Width = 217
Height = 13 Height = 13
Caption = 'Toolbar icons are part of the Gentleface pack' Caption = 'Toolbar icons are part of the Gentleface pack'
end end
object lblSuperObject: TLabel object lblSuperObject: TLabel
Left = 12 Left = 12
Top = 278 Top = 310
Width = 176 Width = 176
Height = 13 Height = 13
Caption = 'Uses SuperObject by Henri Gourvest' Caption = 'Uses SuperObject by Henri Gourvest'
end 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 object llJCL: TLinkLabel
Left = 12 Left = 12
Top = 241 Top = 277
Width = 101 Width = 101
Height = 17 Height = 17
Caption = '<a href="http://www.delphi-jedi.org/">www.delphi-jedi.org</a>' Caption = '<a href="http://www.delphi-jedi.org/">www.delphi-jedi.org</a>'
TabOrder = 5 TabOrder = 6
TabStop = True TabStop = True
OnLinkClick = llLinkClick OnLinkClick = llLinkClick
end end
object llVirtualTreeview: TLinkLabel object llVirtualTreeview: TLinkLabel
Left = 12 Left = 12
Top = 353 Top = 373
Width = 274 Width = 274
Height = 17 Height = 17
Caption = Caption =
'<a href="http://www.soft-gems.net/index.php/controls/virtual-tre' + '<a href="http://www.soft-gems.net/index.php/controls/virtual-tre' +
'eview">www.soft-gems.net/index.php/controls/virtual-treeview</a>' 'eview">www.soft-gems.net/index.php/controls/virtual-treeview</a>'
TabOrder = 7 TabOrder = 8
TabStop = True TabStop = True
OnLinkClick = llLinkClick OnLinkClick = llLinkClick
end end
object llChivalry: TLinkLabel object llChivalry: TLinkLabel
Left = 12 Left = 12
Top = 35 Top = 95
Width = 109 Width = 109
Height = 17 Height = 17
Caption = '<a href="http://www.tornbanner.com/">www.tornbanner.com</a>' Caption = '<a href="http://www.tornbanner.com/">www.tornbanner.com</a>'
TabOrder = 0 TabOrder = 1
TabStop = True TabStop = True
OnLinkClick = llLinkClick OnLinkClick = llLinkClick
end end
object llPixelophilia: TLinkLabel object llPixelophilia: TLinkLabel
Left = 12 Left = 12
Top = 91 Top = 143
Width = 128 Width = 128
Height = 17 Height = 17
Caption = Caption =
'<a href="http://omercetin.deviantart.com/">omercetin.deviantart.' + '<a href="http://omercetin.deviantart.com/">omercetin.deviantart.' +
'com</a>' 'com</a>'
TabOrder = 1 TabOrder = 2
TabStop = True TabStop = True
OnLinkClick = llLinkClick OnLinkClick = llLinkClick
end end
object llGentleface: TLinkLabel object llGentleface: TLinkLabel
Left = 12 Left = 12
Top = 166 Top = 210
Width = 172 Width = 172
Height = 17 Height = 17
Caption = Caption =
'<a href="http://gentleface.com/free_icon_set.html">gentleface.co' + '<a href="http://gentleface.com/free_icon_set.html">gentleface.co' +
'm/free_icon_set.html</a>' 'm/free_icon_set.html</a>'
TabOrder = 3 TabOrder = 4
TabStop = True TabStop = True
OnLinkClick = llLinkClick OnLinkClick = llLinkClick
end end
object llPixelophiliaCC: TLinkLabel object llPixelophiliaCC: TLinkLabel
Left = 12 Left = 12
Top = 110 Top = 158
Width = 303 Width = 303
Height = 17 Height = 17
Caption = Caption =
'<a href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Crea' + '<a href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Crea' +
'tive Commons Attribution-Noncommercial-No Derivate 3.0</a>' 'tive Commons Attribution-Noncommercial-No Derivate 3.0</a>'
TabOrder = 2 TabOrder = 3
TabStop = True TabStop = True
OnLinkClick = llLinkClick OnLinkClick = llLinkClick
end end
object llGentlefaceCC: TLinkLabel object llGentlefaceCC: TLinkLabel
Left = 12 Left = 12
Top = 185 Top = 225
Width = 242 Width = 242
Height = 17 Height = 17
Caption = Caption =
'<a href="http://creativecommons.org/licenses/by-nc/3.0/">Creativ' + '<a href="http://creativecommons.org/licenses/by-nc/3.0/">Creativ' +
'e Commons Attribution-Noncommercial 3.0</a>' 'e Commons Attribution-Noncommercial 3.0</a>'
TabOrder = 4 TabOrder = 5
TabStop = True TabStop = True
OnLinkClick = llLinkClick OnLinkClick = llLinkClick
end end
object llSuperObject: TLinkLabel object llSuperObject: TLinkLabel
Left = 12 Left = 12
Top = 297 Top = 325
Width = 157 Width = 157
Height = 17 Height = 17
Caption = Caption =
'<a href="https://code.google.com/p/superobject/">code.google.com' + '<a href="https://code.google.com/p/superobject/">code.google.com' +
'/p/superobject</a>' '/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 TabStop = True
OnLinkClick = llLinkClick OnLinkClick = llLinkClick
end end
@ -1689,7 +1714,7 @@ object MainForm: TMainForm
end end
object btnSave: TButton object btnSave: TButton
AlignWithMargins = True AlignWithMargins = True
Left = 533 Left = 434
Top = 0 Top = 0
Width = 91 Width = 91
Height = 25 Height = 25
@ -1701,6 +1726,20 @@ object MainForm: TMainForm
Align = alRight Align = alRight
TabOrder = 2 TabOrder = 2
end 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 end
object mbpMenuPainter: TX2MenuBarmusikCubePainter object mbpMenuPainter: TX2MenuBarmusikCubePainter
Left = 44 Left = 44
@ -2469,11 +2508,16 @@ object MainForm: TMainForm
end end
object actSave: TAction object actSave: TAction
Caption = '&Save changes' Caption = '&Save changes'
OnExecute = actSaveExecute
end end
object actClose: TAction object actClose: TAction
Caption = '&Close' Caption = '&Close'
OnExecute = actCloseExecute OnExecute = actCloseExecute
end end
object actRevert: TAction
Caption = '&Revert changes'
OnExecute = actRevertExecute
end
end end
object pmnLaunch: TPopupMenu object pmnLaunch: TPopupMenu
Left = 144 Left = 144

View File

@ -134,9 +134,15 @@ type
btnMapListLoad: TButton; btnMapListLoad: TButton;
actMapListLoad: TAction; actMapListLoad: TAction;
actMapListSave: TAction; actMapListSave: TAction;
btnRevert: TButton;
actRevert: TAction;
llWebsite: TLinkLabel;
lblProductName: TLabel;
lblCopyright: TLabel;
procedure FormCreate(Sender: TObject); procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject); procedure FormDestroy(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure mbMenuCollapsing(Sender: TObject; Group: TX2MenuBarGroup; var Allowed: Boolean); procedure mbMenuCollapsing(Sender: TObject; Group: TX2MenuBarGroup; var Allowed: Boolean);
procedure mbMenuSelectedChanged(Sender: TObject; Item: TX2CustomMenuBarItem); procedure mbMenuSelectedChanged(Sender: TObject; Item: TX2CustomMenuBarItem);
procedure llLinkClick(Sender: TObject; const Link: string; LinkType: TSysLinkType); procedure llLinkClick(Sender: TObject; const Link: string; LinkType: TSysLinkType);
@ -159,6 +165,8 @@ type
procedure actMapRemoveExecute(Sender: TObject); procedure actMapRemoveExecute(Sender: TObject);
procedure actMapUpExecute(Sender: TObject); procedure actMapUpExecute(Sender: TObject);
procedure actMapDownExecute(Sender: TObject); procedure actMapDownExecute(Sender: TObject);
procedure actSaveExecute(Sender: TObject);
procedure actRevertExecute(Sender: TObject);
private type private type
TBindingExpressionList = TList<TBindingExpression>; TBindingExpressionList = TList<TBindingExpression>;
TPageMenuDictionary = TDictionary<TTabSheet, TX2MenuBarItem>; TPageMenuDictionary = TDictionary<TTabSheet, TX2MenuBarItem>;
@ -167,10 +175,14 @@ type
FPageMenuMap: TPageMenuDictionary; FPageMenuMap: TPageMenuDictionary;
FUIBindings: TBindingExpressionList; FUIBindings: TBindingExpressionList;
function GetModified: Boolean;
procedure SetActiveGame(const Value: TCustomGame); procedure SetActiveGame(const Value: TCustomGame);
procedure SetModified(const Value: Boolean);
protected protected
procedure EnablePageActions; procedure EnablePageActions;
procedure ActiveGameChanged; procedure ActiveGameChanged;
procedure SaveActiveGame;
procedure RevertActiveGame;
procedure ClearUIBindings; procedure ClearUIBindings;
procedure Bind(const APropertyName: string; ADestObject: TObject; const ADestPropertyName: string); procedure Bind(const APropertyName: string; ADestObject: TObject; const ADestPropertyName: string);
@ -181,6 +193,8 @@ type
procedure UpdateGameList; procedure UpdateGameList;
procedure UpdateMapList; procedure UpdateMapList;
procedure SaveGameList;
function FindMapNode(AMap: TGameMap): PVirtualNode; function FindMapNode(AMap: TGameMap): PVirtualNode;
procedure HandleMapSelection(ANodes: TNodeArray; ATargetIndex: Integer; ACopy: Boolean); procedure HandleMapSelection(ANodes: TNodeArray; ATargetIndex: Integer; ACopy: Boolean);
@ -190,6 +204,8 @@ type
property ActiveGame: TCustomGame read FActiveGame write SetActiveGame; property ActiveGame: TCustomGame read FActiveGame write SetActiveGame;
property PageMenuMap: TPageMenuDictionary read FPageMenuMap; property PageMenuMap: TPageMenuDictionary read FPageMenuMap;
property UIBindings: TBindingExpressionList read FUIBindings; property UIBindings: TBindingExpressionList read FUIBindings;
public
property Modified: Boolean read GetModified write SetModified;
end; end;
@ -204,6 +220,7 @@ uses
Winapi.ShellAPI, Winapi.ShellAPI,
Winapi.Windows, Winapi.Windows,
X2UtApp,
X2UtGraphics, X2UtGraphics,
Forms.Game, Forms.Game,
@ -280,6 +297,9 @@ begin
lightBtnFace := BlendColors(clBtnFace, clWindow, 196); lightBtnFace := BlendColors(clBtnFace, clWindow, 196);
pnlGamesWarning.Color := lightBtnFace; pnlGamesWarning.Color := lightBtnFace;
lblProductName.Caption := App.Version.FormatVersion(False, True);
lblCopyright.Caption := App.Version.Strings.LegalCopyright;
{ Load games } { Load games }
userGamesFileName := Resources.GetUserDataPath(Resources.UserGamesFileName); userGamesFileName := Resources.GetUserDataPath(Resources.UserGamesFileName);
@ -310,6 +330,13 @@ begin
end; 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; procedure TMainForm.EnablePageActions;
const const
ActionListState: array[Boolean] of TActionListState = (asSuspended, asNormal); ActionListState: array[Boolean] of TActionListState = (asSuspended, asNormal);
@ -329,7 +356,14 @@ begin
if Assigned(ActiveGame) and (not ActiveGame.Loaded) then if Assigned(ActiveGame) and (not ActiveGame.Loaded) then
ActiveGame.Load; 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 if Supports(ActiveGame, IGameNetwork) then
BindGameNetwork; BindGameNetwork;
@ -344,6 +378,26 @@ begin
end; 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; procedure TMainForm.ClearUIBindings;
var var
binding: TBindingExpression; binding: TBindingExpression;
@ -425,12 +479,19 @@ var
begin begin
if Supports(ActiveGame, IGameMapList, gameMapList) then if Supports(ActiveGame, IGameMapList, gameMapList) then
vstMapList.RootNodeCount := gameMapList.GetMapList.Count vstMapList.RootNodeCount := gameMapList.MapCount
else else
vstMapList.Clear; vstMapList.Clear;
end; end;
procedure TMainForm.SaveGameList;
begin
TGameListPersist.Save(Resources.GetUserDataPath(Resources.UserGamesFileName), TGameList.Instance);
UpdateGameList;
end;
function TMainForm.FindMapNode(AMap: TGameMap): PVirtualNode; function TMainForm.FindMapNode(AMap: TGameMap): PVirtualNode;
var var
gameMapList: IGameMapList; gameMapList: IGameMapList;
@ -443,7 +504,7 @@ begin
Result := vstMapList.IterateSubtree(nil, Result := vstMapList.IterateSubtree(nil,
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean) procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
begin begin
Abort := (gameMapList.GetMapList[Node^.Index] = AMap); Abort := (gameMapList.Map[Node^.Index] = AMap);
end, end,
nil); nil);
end; end;
@ -483,8 +544,8 @@ begin
{ Copy map nodes } { Copy map nodes }
Inc(sourceIndex, sourceShift); Inc(sourceIndex, sourceShift);
map := TGameMap.Create(gameMapList.GetMapList[sourceIndex]); map := TGameMap.Create(gameMapList.Map[sourceIndex]);
gameMapList.GetMapList.Insert(targetIndex, map); gameMapList.InsertMap(targetIndex, map);
selectedMaps.Add(map); selectedMaps.Add(map);
Inc(targetIndex); Inc(targetIndex);
@ -507,8 +568,8 @@ begin
Inc(targetIndex); Inc(targetIndex);
end; end;
selectedMaps.Add(gameMapList.GetMapList[sourceIndex]); selectedMaps.Add(gameMapList.Map[sourceIndex]);
gameMapList.GetMapList.Move(sourceIndex, newIndex); gameMapList.MoveMap(sourceIndex, newIndex);
end; end;
end; end;
@ -517,7 +578,7 @@ begin
vstMapList.IterateSubtree(nil, vstMapList.IterateSubtree(nil,
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean) procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
begin begin
if selectedMaps.Contains(gameMapList.GetMapList[Node^.Index]) then if selectedMaps.Contains(gameMapList.Map[Node^.Index]) then
Sender.Selected[Node] := True; Sender.Selected[Node] := True;
end, end,
nil); nil);
@ -542,6 +603,12 @@ begin
end; end;
function TMainForm.GetModified: Boolean;
begin
Result := actSave.Enabled;
end;
procedure TMainForm.SetActiveGame(const Value: TCustomGame); procedure TMainForm.SetActiveGame(const Value: TCustomGame);
begin begin
if Value <> FActiveGame then if Value <> FActiveGame then
@ -552,6 +619,13 @@ begin
end; 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); procedure TMainForm.mbMenuCollapsing(Sender: TObject; Group: TX2MenuBarGroup; var Allowed: Boolean);
begin begin
Allowed := False; Allowed := False;
@ -589,6 +663,21 @@ begin
end; 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); procedure TMainForm.actGameAddExecute(Sender: TObject);
var var
game: TCustomGame; game: TCustomGame;
@ -597,10 +686,7 @@ begin
if TGameForm.Insert(Self, game) then if TGameForm.Insert(Self, game) then
begin begin
TGameList.Instance.Add(game); TGameList.Instance.Add(game);
SaveGameList;
// #ToDo1 -oMvR: 30-6-2014: move to shared spot
TGameListPersist.Save(Resources.GetUserDataPath(Resources.UserGamesFileName), TGameList.Instance);
UpdateGameList;
ActiveGame := game; ActiveGame := game;
end; end;
@ -633,10 +719,7 @@ begin
ActiveGame := nil; ActiveGame := nil;
end; end;
// #ToDo1 -oMvR: 30-6-2014: move to shared spot SaveGameList;
TGameListPersist.Save(Resources.GetUserDataPath(Resources.UserGamesFileName), TGameList.Instance);
UpdateGameList;
finally finally
vstGames.EndUpdate; vstGames.EndUpdate;
end; end;
@ -648,6 +731,22 @@ procedure TMainForm.actGameActiveExecute(Sender: TObject);
begin begin
if Assigned(vstGames.FocusedNode) then if Assigned(vstGames.FocusedNode) then
begin 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; vstGames.BeginUpdate;
try try
ActiveGame := TGameList.Instance[vstGames.FocusedNode^.Index]; ActiveGame := TGameList.Instance[vstGames.FocusedNode^.Index];
@ -698,7 +797,7 @@ begin
if not Supports(ActiveGame, IGameMapList, gameMapList) then if not Supports(ActiveGame, IGameMapList, gameMapList) then
exit; exit;
map := gameMapList.GetMapList[Node^.Index]; map := gameMapList.Map[Node^.Index];
case Column of case Column of
MapColumnName: MapColumnName:
@ -782,7 +881,10 @@ begin
if not Supports(ActiveGame, IGameMapList, gameMapList) then if not Supports(ActiveGame, IGameMapList, gameMapList) then
exit; exit;
frmMapPreview.Load(gameMapList.GetMapList[Node^.Index].Name); if Assigned(Node) then
frmMapPreview.Load(gameMapList.Map[Node^.Index].Name)
else
frmMapPreview.Clear;
end; end;
@ -798,7 +900,7 @@ begin
if TMapForm.Insert(Self, gameMapList, map) then if TMapForm.Insert(Self, gameMapList, map) then
begin begin
gameMapList.GetMapList.Add(map); gameMapList.AddMap(map);
UpdateMapList; UpdateMapList;
node := FindMapNode(map); node := FindMapNode(map);
@ -832,7 +934,7 @@ begin
selectedNodes := vstMapList.GetSortedSelection(True); selectedNodes := vstMapList.GetSortedSelection(True);
for nodeIndex := High(selectedNodes) downto Low(selectedNodes) do for nodeIndex := High(selectedNodes) downto Low(selectedNodes) do
gameMapList.GetMapList.Delete(selectedNodes[nodeIndex]^.Index); gameMapList.DeleteMap(selectedNodes[nodeIndex]^.Index);
UpdateMapList; UpdateMapList;
finally finally

View File

@ -87,8 +87,6 @@ object MapForm: TMapForm
OnFocusChanging = vstMapFocusChanging OnFocusChanging = vstMapFocusChanging
OnGetText = vstMapGetText OnGetText = vstMapGetText
OnPaintText = vstMapPaintText OnPaintText = vstMapPaintText
ExplicitLeft = 8
ExplicitWidth = 561
Columns = <> Columns = <>
end end
object pnlMapName: TPanel object pnlMapName: TPanel
@ -105,7 +103,6 @@ object MapForm: TMapForm
AutoSize = True AutoSize = True
BevelOuter = bvNone BevelOuter = bvNone
TabOrder = 2 TabOrder = 2
ExplicitTop = 475
DesignSize = ( DesignSize = (
561 561
21) 21)
@ -173,8 +170,6 @@ object MapForm: TMapForm
Align = alLeft Align = alLeft
BevelOuter = bvNone BevelOuter = bvNone
TabOrder = 4 TabOrder = 4
ExplicitLeft = 0
ExplicitHeight = 342
inline frmMapPreview: TMapPreviewFrame inline frmMapPreview: TMapPreviewFrame
AlignWithMargins = True AlignWithMargins = True
Left = 0 Left = 0
@ -187,7 +182,7 @@ object MapForm: TMapForm
Margins.Bottom = 0 Margins.Bottom = 0
Align = alTop Align = alTop
TabOrder = 0 TabOrder = 0
ExplicitWidth = 342 ExplicitTop = 8
end end
end end
end end

View File

@ -47,7 +47,7 @@ type
function GetMapName: string; function GetMapName: string;
procedure SetMapName(const Value: string); procedure SetMapName(const Value: string);
protected protected
procedure LoadSupportedMapList(AGame: IGameMapList); procedure LoadPredefinedMapList(AGame: IGameMapList);
function CreateMap: TGameMap; function CreateMap: TGameMap;
function FindMapNode(const AMapName: string): PVirtualNode; function FindMapNode(const AMapName: string): PVirtualNode;
@ -82,7 +82,7 @@ class function TMapForm.Insert(AOwner: TComponent; AGame: IGameMapList; out AMap
begin begin
with Self.Create(AOwner) do with Self.Create(AOwner) do
try try
LoadSupportedMapList(AGame); LoadPredefinedMapList(AGame);
Result := (ShowModal = mrOk); Result := (ShowModal = mrOk);
if Result then if Result then
@ -99,7 +99,7 @@ begin
end; end;
procedure TMapForm.LoadSupportedMapList(AGame: IGameMapList); procedure TMapForm.LoadPredefinedMapList(AGame: IGameMapList);
var var
map: TGameMap; map: TGameMap;
categoryNodes: TDictionary<string, PVirtualNode>; categoryNodes: TDictionary<string, PVirtualNode>;
@ -113,7 +113,7 @@ begin
categoryNodes := TDictionary<string, PVirtualNode>.Create; categoryNodes := TDictionary<string, PVirtualNode>.Create;
try try
for map in AGame.GetSupportedMapList do for map in AGame.PredefinedMapList do
begin begin
if categoryNodes.ContainsKey(map.Category) then if categoryNodes.ContainsKey(map.Category) then
parentNode := categoryNodes[map.Category] parentNode := categoryNodes[map.Category]
@ -130,6 +130,7 @@ begin
end; end;
finally finally
vstMap.FullExpand; vstMap.FullExpand;
vstMap.EndUpdate;
node := vstMap.GetFirstLevel(1); node := vstMap.GetFirstLevel(1);
if Assigned(node) then if Assigned(node) then
@ -137,8 +138,6 @@ begin
vstMap.FocusedNode := node; vstMap.FocusedNode := node;
vstMap.Selected[node] := True; vstMap.Selected[node] := True;
end; end;
vstMap.EndUpdate;
end; end;
end; end;