Files
oboe/osx/dialogxml/dialog.cpp

1352 lines
40 KiB
C++

/*
* dialog.cpp
* BoE
*
* Created by Celtic Minstrel on 11/05/09.
*
*/
#include <cmath>
#include <stdexcept>
#include <boost/type_traits/is_pointer.hpp>
#include "dialog.h"
#include "graphtool.h"
#include "soundtool.h"
using namespace std;
using namespace ticpp;
#include "pict.h"
#include "button.h"
#include "field.h"
#include "message.h"
#include "scrollbar.h"
#include "winutil.h"
#include "mathutil.h"
#include "cursors.h"
// TODO: Would be nice if I could avoid depending on mainPtr
extern sf::RenderWindow mainPtr;
extern cursor_type current_cursor;
extern sf::Texture bg_gworld;
extern bool play_sounds;
const short cDialog::BG_DARK = 5, cDialog::BG_LIGHT = 16;
short cDialog::defaultBackground = cDialog::BG_DARK;
static std::string generateRandomString(){
// Not bothering to seed, because it doesn't actually matter if it's truly random.
// Though, this will be called after srand() is called in main() anyway.
int n_chars = rand() % 100;
std::string s = "$";
while(n_chars > 0){
s += char(rand() % 96) + ' '; // was 223 ...
n_chars--;
}
return s;
}
template<> pair<string,cPict*> cDialog::parse(Element& who /*pict*/){
std::pair<std::string,cPict*> p;
Iterator<Attribute> attr;
std::string name;
bool wide = false, tall = false, custom = false;
bool foundTop = false, foundLeft = false, foundType = false, foundNum = false; // required attributes
RECT frame;
int width = 0, height = 0;
p.second = new cPict(*this);
for(attr = attr.begin(&who); attr != attr.end(); attr++){
attr->GetName(&name);
if(name == "name")
attr->GetValue(&p.first);
else if(name == "type"){
std::string val;
foundType = true;
attr->GetValue(&val);
pic_num_t wasPic = p.second->getPicNum();
if(val == "blank"){
p.second->setPict(-1, PIC_TER);
}else if(val == "ter")
p.second->setPict(wasPic, PIC_TER);
else if(val == "teranim")
p.second->setPict(wasPic, PIC_TER_ANIM);
else if(val == "monst")
p.second->setPict(wasPic, PIC_MONST);
else if(val == "dlog")
p.second->setPict(wasPic, PIC_DLOG);
else if(val == "talk")
p.second->setPict(wasPic, PIC_TALK);
else if(val == "scen")
p.second->setPict(wasPic, PIC_SCEN);
else if(val == "item")
p.second->setPict(wasPic, PIC_ITEM);
else if(val == "pc")
p.second->setPict(wasPic, PIC_PC);
else if(val == "field")
p.second->setPict(wasPic, PIC_FIELD);
else if(val == "boom")
p.second->setPict(wasPic, PIC_BOOM);
else if(val == "missile")
p.second->setPict(wasPic, PIC_MISSILE);
else if(val == "full")
p.second->setPict(wasPic, PIC_FULL);
else if(val == "map")
p.second->setPict(wasPic, PIC_TER_MAP);
else if(val == "status")
p.second->setPict(wasPic, PIC_STATUS);
else throw xBadVal("pict",name,val,attr->Row(),attr->Column(),fname);
}else if(name == "custom"){
std::string val;
attr->GetValue(&val);
if(val == "true") custom = true;
}else if(name == "size"){
std::string val;
attr->GetValue(&val);
if(val == "wide") wide = true;
else if(val == "tall") tall = true;
else if(val == "large") wide = tall = true;
else throw xBadVal("pict",name,val,attr->Row(),attr->Column(),fname);
}else if(name == "def-key"){
std::string val;
attr->GetValue(&val);
// TODO: The modifiers are now in key-mod, so this needs to be updated
try{
p.second->attachKey(parseKey(val));
}catch(int){
throw xBadVal("pict",name,val,attr->Row(),attr->Column(),fname);
}
}else if(name == "num"){
pic_num_t newPic;
attr->GetValue(&newPic), foundNum = true;
p.second->setPict(newPic);
}else if(name == "top"){
attr->GetValue(&frame.top), foundTop = true;
}else if(name == "left"){
attr->GetValue(&frame.left), foundLeft = true;
}else if(name == "width"){
attr->GetValue(&width);
}else if(name == "height"){
attr->GetValue(&height);
}else throw xBadAttr("pict",name,attr->Row(),attr->Column(),fname);
}
if(!foundType) throw xMissingAttr("pict","type",who.Row(),who.Column(),fname);
if(!foundNum) throw xMissingAttr("pict","num",who.Row(),who.Column(),fname);
if(!foundTop) throw xMissingAttr("pict","top",who.Row(),who.Column(),fname);
if(!foundLeft) throw xMissingAttr("pict","left",who.Row(),who.Column(),fname);
if(wide || tall) {
pic_num_t wasPic = p.second->getPicNum();
if(wide && !tall && p.second->getPicType() == PIC_MONST) p.second->setPict(wasPic, PIC_MONST_WIDE);
else if(!wide && tall && p.second->getPicType() == PIC_MONST) p.second->setPict(wasPic, PIC_MONST_TALL);
else if(wide && tall){
if(p.second->getPicType() == PIC_MONST) p.second->setPict(wasPic, PIC_MONST_LG);
else if(p.second->getPicType() == PIC_SCEN) p.second->setPict(wasPic, PIC_SCEN_LG);
else if(p.second->getPicType() == PIC_DLOG) p.second->setPict(wasPic, PIC_DLOG_LG);
}
}
frame.right = frame.left;
frame.bottom = frame.top;
if(width > 0 || height > 0 || p.second->getPicType() == PIC_FULL){
frame.right = frame.left + width;
frame.bottom = frame.top + height;
}else switch(p.second->getPicType()){
case PIC_DLOG:
frame.right = frame.left + 36;
frame.bottom = frame.top + 36;
break;
case PIC_DLOG_LG:
frame.right = frame.left + 72;
frame.bottom = frame.top + 72;
break;
case PIC_SCEN:
case PIC_TALK:
frame.right = frame.left + 32;
frame.bottom = frame.top + 32;
break;
case PIC_SCEN_LG:
frame.right = frame.left + 64;
frame.bottom = frame.top + 64;
break;
case PIC_MISSILE:
frame.right = frame.left + 18;
frame.bottom = frame.top + 18;
break;
case PIC_TER_MAP:
frame.right = frame.left + 24;
frame.bottom = frame.top + 24;
break;
case PIC_STATUS:
frame.right = frame.left + 12;
frame.bottom = frame.top + 12;
break;
case PIC_FULL:
// TODO: Do some handling here to determine the sheet to use, and perhaps to load and set it.
break;
default:
frame.right = frame.left + 28;
frame.bottom = frame.top + 36;
break;
}
p.second->setBounds(frame);
if(custom) {
pic_num_t wasPic = p.second->getPicNum();
p.second->setPict(wasPic, p.second->getPicType() + PIC_CUSTOM);
}
if(p.first == ""){
do{
p.first = generateRandomString();
}while(controls.find(p.first) != controls.end());
}
return p;
}
// A predicate for stripping out whitespace other than spaces
static bool isAllowableCharacter(char c) {
if(c == '\n') return false;
if(c == '\r') return false;
if(c == '\t') return false;
return true;
}
template<> pair<string,cTextMsg*> cDialog::parse(Element& who /*text*/){
pair<string,cTextMsg*> p;
Iterator<Attribute> attr;
Iterator<Node> node;
string name;
int width = 0, height = 0;
bool foundTop = false, foundLeft = false; // top and left are required attributes
RECT frame;
p.second = new cTextMsg(*this);
if(bg == BG_DARK) p.second->setColour(sf::Color::White);
for(attr = attr.begin(&who); attr != attr.end(); attr++){
attr->GetName(&name);
if(name == "name")
attr->GetValue(&p.first);
else if(name == "framed"){
std::string val;
attr->GetValue(&val);
if(val == "true") p.second->setFormat(TXT_FRAME, true);
}else if(name == "font"){
std::string val;
attr->GetValue(&val);
if(val == "dungeon")
p.second->setFormat(TXT_FONT, FONT_DUNGEON);
else if(val == "plain")
p.second->setFormat(TXT_FONT, FONT_PLAIN);
else if(val == "bold")
p.second->setFormat(TXT_FONT, FONT_BOLD);
else if(val == "maidenword")
p.second->setFormat(TXT_FONT, FONT_MAIDWORD);
else throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
}else if(name == "size"){
std::string val;
attr->GetValue(&val);
if(val == "large")
p.second->setFormat(TXT_SIZE, 12);
else if(val == "small")
p.second->setFormat(TXT_SIZE, 10);
else if(val == "title")
p.second->setFormat(TXT_SIZE, 18);
else throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
}else if(name == "color" || name == "colour"){
std::string val;
attr->GetValue(&val);
sf::Color clr;
try{
clr = parseColor(val);
}catch(int){
throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
}
p.second->setColour(clr);
}else if(name == "def-key"){
std::string val;
attr->GetValue(&val);
try{
p.second->attachKey(parseKey(val));
}catch(int){
throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
}
}else if(name == "top"){
attr->GetValue(&frame.top), foundTop = true;
}else if(name == "left"){
attr->GetValue(&frame.left), foundLeft = true;
}else if(name == "width"){
attr->GetValue(&width);
}else if(name == "height"){
attr->GetValue(&height);
// }else if(name == "fromlist"){
// attr->GetValue(&p.second->fromList);
}else throw xBadAttr("pict",name,attr->Row(),attr->Column(),fname);
}
if(!foundTop) throw xMissingAttr("text","top",who.Row(),who.Column(),fname);
if(!foundLeft) throw xMissingAttr("text","left",who.Row(),who.Column(),fname);
frame.right = frame.left + width;
frame.bottom = frame.top + height;
p.second->setBounds(frame);
string content;
for(node = node.begin(&who); node != node.end(); node++){
string val;
int type = node->Type();
node->GetValue(&val);
// TODO: De-magic the | character
if(type == TiXmlNode::ELEMENT && val == "br") content += '|'; // because vertical bar is replaced by a newline when drawing strings
else if(type == TiXmlNode::TEXT)
// TODO: One small problem with this: newlines should be replaced by a space instead of being removed altogether. Or something like that.
copy_if(val.begin(), val.end(), std::inserter(content, content.end()), isAllowableCharacter);
else{
val = '<' + val + '>';
throw xBadVal("text","<content>",content + val,node->Row(),node->Column(),fname);
}
}
p.second->setText(content);
if(p.first == ""){
do{
p.first = generateRandomString();
}while(controls.find(p.first) != controls.end());
}
return p;
}
/**
* Parses an HTML-style colour string, recognizing three-digit hex, six-digit hex, and HTML colour names.
*/
sf::Color cDialog::parseColor(string what){
sf::Color clr;
if(what[0] == '#'){
unsigned int r,g,b;
if(sscanf(what.c_str(),"#%2x%2x%2x",&r,&g,&b) < 3)
if(sscanf(what.c_str(),"#%1x%1x%1x",&r,&g,&b) < 3)
throw -1;
clr.r = r, clr.g = g, clr.b = b;
}else if(what == "black")
clr.r = 0x00, clr.g = 0x00, clr.b = 0x00;
else if(what == "red")
clr.r = 0xFF, clr.g = 0x00, clr.b = 0x00;
else if(what == "lime")
clr.r = 0x00, clr.g = 0xFF, clr.b = 0x00;
else if(what == "blue")
clr.r = 0x00, clr.g = 0x00, clr.b = 0xFF;
else if(what == "yellow")
clr.r = 0xFF, clr.g = 0xFF, clr.b = 0x00;
else if(what == "aqua")
clr.r = 0x00, clr.g = 0xFF, clr.b = 0xFF;
else if(what == "fuchsia")
clr.r = 0xFF, clr.g = 0x00, clr.b = 0xFF;
else if(what == "white")
clr.r = 0xFF, clr.g = 0xFF, clr.b = 0xFF;
else if(what == "gray" || what == "grey")
clr.r = 0x80, clr.g = 0x80, clr.b = 0x80;
else if(what == "maroon")
clr.r = 0x80, clr.g = 0x00, clr.b = 0x00;
else if(what == "green")
clr.r = 0x00, clr.g = 0x80, clr.b = 0x00;
else if(what == "navy")
clr.r = 0x00, clr.g = 0x00, clr.b = 0x80;
else if(what == "olive")
clr.r = 0x80, clr.g = 0x80, clr.b = 0x00;
else if(what == "teal")
clr.r = 0x00, clr.g = 0x80, clr.b = 0x80;
else if(what == "purple")
clr.r = 0x80, clr.g = 0x00, clr.b = 0x80;
else if(what == "silver")
clr.r = 0xC0, clr.g = 0xC0, clr.b = 0xC0;
else throw -1;
return clr;
}
template<> pair<string,cButton*> cDialog::parse(Element& who /*button*/){
pair<string,cButton*> p;
Iterator<Attribute> attr;
Iterator<Node> node;
string name;
int width = 0, height = 0;
bool foundType = false, foundTop = false, foundLeft = false; // required attributes
bool foundKey = false;
std::string keyMod, keyMain;
int keyModRow, keyModCol, keyMainRow, keyMainCol;
RECT frame;
p.second = new cButton(this);
for(attr = attr.begin(&who); attr != attr.end(); attr++){
attr->GetName(&name);
if(name == "name")
attr->GetValue(&p.first);
else if(name == "wrap"){
std::string val;
attr->GetValue(&val);
if(val == "true") p.second->setFormat(TXT_WRAP, true);
}else if(name == "type"){
std::string val;
foundType = true;
attr->GetValue(&val);
if(val == "small")
p.second->setBtnType(BTN_SM);
else if(val == "regular")
p.second->setBtnType(BTN_REG);
else if(val == "large")
p.second->setBtnType(BTN_LG);
else if(val == "help")
p.second->setBtnType(BTN_HELP);
else if(val == "left")
p.second->setBtnType(BTN_LEFT);
else if(val == "right")
p.second->setBtnType(BTN_RIGHT);
else if(val == "up")
p.second->setBtnType(BTN_UP);
else if(val == "down")
p.second->setBtnType(BTN_DOWN);
else if(val == "tiny")
p.second->setBtnType(BTN_TINY);
else if(val == "done")
p.second->setBtnType(BTN_DONE);
else if(val == "tall")
p.second->setBtnType(BTN_TALL);
else if(val == "trait")
p.second->setBtnType(BTN_TRAIT);
else if(val == "push")
p.second->setBtnType(BTN_PUSH);
}else if(name == "def-key"){
attr->GetValue(&keyMain);
foundKey = true;
keyMainRow = attr->Row();
keyMainCol = attr->Column();
}else if(name == "key-mod"){
attr->GetValue(&keyMod);
foundKey = true;
keyModRow = attr->Row();
keyModCol = attr->Column();
// }else if(name == "fromlist"){
// attr->GetValue(&p.second->fromList);
}else if(name == "top"){
attr->GetValue(&frame.top), foundTop = true;
}else if(name == "left"){
attr->GetValue(&frame.left), foundLeft = true;
}else if(name == "width"){
attr->GetValue(&width);
}else if(name == "height"){
attr->GetValue(&height);
}else throw xBadAttr("button",name,attr->Row(),attr->Column(),fname);
}
if(bg == BG_DARK && p.second->getBtnType() == BTN_TINY) p.second->setColour(sf::Color::White);
if(!foundType) throw xMissingAttr("button","type",who.Row(),who.Column(),fname);
if(!foundTop) throw xMissingAttr("button","top",who.Row(),who.Column(),fname);
if(!foundLeft) throw xMissingAttr("button","left",who.Row(),who.Column(),fname);
if(foundKey) {
cKey theKey;
try{
theKey = parseKey(keyMod + " " + keyMain);
}catch(int){
try {
theKey = parseKey(keyMain);
}catch(int){
throw xBadVal("button","def-key",keyMain,keyMainRow,keyMainCol,fname);
}
throw xBadVal("button","key-mod",keyMod,keyModRow,keyModCol,fname);
}
p.second->attachKey(theKey);
}
if(width > 0 || height > 0) {
// TODO: What if width is set but height isn't?
frame.right = frame.left + width;
frame.bottom = frame.top + height;
}else switch(p.second->getBtnType()){
case BTN_SM:
frame.right = frame.left + 23;
frame.bottom = frame.top + 23;
break;
case BTN_LG:
frame.right = frame.left + 102;
frame.bottom = frame.top + 23;
break;
case BTN_HELP:
frame.right = frame.left + 16;
frame.bottom = frame.top + 13;
break;
case BTN_TINY:
case BTN_LED: // this should never happen
frame.right = frame.left + 14;
frame.bottom = frame.top + 10;
break;
case BTN_TALL:
case BTN_TRAIT:
frame.right = frame.left + 63;
frame.bottom = frame.top + 40;
break;
case BTN_PUSH:
frame.right = frame.left + 30;
frame.bottom = frame.top + 30;
break;
default:
frame.right = frame.left + 63;
frame.bottom = frame.top + 23;
}
p.second->setBounds(frame);
string content;
for(node = node.begin(&who); node != node.end(); node++){
string val;
int type = node->Type();
node->GetValue(&val);
if(type == TiXmlNode::ELEMENT && val == "key"){
// TODO: There's surely a better way to do this
if(content.length() > 0) throw xBadVal("button","<content>",content + val,node->Row(),node->Column(),fname);
// p.second->labelWithKey = true;
}else if(type == TiXmlNode::TEXT)
// TODO: One small problem with this: newlines should be replaced by a space instead of being removed altogether. Or something like that.
copy_if(val.begin(), val.end(), std::inserter(content, content.end()), isAllowableCharacter);
else{
val = '<' + val + '>';
throw xBadVal("text","<content>",val,node->Row(),node->Column(),fname);
}
}
p.second->setText(content);
if(p.first == ""){
do{
p.first = generateRandomString();
}while(controls.find(p.first) != controls.end());
}
return p;
}
cKey cDialog::parseKey(string what){
cKey key;
key.spec = false;
key.c = 0;
key.mod = mod_none;
if(what == "none") return key;
istringstream sin(what);
string parts[4];
sin >> parts[0] >> parts[1] >> parts[2] >> parts[3];
for(int i = 0; i < 4; i++){
if(parts[i] == "ctrl") key.mod += mod_ctrl;
else if(parts[i] == "alt") key.mod += mod_alt;
else if(parts[i] == "shift") key.mod += mod_shift;
else if(parts[i] == "left") {
key.spec = true;
key.k = key_left;
break;
}else if(parts[i] == "right") {
key.spec = true;
key.k = key_right;
break;
}else if(parts[i] == "up") {
key.spec = true;
key.k = key_up;
break;
}else if(parts[i] == "down") {
key.spec = true;
key.k = key_down;
break;
}else if(parts[i] == "esc") {
key.spec = true;
key.k = key_esc;
break;
}else if(parts[i] == "enter" || parts[i] == "return") {
key.spec = true;
key.k = key_enter;
break;
}else if(parts[i] == "tab") {
key.spec = true;
key.k = key_tab;
break;
}else if(parts[i] == "help") {
key.spec = true;
key.k = key_help;
break;
}else if(parts[i] == "space") {
key.c = ' ';
break;
}else if(parts[i].length() == 1) {
key.c = parts[i][0];
break;
}else throw -1;
}
return key;
}
template<> pair<string,cLed*> cDialog::parse(Element& who /*LED*/){
pair<string,cLed*> p;
Iterator<Attribute> attr;
Iterator<Node> node;
string name;
int width = 0, height = 0;
bool foundTop = false, foundLeft = false; // requireds
RECT frame;
p.second = new cLed(this);
if(bg == BG_DARK) p.second->setColour(sf::Color::White);
for(attr = attr.begin(&who); attr != attr.end(); attr++){
attr->GetName(&name);
if(name == "name")
attr->GetValue(&p.first);
else if(name == "state"){
std::string val;
attr->GetValue(&val);
if(val == "red") p.second->setState(led_red);
else if(val == "green") p.second->setState(led_green);
else if(val == "off") p.second->setState(led_off);
else throw xBadVal("led",name,val,attr->Row(),attr->Column(),fname);
// }else if(name == "fromlist"){
// attr->GetValue(&p.second->fromList);
}else if(name == "font"){
std::string val;
attr->GetValue(&val);
if(val == "dungeon")
p.second->setFormat(TXT_FONT, FONT_DUNGEON);
else if(val == "plain")
p.second->setFormat(TXT_FONT, FONT_PLAIN);
else if(val == "bold")
p.second->setFormat(TXT_FONT, FONT_BOLD);
else if(val == "maidenword")
p.second->setFormat(TXT_FONT, FONT_MAIDWORD);
else throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
}else if(name == "size"){
std::string val;
attr->GetValue(&val);
if(val == "large")
p.second->setFormat(TXT_SIZE, 12);
else if(val == "small")
p.second->setFormat(TXT_SIZE, 10);
if(val == "title")
p.second->setFormat(TXT_SIZE, 18);
else throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
}else if(name == "color" || name == "colour"){
std::string val;
attr->GetValue(&val);
sf::Color clr;
try{
clr = parseColor(val);
}catch(int){
throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
}
p.second->setColour(clr);
}else if(name == "top"){
attr->GetValue(&frame.top), foundTop = true;
}else if(name == "left"){
attr->GetValue(&frame.left), foundLeft = true;
}else if(name == "width"){
attr->GetValue(&width);
}else if(name == "height"){
attr->GetValue(&height);
}else throw xBadAttr("button",name,attr->Row(),attr->Column(),fname);
}
if(!foundTop) throw xMissingAttr("led","top",who.Row(),who.Column(),fname);
if(!foundLeft) throw xMissingAttr("led","left",who.Row(),who.Column(),fname);
if(width > 0) {
frame.right = frame.left + width;
}else{
frame.right = frame.left + 14;
}
if(height > 0) {
frame.bottom = frame.top + height;
}else{
frame.bottom = frame.top + 10;
}
p.second->setBounds(frame);
string content;
for(node = node.begin(&who); node != node.end(); node++){
string val;
int type = node->Type();
node->GetValue(&val);
if(type == TiXmlNode::TEXT)
// TODO: One small problem with this: newlines should be replaced by a space instead of being removed altogether. Or something like that.
copy_if(val.begin(), val.end(), std::inserter(content, content.end()), isAllowableCharacter);
else{
val = '<' + val + '>';
throw xBadVal("text","<content>",content + val,node->Row(),node->Column(),fname);
}
}
p.second->setText(content);
if(p.first == ""){
do{
p.first = generateRandomString();
}while(controls.find(p.first) != controls.end());
}
return p;
}
template<> pair<string,cLedGroup*> cDialog::parse(Element& who /*group*/){
pair<string,cLedGroup*> p;
Iterator<Attribute> attr;
Iterator<Element> node;
string name;
p.second = new cLedGroup(this);
for(attr = attr.begin(&who); attr != attr.end(); attr++){
attr->GetName(&name);
if(name == "name")
attr->GetValue(&p.first);
// else if(name == "fromlist")
// attr->GetValue(&p.second->fromList);
else throw xBadAttr("button",name,attr->Row(),attr->Column(),fname);
}
string content;
for(node = node.begin(&who); node != node.end(); node++){
string val;
int type = node->Type();
node->GetValue(&val);
if(type == TiXmlNode::ELEMENT && val == "led"){
auto led = parse<cLed>(*node);
p.second->addChoice(led.second, led.first);
}else{
val = '<' + val + '>';
throw xBadVal("text","<content>", content + val,node->Row(),node->Column(),fname);
}
}
p.second->setText(content);
p.second->recalcRect();
if(p.first == ""){
do{
p.first = generateRandomString();
}while(controls.find(p.first) != controls.end());
}
return p;
}
template<> pair<string,cTextField*> cDialog::parse(Element& who /*field*/){
pair<string,cTextField*> p;
Iterator<Attribute> attr;
Iterator<Node> node;
string name;
int width = 0, height = 0;
bool foundTop = false, foundLeft = false; // requireds
RECT frame;
p.second = new cTextField(this);
for(attr = attr.begin(&who); attr != attr.end(); attr++){
attr->GetName(&name);
if(name == "name")
attr->GetValue(&p.first);
else if(name == "type"){
std::string val;
attr->GetValue(&val);
if(val == "num")
p.second->setInputType(FLD_NUM);
else if(val == "text")
p.second->setInputType(FLD_TEXT);
else throw xBadVal("field",name,val,attr->Row(),attr->Column(),fname);
}else if(name == "top"){
attr->GetValue(&frame.top), foundTop = true;
}else if(name == "left"){
attr->GetValue(&frame.left), foundLeft = true;
}else if(name == "width"){
attr->GetValue(&width);
}else if(name == "height"){
attr->GetValue(&height);
}else throw xBadAttr("button",name,attr->Row(),attr->Column(),fname);
}
if(!foundTop) throw xMissingAttr("field","top",attr->Row(),attr->Column(),fname);
if(!foundLeft) throw xMissingAttr("field","left",attr->Row(),attr->Column(),fname);
frame.right = frame.left + width;
frame.bottom = frame.top + height;
p.second->setBounds(frame);
if(p.first == ""){
do{
p.first = generateRandomString();
}while(controls.find(p.first) != controls.end());
}
return p;
}
cDialog::cDialog(cDialog* p) : parent(p) {}
cDialog::cDialog(std::string path) : parent(NULL) {
loadFromFile(path);
}
cDialog::cDialog(std::string path, cDialog* p) : parent(p) {
loadFromFile(path);
}
extern fs::path progDir;
void cDialog::loadFromFile(std::string path){
bg = defaultBackground;
fname = path;
fs::path cPath = progDir/"data"/"dialogs"/path;
try{
printf("Loading dialog from: %s\n", cPath.c_str());
TiXmlBase::SetCondenseWhiteSpace(false);
Document xml(cPath.c_str());
xml.LoadFile();
Iterator<Attribute> attr;
Iterator<Element> node;
string type, name, val;
xml.FirstChildElement()->GetValue(&type);
if(type != "dialog") throw xBadNode(type,xml.FirstChildElement()->Row(),xml.FirstChildElement()->Column(),fname);
for(attr = attr.begin(xml.FirstChildElement()); attr != attr.end(); attr++){
attr->GetName(&name);
attr->GetValue(&val);
if(name == "skin"){
if(val == "light") bg = BG_LIGHT;
else if(val == "dark") bg = BG_DARK;
else{
istringstream sin(val);
sin >> bg;
if(sin.fail()) throw xBadVal(type,name,val,attr->Row(),attr->Column(),fname);
}
}else if(name == "fore"){
sf::Color clr;
try{
clr = parseColor(val);
}catch(int){
throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
}
defTextClr = clr;
} else if(name == "defbtn") {
defaultButton = val;
}else if(name != "debug")
throw xBadAttr(type,name,attr->Row(),attr->Column(),fname);
}
for(node = node.begin(xml.FirstChildElement()); node != node.end(); node++){
node->GetValue(&type);
// Yes, I'm using insert instead of [] to add elements to the map.
// In this situation, it's actually easier that way; the reason being, the
// map key is obtained from the name attribute of each element.
if(type == "field")
controls.insert(parse<cTextField>(*node));
else if(type == "text")
controls.insert(parse<cTextMsg>(*node));
else if(type == "pict")
controls.insert(parse<cPict>(*node));
else if(type == "button")
controls.insert(parse<cButton>(*node));
else if(type == "led")
controls.insert(parse<cLed>(*node));
else if(type == "group")
controls.insert(parse<cLedGroup>(*node));
else throw xBadNode(type,node->Row(),node->Column(),fname);
}
} catch(Exception& x){ // XML processing exception
printf("%s",x.what());
exit(1);
} catch(xBadVal& x){ // Invalid value for an attribute
printf("%s",x.what());
exit(1);
} catch(xBadAttr& x){ // Invalid attribute for an element
printf("%s",x.what());
exit(1);
} catch(xBadNode& x){ // Invalid element
printf("%s",x.what());
exit(1);
} catch(xMissingAttr& x){ // Invalid element
printf("%s",x.what());
exit(1);
}
dialogNotToast = true;
if(bg == BG_DARK) defTextClr = sf::Color::White;
// now calculate window rect
winRect = RECT();
recalcRect();
ctrlIter iter = controls.begin();
currentFocus = NULL;
while(iter != controls.end()){
if(typeid(iter->second) == typeid(cTextField*)){
cTextField* fld = (cTextField*) iter->second;
if(currentFocus == NULL) currentFocus = fld;
}
iter++;
}
}
void cDialog::recalcRect(){
ctrlIter iter = controls.begin();
while(iter != controls.end()){
printf("%s \"%s\"\n",typeid(*(iter->second)).name(),iter->first.c_str());
RECT frame = iter->second->getBounds();
if(frame.right > winRect.right)
winRect.right = frame.right;
if(frame.bottom > winRect.bottom)
winRect.bottom = frame.bottom;
iter++;
}
winRect.right += 6;
winRect.bottom += 6;
}
void cDialog::init(){
cButton::init();
cLed::init();
cPict::init();
cScrollbar::init();
}
cDialog::~cDialog(){
ctrlIter iter = controls.begin();
while(iter != controls.end()){
delete iter->second;
iter++;
}
win.close();
}
bool cDialog::add(cControl* what, RECT ctrl_frame, std::string key){
// First make sure the key is not already present.
// If it is, we can't add the control, so return false.
if(controls.find(key) != controls.end()) return false;
what->setBounds(ctrl_frame);
controls.insert(std::make_pair(key,what));
return true;
}
bool cDialog::remove(std::string key){
ctrlIter ctrl = controls.find(key);
if(ctrl == controls.end()) return false;
delete ctrl->second;
controls.erase(ctrl);
return true;
}
void cDialog::run(){
cursor_type former_curs = current_cursor;
set_cursor(sword_curs);
using kb = sf::Keyboard;
kb::Key k;
cKey key;
sf::Event currentEvent;
std::string itemHit = "";
dialogNotToast = true;
// Focus the first text field, if there is one
for(auto ctrl : controls) {
if(ctrl.second->getType() == CTRL_FIELD) {
ctrl.second->triggerFocusHandler(*this, ctrl.first, false);
break;
}
}
win.create(sf::VideoMode(winRect.width(), winRect.height()), "Dialog", sf::Style::Titlebar);
win.setActive();
win.setVisible(true);
makeFrontWindow(win);
draw();
ModalSession dlog(win);
while(dialogNotToast){
draw();
dlog.pumpEvents();
if(!win.pollEvent(currentEvent)) continue;
location where;
switch(currentEvent.type){
case sf::Event::KeyPressed:
k = currentEvent.key.code;
switch(k){
case kb::Up:
key.spec = true;
key.k = key_up;
break;
case kb::Right:
key.spec = true;
key.k = key_right;
break;
case kb::Left:
key.spec = true;
key.k = key_left;
break;
case kb::Down:
key.spec = true;
key.k = key_down;
break;
case kb::Escape:
key.spec = true;
key.k = key_esc;
break;
case kb::Return: // TODO: Also enter (keypad)
key.spec = true;
key.k = key_enter;
break;
case kb::BackSpace:
key.spec = true;
key.k = key_bsp;
break;
case kb::Delete:
key.spec = true;
key.k = key_del;
break;
case kb::Tab:
key.spec = true;
key.k = key_tab;
break;
case kb::F1:
key.spec = true;
key.k = key_help;
break;
case kb::Home:
key.spec = true;
key.k = key_home;
break;
case kb::End:
key.spec = true;
key.k = key_end;
break;
case kb::PageUp:
key.spec = true;
key.k = key_pgup;
break;
case kb::PageDown:
key.spec = true;
key.k = key_pgdn;
break;
// TODO: Add cases for key_tab and key_help and others
case kb::LShift:
case kb::RShift:
case kb::LAlt:
case kb::RAlt:
case kb::LControl:
case kb::RControl:
case kb::LSystem:
case kb::RSystem:
continue;
default:
// TODO: Should probably only support system or control depending on OS
key.spec = false;
if(currentEvent.key.system || currentEvent.key.control) {
if(k == kb::C) {
key.spec = true;
key.k = key_copy;
} else if(k == kb::X) {
key.spec = true;
key.k = key_cut;
} else if(k == kb::V) {
key.spec = true;
key.k = key_paste;
} else if(k == kb::A) {
key.spec = true;
key.k = key_selectall;
}
if(key.spec) break;
}
key.c = keyToChar(k, currentEvent.key.shift);
break;
}
key.mod = mod_none;
if(currentEvent.key.control || currentEvent.key.system) {
if(key.spec){
if(key.k == key_left) key.k = key_home;
else if(key.k == key_right) key.k = key_end;
else if(key.k == key_up) key.k = key_pgup;
else if(key.k == key_down) key.k = key_pgdn;
else if(key.k == key_copy || key.k == key_cut);
else if(key.k == key_paste || key.k == key_selectall);
else key.mod += mod_ctrl;
}else key.mod += mod_ctrl;
}
if(currentEvent.key.shift) key.mod += mod_shift;
if(currentEvent.key.alt) key.mod += mod_alt;
itemHit = process_keystroke(key); // TODO: This should be a separate check from the fields thing?
if(itemHit.empty()) break;
where = controls[itemHit]->getBounds().centre();
if(controls[itemHit]->getType() == CTRL_FIELD){
if(key.spec && key.k == key_tab){
// TODO: Tabbing through fields, and trigger focus events.
ctrlIter cur = controls.find(itemHit), iter;
if(!cur->second->triggerFocusHandler(*this,itemHit,true)) break;
iter = std::next(cur);
while(iter != cur){
if(typeid(iter->second) == typeid(cTextField*)){
if(iter->second->triggerFocusHandler(*this,iter->first,false)){
currentFocus = (cTextField*) iter->second;
itemHit = "";
break;
}
}
iter++;
if(iter == controls.end()) iter = controls.begin();
}
if(iter == cur) // no focus change occured!
; // TODO: Surely something should happen here?
} else if(!key.spec || key.k != key_enter || mod_contains(key.mod, mod_alt)) {
dynamic_cast<cTextField*>(controls[itemHit])->handleInput(key);
itemHit = "";
}
}
break;
case sf::Event::MouseButtonPressed:
key.mod = mod_none;
if(kb::isKeyPressed(kb::LControl)) key.mod += mod_ctrl;
if(kb::isKeyPressed(kb::RControl)) key.mod += mod_ctrl;
if(kb::isKeyPressed(kb::LSystem)) key.mod += mod_ctrl;
if(kb::isKeyPressed(kb::RSystem)) key.mod += mod_ctrl;
if(kb::isKeyPressed(kb::LAlt)) key.mod += mod_alt;
if(kb::isKeyPressed(kb::RAlt)) key.mod += mod_alt;
if(kb::isKeyPressed(kb::LShift)) key.mod += mod_shift;
if(kb::isKeyPressed(kb::RShift)) key.mod += mod_shift;
where = {currentEvent.mouseButton.x, currentEvent.mouseButton.y};
itemHit = process_click(where, key.mod);
break;
case sf::Event::MouseMoved:
set_cursor(sword_curs);
for(auto& ctrl : controls) {
if(ctrl.second->getType() == CTRL_FIELD && ctrl.second->getBounds().contains(currentEvent.mouseMove.x, currentEvent.mouseMove.y)) {
set_cursor(text_curs);
break;
}
}
break;
}
if(itemHit.empty()) continue;;
ctrlIter ctrl = controls.find(itemHit);
// TODO: Should it do something with the boolean return value?
if(ctrl != controls.end()) ctrl->second->triggerClickHandler(*this,itemHit,key.mod,where);
itemHit.clear();
}
win.setVisible(false);
sf::RenderWindow* parentWin = &(parent ? parent->win : mainPtr);
while(parentWin->pollEvent(currentEvent));
set_cursor(former_curs);
}
void cDialog::setBg(short n){
bg = n;
}
void cDialog::setDefBtn(std::string defBtn) {
defaultButton = defBtn;
}
void cDialog::setDefTextClr(sf::Color clr){
defTextClr = clr;
}
sf::Color cDialog::getDefTextClr() {
return defTextClr;
}
bool cDialog::toast(){
dialogNotToast = false;
return true;
}
void cDialog::attachClickHandlers(click_callback_t handler, std::vector<std::string> controls) {
cDialog& me = *this;
for(std::string control : controls) {
me[control].attachClickHandler(handler);
}
}
void cDialog::attachFocusHandlers(focus_callback_t handler, std::vector<std::string> controls) {
cDialog& me = *this;
for(std::string control : controls) {
me[control].attachFocusHandler(handler);
}
}
bool cDialog::addLabelFor(std::string key, std::string label, eLabelPos where, short offset, bool bold) {
cControl& ctrl = this->getControl(key);
key += "-label";
RECT labelRect = ctrl.getBounds();
switch(where) {
case LABEL_LEFT:
labelRect.right = labelRect.left;
labelRect.left -= 2 * offset;
break;
case LABEL_ABOVE:
labelRect.bottom = labelRect.top;
labelRect.top -= 2 * offset;
break;
case LABEL_RIGHT:
labelRect.left = labelRect.right;
labelRect.right += 2 * offset;
break;
case LABEL_BELOW:
labelRect.top = labelRect.bottom;
labelRect.bottom += 2 * offset;
break;
}
if(labelRect.height() < 14){
int expand = (14 - labelRect.height()) / 2 + 1;
labelRect.inset(0, -expand);
} else labelRect.offset(0, labelRect.height() / 6);
cControl* labelCtrl;
auto iter = controls.find(key);
if(iter != controls.end()) {
labelCtrl = iter->second;
} else {
labelCtrl = new cTextMsg(*this);
}
labelCtrl->setText(label);
labelCtrl->setFormat(TXT_FONT, bold ? FONT_BOLD : FONT_PLAIN);
if(bg == BG_DARK && dynamic_cast<cButton*>(&ctrl) != NULL)
labelCtrl->setColour(defTextClr);
else labelCtrl->setColour(ctrl.getColour());
return add(labelCtrl, labelRect, key);
}
std::string cDialog::process_keystroke(cKey keyHit){
ctrlIter iter = controls.begin();
while(iter != controls.end()){
if(iter->second->getType() == CTRL_FIELD && iter->second->isVisible() && dynamic_cast<cTextField*>(iter->second)->hasFocus()) {
if(!keyHit.spec || (keyHit.k != key_esc && keyHit.k != key_help))
return iter->first;
}
if(iter->second->isVisible() && iter->second->isClickable() && iter->second->getAttachedKey() == keyHit){
iter->second->setActive(true);
draw();
if (play_sounds) {
if(typeid(iter->second) == typeid(cLed*))
play_sound(34);
else play_sound(37);
sf::sleep(time_in_ticks(6));
}
else sf::sleep(time_in_ticks(14));
iter->second->setActive(false);
draw();
sf::sleep(sf::milliseconds(8));
return iter->first;
}
iter++;
}
// If you get an escape and it isn't processed, make it an enter.
if(keyHit.spec && keyHit.k == key_esc){
keyHit.k = key_enter;
return process_keystroke(keyHit);
}
// If nothing was hit and the key was enter, return the default button (if any)
if(keyHit.spec && keyHit.k == key_enter)
return defaultButton;
return "";
}
std::string cDialog::process_click(location where, eKeyMod mods){
ctrlIter iter = controls.begin();
while(iter != controls.end()){
if(iter->second->isVisible() && iter->second->isClickable() && where.in(iter->second->getBounds())){
if(iter->second->handleClick(where))
return iter->first;
else return "";
}
iter++;
}
return "";
}
xBadNode::xBadNode(std::string t, int r, int c, std::string dlg) throw() :
type(t),
row(r),
col(c),
msg(NULL),
dlg(dlg) {}
const char* xBadNode::what() throw() {
if(msg == NULL){
char* s = new (nothrow) char[200];
if(s == NULL){
printf("Allocation of memory for error message failed, bailing out...");
abort();
}
sprintf(s,"XML Parse Error: Unknown element %s encountered (dialog %s, line %d, column %d).",type.c_str(),dlg.c_str(),row,col);
msg = s;
}
return msg;
}
xBadNode::~xBadNode() throw(){
if(msg != NULL) delete msg;
}
xBadAttr::xBadAttr(std::string t, std::string n, int r, int c, std::string dlg) throw() :
type(t),
name(n),
row(r),
col(c),
msg(NULL),
dlg(dlg) {}
const char* xBadAttr::what() throw() {
if(msg == NULL){
char* s = new (nothrow) char[200];
if(s == NULL){
printf("Allocation of memory for error message failed, bailing out...");
abort();
}
sprintf(s,"XML Parse Error: Unknown attribute %s encountered on element %s (dialog %s, line %d, column %d).",name.c_str(),type.c_str(),dlg.c_str(),row,col);
msg = s;
}
return msg;
}
xBadAttr::~xBadAttr() throw(){
if(msg != NULL) delete msg;
}
xMissingAttr::xMissingAttr(std::string t, std::string n, int r, int c, std::string dlg) throw() :
type(t),
name(n),
row(r),
col(c),
msg(NULL),
dlg(dlg) {}
const char* xMissingAttr::what() throw() {
if(msg == NULL){
char* s = new (nothrow) char[200];
if(s == NULL){
printf("Allocation of memory for error message failed, bailing out...");
abort();
}
sprintf(s,"XML Parse Error: Required attribute %s missing on element %s (dialog %s, line %d, column %d).",name.c_str(),type.c_str(),dlg.c_str(),row,col);
msg = s;
}
return msg;
}
xMissingAttr::~xMissingAttr() throw(){
if(msg != NULL) delete msg;
}
xBadVal::xBadVal(std::string t, std::string n, std::string v, int r, int c, std::string dlg) throw() :
type(t),
name(n),
val(v),
row(r),
col(c),
msg(NULL),
dlg(dlg) {}
const char* xBadVal::what() throw() {
if(msg == NULL){
char* s = new (nothrow) char[200];
if(s == NULL){
printf("Allocation of memory for error message failed, bailing out...");
abort();
}
sprintf(s,"XML Parse Error: Invalid value %s for attribute %s encountered on element %s (dialog %s, line %d, column %d).",val.c_str(),name.c_str(),type.c_str(),dlg.c_str(),row,col);
msg = s;
}
return msg;
}
xBadVal::~xBadVal() throw(){
if(msg != NULL) delete msg;
}
void cDialog::draw(){
win.setActive();
tileImage(win,winRect,bg_gworld,::bg[bg]);
ctrlIter iter = controls.begin();
while(iter != controls.end()){
iter->second->draw();
iter++;
}
win.display();
}
cControl& cDialog::operator[](std::string id){
return getControl(id);
}
cControl& cDialog::getControl(std::string id) {
ctrlIter iter = controls.find(id);
if(iter != controls.end()) return *(iter->second);
iter = controls.begin();
while(iter != controls.end()){
if(iter->second->getType() == CTRL_GROUP){
try{
cLedGroup* tmp = (cLedGroup*) (iter->second);
return tmp->operator[](id);
}catch(std::invalid_argument) {}
}else if(iter->second->getType() == CTRL_STACK){ // TODO: Implement stacks
// try{
// cStack* tmp = (cStack*) (iter->second);
// return tmp->operator[](id);
// }catch(std::invalid_argument) {}
}
iter++;
}
throw std::invalid_argument(id + " does not exist in dialog " + fname);
}