mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-15 13:19:23 +00:00
Fixes #9430. Adds a 'default' property to subkeys, only used by longpress-type subkeys: * Added property to schema and readers/writers * Added property to touch layout validator * Added editor for property to layout builder
1130 lines
30 KiB
ObjectPascal
1130 lines
30 KiB
ObjectPascal
(*
|
|
Name: TouchLayout
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 29 Nov 2013
|
|
|
|
Modified Date: 27 May 2015
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 29 Nov 2013 - mcdurdin - I3982 - V9.0 - Touch layout editor should not override special keys when swapping layouts
|
|
07 Feb 2014 - mcdurdin - I4035 - V9.0 - Parsing of UTF-8 in JSON layout file crashes TIKE
|
|
21 Feb 2014 - mcdurdin - I4060 - V9.0 - KeymanWeb compiler should validate the layout file
|
|
21 Feb 2014 - mcdurdin - I4062 - V9.0 - Touch Layout platforms do not allow font size specification
|
|
27 Feb 2014 - mcdurdin - I4083 - V9.0 - When errors encountered in JSON layout file, locate the error in the source view
|
|
06 Mar 2014 - mcdurdin - I4119 - V9.0 - KMW compiler should only warn unassociated keys that are not special keys
|
|
27 May 2015 - mcdurdin - I4655 - Developer crashes when changing font settings and in code view for touch layout if not on first line [CrashID:tike.exe_9.0.487.0_0069BE51_ERangeError]
|
|
*)
|
|
unit TouchLayout; // I4060
|
|
|
|
interface
|
|
|
|
uses
|
|
System.JSON,
|
|
System.Generics.Collections,
|
|
System.SysUtils;
|
|
|
|
type
|
|
ETouchLayoutValidate = class(Exception)
|
|
end;
|
|
|
|
TTouchLayoutFlickDirection = (
|
|
tlfdNorth, tlfdSouth, tlfdEast, tlfdWest,
|
|
tlfdNorthEast, tlfdNorthWest,
|
|
tlfdSouthEast, tlfdSouthWest
|
|
);
|
|
|
|
const
|
|
STouchLayoutFlickDirectionName: array[TTouchLayoutFlickDirection] of string = (
|
|
'n', 's', 'e', 'w',
|
|
'ne', 'nw',
|
|
'se', 'sw'
|
|
);
|
|
|
|
type
|
|
// TouchKeyType
|
|
TTouchKeyType = (tktNormal = 0, tktSpecial = 1, tktSpecialActive = 2, tktDeadkey = 8, tktBlank = 9, tktSpacer = 10); // I4119
|
|
|
|
TTouchLayoutClass = class of TTouchLayoutObject;
|
|
|
|
TTouchLayoutObject = class;
|
|
|
|
TTouchLayoutAddProc = reference to procedure(obj:TTouchLayoutObject);
|
|
|
|
TTouchLayoutObject = class
|
|
private
|
|
FJSON: TJSONObject;
|
|
FParent: TTouchLayoutObject;
|
|
protected
|
|
function GetValue(const name: string; var value: TJSONValue): Boolean; overload;
|
|
function GetValue(const name: string; var value: string): Boolean; overload;
|
|
function GetValue(const name: string; var value: Integer): Boolean; overload;
|
|
function GetValue(const name: string; var value: Boolean): Boolean; overload;
|
|
|
|
procedure AddJSONValue(JSON: TJSONObject; const name, value: string); overload;
|
|
procedure AddJSONValue(JSON: TJSONObject; const name: string;
|
|
value: Integer); overload;
|
|
procedure AddJSONValue(JSON: TJSONObject; const name: string;
|
|
value: Boolean); overload;
|
|
|
|
//function GetArray(const name: string): TJSONArray; overload;
|
|
procedure ReadArray(const name: string; cls: TTouchLayoutClass; add: TTouchLayoutAddProc);
|
|
procedure DoRead; virtual; abstract;
|
|
procedure DoWrite(JSON: TJSONObject); virtual; abstract;
|
|
public
|
|
constructor Create(AParent: TTouchLayoutObject); virtual;
|
|
procedure Read(JSON: TJSONObject);
|
|
procedure Write(JSON: TJSONObject);
|
|
property Parent: TTouchLayoutObject read FParent;
|
|
end;
|
|
|
|
TTouchLayoutSubKey = class(TTouchLayoutObject)
|
|
private
|
|
FNextLayer: string;
|
|
FFontSize: string;
|
|
FLayer: string;
|
|
FWidth: Integer;
|
|
FDk: Integer;
|
|
FFont: string;
|
|
FId: string;
|
|
FPad: Integer;
|
|
FSp: Integer;
|
|
FText: string;
|
|
FDefault: boolean;
|
|
function GetSpT: TTouchKeyType; // I4119
|
|
procedure SetSpT(const Value: TTouchKeyType); // I4119
|
|
protected
|
|
procedure DoRead; override;
|
|
procedure DoWrite(JSON: TJSONObject); override;
|
|
public
|
|
property Id: string read FId write FId;
|
|
property Text: string read FText write FText;
|
|
property Sp: Integer read FSp write FSp;
|
|
property SpT: TTouchKeyType read GetSpT write SetSpT; // I4119
|
|
property Width: Integer read FWidth write FWidth;
|
|
property Pad: Integer read FPad write FPad;
|
|
property Dk: Integer read FDk write FDk;
|
|
property Layer: string read FLayer write FLayer;
|
|
property NextLayer: string read FNextLayer write FNextLayer;
|
|
property Font: string read FFont write FFont;
|
|
property FontSize: string read FFontSize write FFontSize;
|
|
property Default: boolean read FDefault write FDefault;
|
|
end;
|
|
|
|
TTouchLayoutSubKeys = class(TObjectList<TTouchLayoutSubKey>)
|
|
end;
|
|
|
|
TTouchLayoutMultiTaps = class(TObjectList<TTouchLayoutSubKey>)
|
|
end;
|
|
|
|
TTouchLayoutFlicks = class(TObjectDictionary<TTouchLayoutFlickDirection, TTouchLayoutSubKey>)
|
|
end;
|
|
|
|
TTouchLayoutKey = class(TTouchLayoutObject)
|
|
private
|
|
FNextLayer: string;
|
|
FFontSize: string;
|
|
FLayer: string;
|
|
FWidth: Integer;
|
|
FDk: Integer;
|
|
FFont: string;
|
|
FId: string;
|
|
FPad: Integer;
|
|
FSp: Integer;
|
|
FSk: TTouchlayoutSubKeys;
|
|
FFlick: TTouchLayoutFlicks;
|
|
FMultiTap: TTouchLayoutMultiTaps;
|
|
FText: string;
|
|
FHint: string;
|
|
function GetSpT: TTouchKeyType; // I4119
|
|
procedure SetSpT(const Value: TTouchKeyType); // I4119
|
|
protected
|
|
procedure DoRead; override;
|
|
procedure DoWrite(JSON: TJSONObject); override;
|
|
public
|
|
constructor Create(AParent: TTouchLayoutObject); override;
|
|
destructor Destroy; override;
|
|
property Id: string read FId write FId;
|
|
property Text: string read FText write FText;
|
|
property Hint: string read FHint write FHint;
|
|
property Sp: Integer read FSp write FSp;
|
|
property SpT: TTouchKeyType read GetSpT write SetSpT; // I4119
|
|
property Width: Integer read FWidth write FWidth;
|
|
property Pad: Integer read FPad write FPad;
|
|
property Dk: Integer read FDk write FDk;
|
|
property Layer: string read FLayer write FLayer;
|
|
property NextLayer: string read FNextLayer write FNextLayer;
|
|
property Font: string read FFont write FFont;
|
|
property FontSize: string read FFontSize write FFontSize;
|
|
property Sk: TTouchlayoutSubKeys read FSk;
|
|
property Flick: TTouchLayoutFlicks read FFlick;
|
|
property MultiTap: TTouchLayoutMultiTaps read FMultiTap;
|
|
end;
|
|
|
|
TTouchLayoutKeys = class(TObjectList<TTouchLayoutKey>)
|
|
end;
|
|
|
|
TTouchLayoutRow = class(TTouchLayoutObject)
|
|
private
|
|
FId: string;
|
|
FKeys: TTouchLayoutKeys;
|
|
protected
|
|
procedure DoRead; override;
|
|
procedure DoWrite(JSON: TJSONObject); override;
|
|
public
|
|
constructor Create(AParent: TTouchLayoutObject); override;
|
|
destructor Destroy; override;
|
|
function FindKeyById(Id: string): TTouchLayoutKey;
|
|
property Id: string read FId write FId;
|
|
property Keys: TTouchLayoutKeys read FKeys;
|
|
end;
|
|
|
|
TTouchLayoutRows = class(TObjectList<TTouchLayoutRow>)
|
|
end;
|
|
|
|
TTouchLayoutLayer = class(TTouchLayoutObject)
|
|
private
|
|
FRows: TTouchLayoutRows;
|
|
FId: string;
|
|
protected
|
|
procedure DoRead; override;
|
|
procedure DoWrite(JSON: TJSONObject); override;
|
|
public
|
|
constructor Create(AParent: TTouchLayoutObject); override;
|
|
destructor Destroy; override;
|
|
function FindKeyById(Id: string): TTouchLayoutKey;
|
|
property Id: string read FId write FId;
|
|
property Rows: TTouchLayoutRows read FRows;
|
|
end;
|
|
|
|
TTouchLayoutLayers = class(TObjectList<TTouchLayoutLayer>)
|
|
public
|
|
function IndexOfId(const AId: string): Integer;
|
|
end;
|
|
|
|
TTouchLayoutDefaultHint = (
|
|
tldhNone, tldhLongpress, tldhMultitap, tldhFlick,
|
|
tldhFlickN, tldhFlickNE, tldhFlickE, tldhFlickSE, tldhFlickS, tldhFlickSW, tldhFlickW, tldhFlickNW
|
|
);
|
|
|
|
const
|
|
STouchLayoutDefaultHintName: array[TTouchLayoutDefaultHint] of string =
|
|
('none','longpress','multitap','flick','flick-n','flick-ne','flick-e','flick-se','flick-s','flick-sw','flick-w','flick-nw');
|
|
|
|
|
|
function DefaultHintFromString(const s: string): TTouchLayoutDefaultHint;
|
|
|
|
type
|
|
TTouchLayoutPlatform = class(TTouchLayoutObject)
|
|
private
|
|
FName: string;
|
|
FLayers: TTouchLayoutLayers;
|
|
FFontSize: string; // I4062
|
|
FDisplayUnderlying: Boolean;
|
|
FDefaultHint: TTouchLayoutDefaultHint;
|
|
FFont: string;
|
|
protected
|
|
procedure DoRead; override;
|
|
procedure DoWrite(JSON: TJSONObject); override;
|
|
public
|
|
constructor Create(AParent: TTouchLayoutObject); override;
|
|
destructor Destroy; override;
|
|
property DisplayUnderlying: Boolean read FDisplayUnderlying write FDisplayUnderlying;
|
|
property DefaultHint: TTouchLayoutDefaultHint read FDefaultHint write FDefaultHint;
|
|
property Name: string read FName write FName;
|
|
property Font: string read FFont write FFont;
|
|
property FontSize: string read FFontSize write FFontSize; // I4062
|
|
property Layers: TTouchLayoutLayers read FLayers;
|
|
end;
|
|
|
|
TTouchLayoutPlatforms = class(TObjectList<TTouchLayoutPlatform>)
|
|
end;
|
|
|
|
TTouchLayoutData = class(TTouchLayoutObject)
|
|
FPlatforms: TTouchLayoutPlatforms;
|
|
protected
|
|
procedure DoRead; override;
|
|
procedure DoWrite(JSON: TJSONObject); override;
|
|
public
|
|
constructor Create(AParent: TTouchLayoutObject); override;
|
|
destructor Destroy; override;
|
|
property Platforms: TTouchLayoutPlatforms read FPlatforms;
|
|
end;
|
|
|
|
TTouchLayoutMessageEvent = reference to procedure(Sender: TObject; const msg: string);
|
|
|
|
TTouchLayout = class
|
|
private
|
|
FLoadError: string;
|
|
FLayout: TJSONObject;
|
|
FData: TTouchLayoutData;
|
|
FOnMessage: TTouchLayoutMessageEvent;
|
|
FLoadErrorOffset: Integer; // I4083
|
|
procedure Validate(v: TJSONObject);
|
|
function GetLayoutPlatform(const Index: string): TJSONPair;
|
|
procedure DoError(E: Exception; Offset: Integer); overload; // I4083
|
|
procedure DoError(const s: string; Offset: Integer); overload; // I4083
|
|
procedure DoMessage(const s: string);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function Load(json: string): Boolean;
|
|
function Write(AFormatted: Boolean): string;
|
|
function Save(AFormatted: Boolean): string; // I4655
|
|
function Merge(ASource: TTouchLayout): Boolean;
|
|
function HasPlatform(const APlatform: string): Boolean;
|
|
property LayoutPlatform[const Index: string]: TJSONPair read GetLayoutPlatform;
|
|
property LoadError: string read FLoadError;
|
|
property LoadErrorOffset: Integer read FLoadErrorOffset; // I4083
|
|
property OnMessage: TTouchLayoutMessageEvent read FOnMessage write FOnMessage;
|
|
property Data: TTouchLayoutData read FData;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.Classes,
|
|
|
|
JsonUtil,
|
|
TouchLayoutDefinitions;
|
|
|
|
{ TTouchLayout }
|
|
|
|
procedure TTouchLayout.DoError(E: Exception; Offset: Integer); // I4083
|
|
begin
|
|
DoError('Exception ' + E.ClassName + ' reading JSON data: '+E.Message, Offset);
|
|
end;
|
|
|
|
procedure TTouchLayout.DoError(const s: string; Offset: Integer); // I4083
|
|
begin
|
|
DoMessage(s);
|
|
FLoadError := s;
|
|
FLoadErrorOffset := Offset;
|
|
end;
|
|
|
|
procedure TTouchLayout.DoMessage(const s: string);
|
|
begin
|
|
if Assigned(FOnMessage) then
|
|
FOnMessage(Self, s);
|
|
end;
|
|
|
|
function TTouchLayout.Load(json: string): Boolean;
|
|
|
|
function ParseJSONValue(const Data: string; var Offset: Integer): TJSONObject; // I4035
|
|
var
|
|
UTF8Data: TBytes;
|
|
begin
|
|
UTF8Data := TEncoding.UTF8.GetBytes(Data);
|
|
Result := TJSONObject.ParseJSONValue(UTF8Data, Offset, True) as TJSONObject; // I4083
|
|
end;
|
|
|
|
var
|
|
v: TJSONObject;
|
|
Offset: Integer;
|
|
begin
|
|
Offset := 0;
|
|
try
|
|
v := ParseJSONValue(TrimRight(JSON), Offset) as TJSONObject; // I4035
|
|
if not Assigned(v) then
|
|
begin
|
|
DoError('Invalid JSON at offset '+IntToStr(Offset), Offset); // I4035 // I4083
|
|
Exit(False);
|
|
end;
|
|
except
|
|
on E:EJSONException do
|
|
begin
|
|
DoError(E, 0); // I4083
|
|
Exit(False);
|
|
end;
|
|
end;
|
|
|
|
try
|
|
Validate(v);
|
|
except
|
|
on E:ETouchLayoutValidate do
|
|
begin
|
|
DoError(E, 0); // I4083
|
|
Exit(False);
|
|
end;
|
|
end;
|
|
|
|
FreeAndNil(FData);
|
|
FData := TTouchLayoutData.Create(nil);
|
|
FData.Read(v);
|
|
|
|
FLayout := v;
|
|
Result := True;
|
|
end;
|
|
|
|
type
|
|
TKeyCallback = reference to function(ALayer, AKey: TJSONObject): Boolean;
|
|
|
|
procedure EachKey(APlatform: TJSONObject; ACall: TKeyCallback);
|
|
var
|
|
FLayers: TJSONArray;
|
|
FLayer: TJSONObject;
|
|
FRows: TJSONArray;
|
|
FRow: TJSONObject;
|
|
FKeys: TJSONArray;
|
|
FKey: TJSONObject;
|
|
FLayerBase: TJSONValue;
|
|
FRowBase: TJSONValue;
|
|
FKeyBase: TJSONValue;
|
|
begin
|
|
FLayers := APlatform.Get('layer').JsonValue as TJsonArray;
|
|
for FLayerBase in FLayers do
|
|
begin
|
|
FLayer := FLayerBase as TJsonObject;
|
|
FRows := FLayer.Get('row').JsonValue as TJsonArray;
|
|
for FRowBase in FRows do
|
|
begin
|
|
FRow := FRowBase as TJsonObject;
|
|
FKeys := FRow.Get('key').JsonValue as TJsonArray;
|
|
for FKeyBase in FKeys do
|
|
begin
|
|
FKey := FKeyBase as TJsonObject;
|
|
if not ACall(FLayer, FKey) then Exit;
|
|
end
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function GetKeyPredefinedLayer(layer, key: TJSONObject): TTouchLayoutPredefinedLayer;
|
|
var
|
|
pair: TJSONPair;
|
|
i: Integer;
|
|
begin
|
|
pair := key.Get('layer');
|
|
if pair <> nil
|
|
then Result := (pair.JsonValue as TJSONString).Value
|
|
else Result := (layer.Get('id').JsonValue as TJSONString).Value;
|
|
|
|
for i := 0 to High(PredefinedLayers) do
|
|
if PredefinedLayers[i] = Result then Exit;
|
|
|
|
Result := 'default';
|
|
end;
|
|
|
|
function TTouchLayout.Merge(ASource: TTouchLayout): Boolean;
|
|
|
|
|
|
|
|
procedure MergePlatform(const ASourcePlatformName, ADestPlatformName: string);
|
|
var
|
|
dstPlatform: TJSONObject;
|
|
begin
|
|
dstPlatform := FLayout.Get(ADestPlatformName).JsonValue as TJSONObject;
|
|
|
|
// For each key in the dstPlatform, find the equivalent in srcPlatform
|
|
// Copy the key caps in
|
|
|
|
EachKey(dstPlatform,
|
|
function(dstLayer, dstKey: TJSONObject): Boolean
|
|
var
|
|
srcPlatform: TJSONObject;
|
|
dstPredefinedLayer: TTouchLayoutPredefinedLayer;
|
|
begin
|
|
srcPlatform := ASource.FLayout.Get(ASourcePlatformName).JsonValue as TJSONObject;
|
|
dstPredefinedLayer := GetKeyPredefinedLayer(dstLayer, dstKey);
|
|
|
|
EachKey(srcPlatform,
|
|
function(srcLayer, srcKey: TJSONObject): Boolean
|
|
var
|
|
srcPredefinedLayer: TTouchLayoutPredefinedLayer;
|
|
v: TJSONString;
|
|
srcText: TJSONPair;
|
|
srcId: TJSONPair;
|
|
dstId: TJSONPair;
|
|
dstSp: TJSONPair;
|
|
begin
|
|
srcId := srcKey.Get('id');
|
|
dstId := dstKey.Get('id');
|
|
|
|
dstSp := dstKey.Get('sp');
|
|
if Assigned(dstSp) and (dstSp.JsonValue.ToString = '"1"') then // I3982
|
|
begin
|
|
// We don't override the template's special keys
|
|
Exit(True);
|
|
end;
|
|
|
|
if Assigned(srcId) and Assigned(dstId) and (srcId.JsonValue.ToString = dstId.JsonValue.ToString) then
|
|
begin
|
|
srcPredefinedLayer := GetKeyPredefinedLayer(srcLayer, srcKey);
|
|
if srcPredefinedLayer = dstPredefinedLayer then
|
|
begin
|
|
dstKey.RemovePair('text');
|
|
srcText := srcKey.Get('text');
|
|
if Assigned(srcText) then
|
|
begin
|
|
v := TJSONString.Create((srcText.JsonValue as TJsonString).Value);
|
|
dstKey.AddPair('text', v);
|
|
end;
|
|
end;
|
|
end;
|
|
Result := True;
|
|
end
|
|
);
|
|
Result := True;
|
|
end
|
|
);
|
|
end;
|
|
|
|
begin
|
|
if HasPlatform('desktop') then
|
|
begin
|
|
if ASource.HasPlatform('desktop') then MergePlatform('desktop', 'desktop')
|
|
else if ASource.HasPlatform('tablet') then MergePlatform('tablet', 'desktop')
|
|
else if ASource.HasPlatform('phone') then MergePlatform('phone', 'desktop');
|
|
end;
|
|
|
|
if HasPlatform('tablet') then
|
|
begin
|
|
if ASource.HasPlatform('tablet') then MergePlatform('tablet', 'tablet')
|
|
else if ASource.HasPlatform('phone') then MergePlatform('phone', 'tablet')
|
|
else if ASource.HasPlatform('desktop') then MergePlatform('desktop', 'tablet');
|
|
end;
|
|
|
|
if HasPlatform('phone') then
|
|
begin
|
|
if ASource.HasPlatform('phone') then MergePlatform('phone', 'phone')
|
|
else if ASource.HasPlatform('tablet') then MergePlatform('tablet', 'phone')
|
|
else if ASource.HasPlatform('desktop') then MergePlatform('desktop', 'phone');
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
constructor TTouchLayout.Create;
|
|
begin
|
|
inherited Create;
|
|
FData := TTouchLayoutData.Create(nil);
|
|
end;
|
|
|
|
destructor TTouchLayout.Destroy;
|
|
begin
|
|
FreeAndNil(FData);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TTouchLayout.GetLayoutPlatform(const Index: string): TJSONPair;
|
|
begin
|
|
Result := FLayout.Get(Index);
|
|
end;
|
|
|
|
function TTouchLayout.HasPlatform(const APlatform: string): Boolean;
|
|
begin
|
|
Result := FLayout.Get(APlatform) <> nil;
|
|
end;
|
|
|
|
function TTouchLayout.Save(AFormatted: Boolean): string; // I4655
|
|
var
|
|
str: TStringList;
|
|
begin
|
|
if not Assigned(FLayout) then
|
|
Result := ''
|
|
else if AFormatted then // I4655
|
|
begin
|
|
str := TStringList.Create;
|
|
try
|
|
PrettyPrintJSON(FLayout, str);
|
|
Result := str.Text;
|
|
finally
|
|
str.Free;
|
|
end;
|
|
end
|
|
else
|
|
Result := JSONToString(FLayout);
|
|
end;
|
|
|
|
procedure TTouchLayout.Validate(v: TJSONObject);
|
|
|
|
procedure Failure(const Message: string; Source: TJSONAncestor = nil);
|
|
begin
|
|
raise ETouchLayoutValidate.Create('Validation failure: '+Message);
|
|
end;
|
|
|
|
procedure TouchAssert(v: Boolean; const Message: string; Source: TJSONAncestor = nil);
|
|
begin
|
|
if not v then
|
|
Failure(Message, Source);
|
|
end;
|
|
|
|
procedure ValidateObject(AObject: TJSONObject; PDef: PJSONDef; PDefSize: Integer);
|
|
var
|
|
pair: TJSONPair;
|
|
i: Integer;
|
|
Found: Boolean;
|
|
valueArray: TJSONArray;
|
|
member: TJSONValue;
|
|
Def: TJSONDefArray;
|
|
begin
|
|
// Validate required members
|
|
|
|
SetLength(Def, PDefSize);
|
|
for i := 0 to PDefSize - 1 do
|
|
begin
|
|
Def[i] := PDef^;
|
|
Inc(PDef);
|
|
end;
|
|
|
|
for i := Low(Def) to High(Def) do
|
|
begin
|
|
TouchAssert(not Def[i].Required or (AObject.Get(Def[i].Name) <> nil),
|
|
'Member '+Def[i].Name+' is required', AObject);
|
|
end;
|
|
|
|
// Validate all members
|
|
|
|
for pair in AObject do
|
|
begin
|
|
Found := False;
|
|
for i := Low(Def) to High(Def) do
|
|
begin
|
|
if Def[i].Name = pair.JsonString.Value then
|
|
begin
|
|
Found := True;
|
|
|
|
TouchAssert(pair.JsonValue is Def[i].ClassType,
|
|
pair.JsonString.Value + ' is not a valid type');
|
|
|
|
if Def[i].ClassType = TJSONArray then
|
|
begin
|
|
valueArray := pair.JsonValue as TJSONArray;
|
|
for member in valueArray do
|
|
begin
|
|
if Assigned(Def[i].Value) then
|
|
begin
|
|
TouchAssert(member is TJSONObject, 'Array members must be objects', member);
|
|
ValidateObject(member as TJSONObject, Def[i].Value, Def[i].ValueSize);
|
|
end;
|
|
end;
|
|
end
|
|
else if Def[i].ClassType = TJSONObject then
|
|
begin
|
|
ValidateObject(pair.JsonValue as TJSONObject, Def[i].Value, Def[i].ValueSize);
|
|
end;
|
|
Break;
|
|
end;
|
|
end;
|
|
TouchAssert(Found, 'Member '+pair.JsonString.Value+' is not valid', AObject);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
TouchAssert(Assigned(v), 'Layout file must not be empty');
|
|
TouchAssert(v.Count > 0, 'At least one layout is required', FLayout);
|
|
|
|
ValidateObject(v, @LayoutDef[0], Length(LayoutDef));
|
|
end;
|
|
|
|
function TTouchLayout.Write(AFormatted: Boolean): string;
|
|
var
|
|
o: TJSONObject;
|
|
begin
|
|
o := TJSONObject.Create;
|
|
FData.Write(o);
|
|
FreeAndNil(FLayout);
|
|
FLayout := o;
|
|
FreeAndNil(FData);
|
|
FData := TTouchLayoutData.Create(nil);
|
|
FData.Read(FLayout);
|
|
|
|
Result := Save(AFormatted);
|
|
end;
|
|
|
|
{ TTouchLayoutObject }
|
|
|
|
function TTouchLayoutObject.GetValue(const name: string; var value: TJSONValue): Boolean;
|
|
var
|
|
p: TJSONPair;
|
|
begin
|
|
value := nil;
|
|
p := FJSON.Get(name);
|
|
if not Assigned(p) then Exit(False);
|
|
value := p.JsonValue;
|
|
Result := True;
|
|
end;
|
|
|
|
function TTouchLayoutObject.GetValue(const name: string; var value: string): Boolean;
|
|
var
|
|
v: TJSONValue;
|
|
begin
|
|
value := '';
|
|
if not GetValue(name, v) then Exit(False);
|
|
value := v.Value;
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TTouchLayoutObject.AddJSONValue(JSON: TJSONObject; const name: string; value: Boolean);
|
|
begin
|
|
JSON.AddPair(name, TJSONBool.Create(value));
|
|
end;
|
|
|
|
constructor TTouchLayoutObject.Create(AParent: TTouchLayoutObject);
|
|
begin
|
|
inherited Create;
|
|
FParent := AParent;
|
|
end;
|
|
|
|
function TTouchLayoutObject.GetValue(const name: string; var value: Integer): Boolean;
|
|
var
|
|
v: string;
|
|
begin
|
|
value := 0;
|
|
Result := GetValue(name, v) and TryStrToInt(v, value);
|
|
end;
|
|
|
|
procedure TTouchLayoutObject.Read(JSON: TJSONObject);
|
|
begin
|
|
FJSON := JSON;
|
|
DoRead;
|
|
end;
|
|
|
|
procedure TTouchLayoutObject.ReadArray(const name: string;
|
|
cls: TTouchLayoutClass; add: TTouchLayoutAddProc);
|
|
var
|
|
a: TJSONArray;
|
|
i: Integer;
|
|
ask: TTouchLayoutObject;
|
|
v: TJSONValue;
|
|
begin
|
|
if GetValue(name, v) and (v is TJSONArray) then
|
|
begin
|
|
a := v as TJSONArray;
|
|
for i := 0 to a.Count - 1 do
|
|
begin
|
|
ask := cls.Create(Self);
|
|
ask.Read(a.Items[i] as TJSONObject);
|
|
add(ask);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTouchLayoutObject.Write(JSON: TJSONObject);
|
|
begin
|
|
DoWrite(JSON);
|
|
end;
|
|
|
|
function TTouchLayoutObject.GetValue(const name: string;
|
|
var value: Boolean): Boolean;
|
|
var
|
|
p: TJSONPair;
|
|
begin
|
|
value := False;
|
|
p := FJSON.Get(name);
|
|
if not Assigned(p) then Exit(False);
|
|
Result := p.JsonValue.TryGetValue<Boolean>(value);
|
|
end;
|
|
|
|
{ TTouchLayoutSubKey }
|
|
|
|
procedure TTouchLayoutSubKey.DoRead;
|
|
begin
|
|
GetValue('nextlayer', FNextLayer);
|
|
GetValue('fontsize', FFontSize);
|
|
GetValue('layer', FLayer);
|
|
GetValue('width', FWidth);
|
|
GetValue('dk', FDk);
|
|
GetValue('font', FFont);
|
|
GetValue('id', FId);
|
|
GetValue('pad', FPad);
|
|
GetValue('sp', FSp);
|
|
GetValue('text', FText);
|
|
GetValue('default', FDefault);
|
|
end;
|
|
|
|
procedure TTouchLayoutObject.AddJSONValue(JSON: TJSONObject; const name, value: string);
|
|
begin
|
|
if value <> '' then
|
|
JSON.AddPair(name, value);
|
|
end;
|
|
|
|
procedure TTouchLayoutObject.AddJSONValue(JSON: TJSONObject; const name: string; value: Integer);
|
|
begin
|
|
if value <> 0 then
|
|
JSON.AddPair(name, TJSONNumber.Create(value));
|
|
end;
|
|
|
|
procedure TTouchLayoutSubKey.DoWrite(JSON: TJSONObject);
|
|
begin
|
|
AddJSONValue(JSON, 'nextlayer', FNextLayer);
|
|
AddJSONValue(JSON, 'fontsize', FFontSize);
|
|
AddJSONValue(JSON, 'layer', FLayer);
|
|
if FWidth <> 0 then AddJSONValue(JSON, 'width', IntToStr(FWidth));
|
|
AddJSONValue(JSON, 'dk', FDk);
|
|
AddJSONValue(JSON, 'font', FFont);
|
|
AddJSONValue(JSON, 'id', FId);
|
|
if FPad <> 0 then AddJSONValue(JSON, 'pad', IntToStr(FPad));
|
|
if FSp <> 0 then AddJSONValue(JSON, 'sp', IntToStr(FSp));
|
|
AddJSONValue(JSON, 'text', FText);
|
|
if FDefault then AddJSONValue(JSON, 'default', FDefault);
|
|
end;
|
|
|
|
function TTouchLayoutSubKey.GetSpT: TTouchKeyType; // I4119
|
|
begin
|
|
Result := TTouchKeyType(FSp);
|
|
end;
|
|
|
|
procedure TTouchLayoutSubKey.SetSpT(const Value: TTouchKeyType); // I4119
|
|
begin
|
|
FSp := Ord(Value);
|
|
end;
|
|
|
|
{ TTouchLayoutPlatform }
|
|
|
|
constructor TTouchLayoutPlatform.Create(AParent: TTouchLayoutObject);
|
|
begin
|
|
inherited Create(AParent);
|
|
FLayers := TTouchLayoutLayers.Create;
|
|
end;
|
|
|
|
destructor TTouchLayoutPlatform.Destroy;
|
|
begin
|
|
FreeAndNil(FLayers);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TTouchLayoutPlatform.DoRead;
|
|
var
|
|
s: string;
|
|
begin
|
|
GetValue('font', FFont);
|
|
GetValue('fontsize', FFontSize);
|
|
GetValue('displayUnderlying', FDisplayUnderlying);
|
|
if GetValue('defaultHint', s)
|
|
then FDefaultHint := DefaultHintFromString(s)
|
|
else FDefaultHint := tldhNone;
|
|
ReadArray('layer', TTouchLayoutLayer,
|
|
procedure (obj: TTouchLayoutObject) begin FLayers.Add(obj as TTouchLayoutLayer); end);
|
|
end;
|
|
|
|
procedure TTouchLayoutPlatform.DoWrite(JSON: TJSONObject);
|
|
var
|
|
v: TJSONArray;
|
|
o: TJSONObject;
|
|
i: Integer;
|
|
begin
|
|
AddJSONValue(JSON, 'font', FFont);
|
|
AddJSONValue(JSON, 'fontsize', FFontSize);
|
|
AddJSONValue(JSON, 'displayUnderlying', FDisplayUnderlying);
|
|
if FDefaultHint <> tldhNone then
|
|
AddJSONValue(JSON, 'defaultHint', STouchLayoutDefaultHintName[FDefaultHint]);
|
|
|
|
if FLayers.Count > 0 then
|
|
begin
|
|
v := TJSONArray.Create;
|
|
for i := 0 to FLayers.Count - 1 do
|
|
begin
|
|
o := TJSONObject.Create;
|
|
FLayers[i].Write(o);
|
|
v.Add(o);
|
|
end;
|
|
JSON.AddPair('layer', v);
|
|
end;
|
|
end;
|
|
|
|
{ TTouchLayoutData }
|
|
|
|
constructor TTouchLayoutData.Create(AParent: TTouchLayoutObject);
|
|
begin
|
|
inherited Create(AParent);
|
|
FPlatforms := TTouchLayoutPlatforms.Create;
|
|
end;
|
|
|
|
destructor TTouchLayoutData.Destroy;
|
|
begin
|
|
FreeAndNil(FPlatforms);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TTouchLayoutData.DoRead;
|
|
procedure ReadPlatform(name: string);
|
|
var
|
|
v: TJSONValue;
|
|
p: TTouchLayoutPlatform;
|
|
begin
|
|
if GetValue(name, v) and (v is TJSONObject) then
|
|
begin
|
|
p := TTouchLayoutPlatform.Create(Self);
|
|
p.FName := name;
|
|
p.Read(v as TJSONObject);
|
|
FPlatforms.Add(p);
|
|
end;
|
|
end;
|
|
begin
|
|
ReadPlatform('phone');
|
|
ReadPlatform('tablet');
|
|
ReadPlatform('desktop');
|
|
end;
|
|
|
|
procedure TTouchLayoutData.DoWrite(JSON: TJSONObject);
|
|
var
|
|
o: TJSONObject;
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to FPlatforms.Count - 1 do
|
|
begin
|
|
o := TJSONObject.Create;
|
|
FPlatforms[i].Write(o);
|
|
JSON.AddPair(FPlatforms[i].Name, o);
|
|
end;
|
|
end;
|
|
|
|
{ TTouchLayoutKey }
|
|
|
|
constructor TTouchLayoutKey.Create(AParent: TTouchLayoutObject);
|
|
begin
|
|
inherited Create(AParent);
|
|
FSk := TTouchLayoutSubKeys.Create;
|
|
FFlick := TTouchLayoutFlicks.Create;
|
|
FMultiTap := TTouchLayoutMultiTaps.Create;
|
|
end;
|
|
|
|
destructor TTouchLayoutKey.Destroy;
|
|
begin
|
|
FreeAndNil(FSk);
|
|
FreeAndNil(FFlick);
|
|
FreeAndNil(FMultiTap);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TTouchLayoutKey.DoRead;
|
|
var
|
|
v: TJSONValue;
|
|
o: TJSONObject;
|
|
d: TTouchLayoutFlickDirection;
|
|
sk: TTouchLayoutSubKey;
|
|
begin
|
|
GetValue('nextlayer', FNextLayer);
|
|
GetValue('fontsize', FFontSize);
|
|
GetValue('layer', FLayer);
|
|
GetValue('width', FWidth);
|
|
GetValue('dk', FDk);
|
|
GetValue('font', FFont);
|
|
GetValue('id', FId);
|
|
GetValue('pad', FPad);
|
|
GetValue('sp', FSp);
|
|
GetValue('text', FText);
|
|
GetValue('hint', FHint);
|
|
ReadArray('sk', TTouchLayoutSubKey,
|
|
procedure (obj: TTouchLayoutObject) begin FSk.Add(obj as TTouchLayoutSubKey); end);
|
|
if GetValue('flick', v) and (v is TJSONObject) then
|
|
begin
|
|
o := v as TJSONObject;
|
|
for d := Low(TTouchLayoutFlickDirection) to High(TTouchLayoutFlickDirection) do
|
|
begin
|
|
v := o.GetValue(STouchLayoutFlickDirectionName[d]);
|
|
if Assigned(v) and (v is TJSONObject) then
|
|
begin
|
|
sk := TTouchLayoutSubKey.Create(Self);
|
|
sk.Read(v as TJSONObject);
|
|
FFlick.Add(d, sk);
|
|
end;
|
|
end;
|
|
end;
|
|
ReadArray('multitap', TTouchLayoutSubKey,
|
|
procedure (obj: TTouchLayoutObject) begin FMultiTap.Add(obj as TTouchLayoutSubKey); end);
|
|
end;
|
|
|
|
procedure TTouchLayoutKey.DoWrite(JSON: TJSONObject);
|
|
var
|
|
i: Integer;
|
|
v: TJSONArray;
|
|
o, f: TJSONObject;
|
|
d: TTouchLayoutFlickDirection;
|
|
begin
|
|
AddJSONValue(JSON, 'nextlayer', FNextLayer);
|
|
AddJSONValue(JSON, 'fontsize', FFontSize);
|
|
AddJSONValue(JSON, 'layer', FLayer);
|
|
if FWidth <> 0 then AddJSONValue(JSON, 'width', IntToStr(FWidth));
|
|
AddJSONValue(JSON, 'dk', FDk);
|
|
AddJSONValue(JSON, 'font', FFont);
|
|
AddJSONValue(JSON, 'id', FId);
|
|
if FPad <> 0 then AddJSONValue(JSON, 'pad', IntToStr(FPad));
|
|
if FSp <> 0 then AddJSONValue(JSON, 'sp', IntToStr(FSp));
|
|
AddJSONValue(JSON, 'text', FText);
|
|
AddJSONValue(JSON, 'hint', FHint);
|
|
|
|
if FSk.Count > 0 then
|
|
begin
|
|
v := TJSONArray.Create;
|
|
for i := 0 to FSk.Count - 1 do
|
|
begin
|
|
o := TJSONObject.Create;
|
|
FSk[i].Write(o);
|
|
v.Add(o);
|
|
end;
|
|
JSON.AddPair('sk', v);
|
|
end;
|
|
|
|
if FFlick.Count > 0 then
|
|
begin
|
|
f := TJSONObject.Create;
|
|
for d in FFlick.Keys do
|
|
begin
|
|
o := TJSONObject.Create;
|
|
FFlick[d].Write(o);
|
|
f.AddPair(STouchLayoutFlickDirectionName[d], o);
|
|
end;
|
|
JSON.AddPair('flick', f);
|
|
end;
|
|
|
|
if FMultiTap.Count > 0 then
|
|
begin
|
|
v := TJSONArray.Create;
|
|
for i := 0 to FMultiTap.Count - 1 do
|
|
begin
|
|
o := TJSONObject.Create;
|
|
FMultiTap[i].Write(o);
|
|
v.Add(o);
|
|
end;
|
|
JSON.AddPair('multitap', v);
|
|
end;
|
|
|
|
end;
|
|
|
|
function TTouchLayoutKey.GetSpT: TTouchKeyType; // I4119
|
|
begin
|
|
Result := TTouchKeyType(FSp);
|
|
end;
|
|
|
|
procedure TTouchLayoutKey.SetSpT(const Value: TTouchKeyType); // I4119
|
|
begin
|
|
FSp := Ord(Value);
|
|
end;
|
|
|
|
{ TTouchLayoutRow }
|
|
|
|
constructor TTouchLayoutRow.Create(AParent: TTouchLayoutObject);
|
|
begin
|
|
inherited Create(AParent);
|
|
FKeys := TTouchLayoutKeys.Create;
|
|
end;
|
|
|
|
destructor TTouchLayoutRow.Destroy;
|
|
begin
|
|
FreeAndNil(FKeys);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TTouchLayoutRow.DoRead;
|
|
begin
|
|
GetValue('id', FId);
|
|
ReadArray('key', TTouchLayoutKey,
|
|
procedure (obj: TTouchLayoutObject) begin FKeys.Add(obj as TTouchLayoutKey); end);
|
|
end;
|
|
|
|
procedure TTouchLayoutRow.DoWrite(JSON: TJSONObject);
|
|
var
|
|
v: TJSONArray;
|
|
o: TJSONObject;
|
|
i: Integer;
|
|
begin
|
|
AddJSONValue(JSON, 'id', FId);
|
|
|
|
if FKeys.Count > 0 then
|
|
begin
|
|
v := TJSONArray.Create;
|
|
for i := 0 to FKeys.Count - 1 do
|
|
begin
|
|
o := TJSONObject.Create;
|
|
FKeys[i].Write(o);
|
|
v.Add(o);
|
|
end;
|
|
JSON.AddPair('key', v);
|
|
end;
|
|
end;
|
|
|
|
function TTouchLayoutRow.FindKeyById(Id: string): TTouchLayoutKey;
|
|
var
|
|
k: TTouchLayoutKey;
|
|
begin
|
|
for k in Keys do
|
|
begin
|
|
if SameText(k.Id, Id) then
|
|
Exit(k);
|
|
end;
|
|
|
|
Result := nil;
|
|
end;
|
|
|
|
{ TTouchLayoutLayer }
|
|
|
|
constructor TTouchLayoutLayer.Create(AParent: TTouchLayoutObject);
|
|
begin
|
|
inherited Create(AParent);
|
|
FRows := TTouchLayoutRows.Create;
|
|
end;
|
|
|
|
destructor TTouchLayoutLayer.Destroy;
|
|
begin
|
|
FreeAndNil(FRows);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TTouchLayoutLayer.DoRead;
|
|
begin
|
|
GetValue('id', FId);
|
|
ReadArray('row', TTouchLayoutRow,
|
|
procedure (obj: TTouchLayoutObject) begin FRows.Add(obj as TTouchLayoutRow); end);
|
|
end;
|
|
|
|
procedure TTouchLayoutLayer.DoWrite(JSON: TJSONObject);
|
|
var
|
|
v: TJSONArray;
|
|
o: TJSONObject;
|
|
i: Integer;
|
|
begin
|
|
AddJSONValue(JSON, 'id', FId);
|
|
|
|
if FRows.Count > 0 then
|
|
begin
|
|
v := TJSONArray.Create;
|
|
for i := 0 to FRows.Count - 1 do
|
|
begin
|
|
o := TJSONObject.Create;
|
|
FRows[i].Write(o);
|
|
v.Add(o);
|
|
end;
|
|
JSON.AddPair('row', v);
|
|
end;
|
|
end;
|
|
|
|
function TTouchLayoutLayer.FindKeyById(Id: string): TTouchLayoutKey;
|
|
var
|
|
r: TTouchLayoutRow;
|
|
begin
|
|
for r in Rows do
|
|
begin
|
|
Result := r.FindKeyById(Id);
|
|
if Assigned(Result) then Exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
{ TTouchLayoutLayers }
|
|
|
|
function TTouchLayoutLayers.IndexOfId(const AId: string): Integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Count - 1 do
|
|
if Items[i].Id = AId then
|
|
Exit(i);
|
|
Result := -1;
|
|
end;
|
|
|
|
function DefaultHintFromString(const s: string): TTouchLayoutDefaultHint;
|
|
begin
|
|
for Result := Low(TTouchLayoutDefaultHint) to High(TTouchLayoutDefaultHint) do
|
|
if s = STouchLayoutDefaultHintName[Result] then Exit;
|
|
Result := tldhNone;
|
|
end;
|
|
|
|
end.
|