1
0
forked from 0ad/0ad

Start phasing out the use of CParser in the GUI. Makes GUI parsing generally faster and stricter while adding better tests and debug information.

This was SVN commit r15213.
This commit is contained in:
JoshuaJB 2014-05-25 03:16:52 +00:00
parent 9c908a5f46
commit 5ce12c2263
6 changed files with 221 additions and 178 deletions

View File

@ -532,7 +532,7 @@
</object>
<object name="tradeStatistics" size="20 90 100%-20 168">
<object name="landTraders" size="0 0 100% 50%" type="text" style="ModernLabelText" text_align="left" ghost="true" />
<object name="landTraders" size="0 0 100% 50%" type="text" style="ModernLabelText" text_align="left" ghost="true" />
<object name="shipTraders" size="0 50% 100% 100%" type="text" style="ModernLabelText" text_align="left" ghost="true" />
</object>

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -21,8 +21,7 @@ GUI base
#include "precompiled.h"
#include <string>
#include "ps/CLogger.h"
#include "GUI.h"
//--------------------------------------------------------
@ -62,118 +61,86 @@ CRect CClientArea::GetClientArea(const CRect &parent) const
bool CClientArea::SetClientArea(const CStr& Value)
{
// This might lack incredible speed, but since all XML files
// are read at startup, reading 100 client areas will be
// negligible in the loading time.
/* ClientAreas contain a left, top, right, and bottom
* for example: "50%-150 10%+9 50%+150 10%+25" means
* the left edge is at 50% minus 150 pixels, the top
* edge is at 10% plus 9 pixels, the right edge is at
* 50% plus 150 pixels, and the bottom edge is at 10%
* plus 25 pixels.
* All four coordinates are required and can be
* defined only in pixels, only in percents, or some
* combination of both.
*/
// Setup parser to parse the value
// Check the input is only numeric
const char* input = Value.c_str();
CStr buffer = "";
unsigned int coord = 0;
float pixels[4] = {0, 0, 0, 0};
float percents[4] = {0, 0, 0, 0};
for (unsigned int i = 0; i < Value.length(); i++)
{
switch (input[i])
{
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
buffer.push_back(input[i]);
break;
case '+':
pixels[coord] += buffer.ToFloat();
buffer = "+";
break;
case '-':
pixels[coord] += buffer.ToFloat();
buffer = "-";
break;
case '%':
percents[coord] += buffer.ToFloat();
buffer = "";
break;
case ' ':
pixels[coord] += buffer.ToFloat();
buffer = "";
coord++;
break;
default:
LOGWARNING(L"ClientArea definitions may only contain numerics. Your input: '%s'", Value.c_str());
return false;
}
if (coord > 3)
{
LOGWARNING(L"Too many CClientArea parameters (4 max). Your input: '%s'", Value.c_str());
return false;
}
}
// One of the four values:
// will give outputs like (in argument):
// (200) <== no percent, just the first $value
// (200) (percent) <== just the percent
// (200) (percent) (100) <== percent PLUS pixel
// (200) (percent) (-100) <== percent MINUS pixel
// (200) (percent) (100) (-100) <== Both PLUS and MINUS are used, INVALID
/*
string one_value = "_[-_$arg(_minus)]$value[$arg(percent)%_[+_$value]_[-_$arg(_minus)$value]_]";
string four_values = one_value + "$arg(delim)" +
one_value + "$arg(delim)" +
one_value + "$arg(delim)" +
one_value + "$arg(delim)_"; // it's easier to just end with another delimiter
*/
// Don't use the above strings, because they make this code go very slowly
const char* four_values =
"_[-_$arg(_minus)]$value[$arg(percent)%_[+_$value]_[-_$arg(_minus)$value]_]" "$arg(delim)"
"_[-_$arg(_minus)]$value[$arg(percent)%_[+_$value]_[-_$arg(_minus)$value]_]" "$arg(delim)"
"_[-_$arg(_minus)]$value[$arg(percent)%_[+_$value]_[-_$arg(_minus)$value]_]" "$arg(delim)"
"_[-_$arg(_minus)]$value[$arg(percent)%_[+_$value]_[-_$arg(_minus)$value]_]" "$arg(delim)"
"_";
CParser& parser (CParserCache::Get(four_values));
CParserLine line;
line.ParseString(parser, Value);
if (!line.m_ParseOK)
if (coord < 3)
{
LOGWARNING(L"Too few CClientArea parameters (4 min). Your input: '%s'", Value.c_str());
return false;
int arg_count[4] = {0,0,0,0}; // argument counts for the four values
int arg_start[4] = {0,0,0,0}; // location of first argument, [0] is always 0
// Divide into the four piles (delimiter is an argument named "delim")
for (int i=0, valuenr=0; i<(int)line.GetArgCount(); ++i)
{
std::string str;
line.GetArgString(i, str);
if (str == "delim")
{
if (valuenr==0)
{
arg_count[0] = i;
arg_start[1] = i+1;
}
else
{
if (valuenr!=3)
{
ENSURE(valuenr <= 2);
arg_start[valuenr+1] = i+1;
arg_count[valuenr] = arg_start[valuenr+1] - arg_start[valuenr] - 1;
}
else
arg_count[3] = (int)line.GetArgCount() - arg_start[valuenr] - 1;
}
++valuenr;
}
}
// Iterate argument
// This is the scheme:
// 1 argument = Just pixel value
// 2 arguments = Just percent value
// 3 arguments = percent and pixel
// 4 arguments = INVALID
// Now that we're at the end of the string, flush the remaining buffer.
pixels[coord] += buffer.ToFloat();
// Default to 0
float values[4][2] = {{0.f,0.f},{0.f,0.f},{0.f,0.f},{0.f,0.f}};
for (int v=0; v<4; ++v)
{
if (arg_count[v] == 1)
{
std::string str;
line.GetArgString(arg_start[v], str);
if (!line.GetArgFloat(arg_start[v], values[v][1]))
return false;
}
else
if (arg_count[v] == 2)
{
if (!line.GetArgFloat(arg_start[v], values[v][0]))
return false;
}
else
if (arg_count[v] == 3)
{
if (!line.GetArgFloat(arg_start[v], values[v][0]) ||
!line.GetArgFloat(arg_start[v]+2, values[v][1]))
return false;
}
else return false;
}
// Now store the values[][] in the right place
pixel.left = values[0][1];
pixel.top = values[1][1];
pixel.right = values[2][1];
pixel.bottom = values[3][1];
percent.left = values[0][0];
percent.top = values[1][0];
percent.right = values[2][0];
percent.bottom = values[3][0];
// Now store the coords in the right place
pixel.left = pixels[0];
pixel.top = pixels[1];
pixel.right = pixels[2];
pixel.bottom = pixels[3];
percent.left = percents[0];
percent.top = percents[1];
percent.right = percents[2];
percent.bottom = percents[3];
return true;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -23,7 +23,6 @@ GUI utilities
#include "GUI.h"
#include "GUIManager.h"
#include "maths/Matrix3D.h"
#include "ps/Parser.h"
extern int g_xres, g_yres;
@ -61,28 +60,35 @@ bool __ParseString<float>(const CStrW& Value, float &Output)
template <>
bool __ParseString<CRect>(const CStrW& Value, CRect &Output)
{
// Use the parser to parse the values
CParser& parser (CParserCache::Get("_$value_$value_$value_$value_"));
CParserLine line;
line.ParseString(parser, Value.ToUTF8());
if (!line.m_ParseOK)
const unsigned int NUM_COORDS = 4;
float coords[NUM_COORDS];
std::wstringstream stream;
stream.str(Value);
// Parse each coordinate
for (unsigned int i = 0; i < NUM_COORDS; i++)
{
// Parsing failed
return false;
}
float values[4];
for (int i=0; i<4; ++i)
{
if (!line.GetArgFloat(i, values[i]))
if (stream.eof())
{
// Parsing failed
LOGWARNING(L"Too few CRect parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
stream >> coords[i];
if ((stream.rdstate() & std::wstringstream::failbit) != 0)
{
LOGWARNING(L"Unable to parse CRect parameters. Your input: '%s'", Value.ToUTF8().c_str());
return false;
}
}
if (!stream.eof())
{
LOGWARNING(L"Too many CRect parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
// Finally the rectangle values
Output = CRect(values[0], values[1], values[2], values[3]);
Output = CRect(coords[0], coords[1], coords[2], coords[3]);
return true;
}
@ -119,57 +125,69 @@ bool __ParseString<CColor>(const CStrW& Value, CColor &Output)
template <>
bool __ParseString<CSize>(const CStrW& Value, CSize &Output)
{
// Use the parser to parse the values
CParser& parser (CParserCache::Get("_$value_$value_"));
CParserLine line;
line.ParseString(parser, Value.ToUTF8());
if (!line.m_ParseOK)
const unsigned int NUM_COORDS = 2;
float coords[NUM_COORDS];
std::wstringstream stream;
stream.str(Value);
// Parse each coordinate
for (unsigned int i = 0; i < NUM_COORDS; i++)
{
// Parsing failed
if (stream.eof())
{
LOGWARNING(L"Too few CSize parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
stream >> coords[i];
if ((stream.rdstate() & std::wstringstream::failbit) != 0)
{
LOGWARNING(L"Unable to parse CSize parameters. Your input: '%s'", Value.ToUTF8().c_str());
return false;
}
}
Output.cx = coords[0];
Output.cy = coords[1];
if (!stream.eof())
{
LOGWARNING(L"Too many CSize parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
float x, y;
// x
if (!line.GetArgFloat(0, x))
{
// TODO Gee: Parsing failed
return false;
}
// y
if (!line.GetArgFloat(1, y))
{
// TODO Gee: Parsing failed
return false;
}
Output.cx = x;
Output.cy = y;
return true;
}
template <>
bool __ParseString<CPos>(const CStrW& Value, CPos &Output)
{
CParser& parser (CParserCache::Get("_[-$arg(_minus)]$value_[-$arg(_minus)]$value_"));
const unsigned int NUM_COORDS = 2;
float coords[NUM_COORDS];
std::wstringstream stream;
stream.str(Value);
// Parse each coordinate
for (unsigned int i = 0; i < NUM_COORDS; i++)
{
if (stream.eof())
{
LOGWARNING(L"Too few CPos parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
stream >> coords[i];
if ((stream.rdstate() & std::wstringstream::failbit) != 0)
{
LOGWARNING(L"Unable to parse CPos parameters. Your input: '%s'", Value.ToUTF8().c_str());
return false;
}
}
CParserLine line;
line.ParseString(parser, Value.ToUTF8());
if (!line.m_ParseOK)
return false;
Output.x = coords[0];
Output.y = coords[1];
float x, y;
if (!line.GetArgFloat(0, x))
if (!stream.eof())
{
LOGWARNING(L"Too many CPos parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
if (!line.GetArgFloat(1, y))
return false;
Output.x = x;
Output.y = y;
}
return true;
}

View File

@ -37,7 +37,6 @@ GUI util
// Includes / Compiler directives
//--------------------------------------------------------
#include "GUIbase.h"
#include "ps/Parser.h"
// TODO Gee: New
#include "ps/Overlay.h"
#include "CGUI.h"

View File

@ -22,8 +22,6 @@ IGUIObject
#include "precompiled.h"
#include "GUI.h"
#include "ps/Parser.h"
#include "gui/scripting/JSInterface_IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "scriptinterface/ScriptInterface.h"

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,26 +18,87 @@
#include "lib/self_test.h"
#include "gui/GUIbase.h"
#include "gui/GUIutil.h"
#include "ps/CLogger.h"
class TestGuiParseString : public CxxTest::TestSuite
{
public:
void test_clientarea()
{
TestLogger nolog;
CClientArea ca;
TS_ASSERT(ca.SetClientArea("0 1 2 3"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, 1, 2, 3), CRect(0, 0, 0, 0)));
TS_ASSERT(ca.SetClientArea("5% 10%+1 20%-2 3"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, 1, -2, 3), CRect(5, 10, 20, 0)));
// Test only pixels
TS_ASSERT(ca.SetClientArea("0.0 -10 20.0 -30"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, -10, 20, -30), CRect(0, 0, 0, 0)));
TS_ASSERT(!ca.SetClientArea("0+5% 1 2 3"));
// Test only pixels, but with math
TS_ASSERT(ca.SetClientArea("0 -100-10+100 20+200-200 -30"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, -10, 20, -30), CRect(0, 0, 0, 0)));
TS_ASSERT(!ca.SetClientArea("5%+10-10 1 2 3"));
// Test only percent
TS_ASSERT(ca.SetClientArea("-5% 10.0% -20% 30.0%"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, 0, 0, 0), CRect(-5, 10, -20, 30)));
TS_ASSERT(ca.SetClientArea("5% 10%-1 -20%-2 3"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, -1, -2, 3), CRect(5, 10, -20, 0)));
// Test only percent, but with math
TS_ASSERT(ca.SetClientArea("15%-5%-15% 10% -20% 30%+500%-500%"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, 0, 0, 0), CRect(-5, 10, -20, 30)));
TS_ASSERT(!ca.SetClientArea("5% 10%+1 -20%-2 3")); // parser bug
// Test mixed
TS_ASSERT(ca.SetClientArea("5% -10 -20% 30"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, -10, 0, 30), CRect(5, 0, -20, 0)));
// Test mixed with math
TS_ASSERT(ca.SetClientArea("5%+10%-10% 30%-10-30% 50-20%-50 30-100+100"));
TS_ASSERT_EQUALS(ca, CClientArea(CRect(0, -10, 0, 30), CRect(5, 0, -20, 0)));
// Test for fail with too many/few parameters
TS_ASSERT(!ca.SetClientArea("10 20 30 40 50"));
TS_ASSERT(!ca.SetClientArea("10 20 30"));
// Test for fail with garbage data
TS_ASSERT(!ca.SetClientArea("Hello world!"));
TS_ASSERT(!ca.SetClientArea("abc 123 xyz 789"));
TS_ASSERT(!ca.SetClientArea("300 wide, 400 high"));
}
void test_rect()
{
TestLogger nolog;
CRect test;
TS_ASSERT(__ParseString(CStrW(L"0.0 10.0 20.0 30.0"), test));
TS_ASSERT_EQUALS(CRect(0.0, 10.0, 20.0, 30.0), test);
TS_ASSERT(!__ParseString(CStrW(L"0 10 20"), test));
TS_ASSERT(!__ParseString(CStrW(L"0 10 20 30 40"), test));
TS_ASSERT(!__ParseString(CStrW(L"0,0 10,0 20,0 30,0"), test));
}
void test_size()
{
TestLogger nolog;
CSize test;
TS_ASSERT(__ParseString(CStrW(L"0.0 10.0"), test));
TS_ASSERT_EQUALS(CSize(0.0, 10.0), test);
TS_ASSERT(!__ParseString(CStrW(L"0"), test));
TS_ASSERT(!__ParseString(CStrW(L"0 10 20"), test));
TS_ASSERT(!__ParseString(CStrW(L"0,0 10,0"), test));
}
void test_pos()
{
TestLogger nolog;
CPos test;
TS_ASSERT(__ParseString(CStrW(L"0.0 10.0"), test));
TS_ASSERT_EQUALS(CPos(0.0, 10.0), test);
TS_ASSERT(!__ParseString(CStrW(L"0"), test));
TS_ASSERT(!__ParseString(CStrW(L"0 10 20"), test));
TS_ASSERT(!__ParseString(CStrW(L"0,0 10,0"), test));
}
};