/* * dialog.cpp * BoE * * Created by Celtic Minstrel on 11/05/09. * */ #include #include #include #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 cDialog::parse(Element& who /*pict*/){ std::pair p; Iterator 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; } p.second->setBounds(frame); pic_num_t wasPic = p.second->getPicNum(); if(custom) { p.second->setPict(wasPic, p.second->getPicType() + PIC_CUSTOM); } else p.second->setPict(wasPic, p.second->getPicType()); // The above line also sets the graphic's bounding rect, if necessary 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 cDialog::parse(Element& who /*text*/){ pair p; Iterator attr; Iterator 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 + 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 cDialog::parse(Element& who /*button*/){ pair p; Iterator attr; Iterator 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 + 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","",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 cDialog::parse(Element& who /*LED*/){ pair p; Iterator attr; Iterator 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 + 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 cDialog::parse(Element& who /*group*/){ pair p; Iterator attr; Iterator 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(*node); p.second->addChoice(led.second, led.first); }else{ val = '<' + val + '>'; throw xBadVal("text","", 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 cDialog::parse(Element& who /*field*/){ pair p; Iterator attr; Iterator 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 attr; Iterator 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(*node)); else if(type == "text") controls.insert(parse(*node)); else if(type == "pict") controls.insert(parse(*node)); else if(type == "button") controls.insert(parse(*node)); else if(type == "led") controls.insert(parse(*node)); else if(type == "group") controls.insert(parse(*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; cTextField* wasFocus = currentFocus; iter = std::next(cur); while(iter != cur){ if((currentFocus = dynamic_cast(iter->second))){ if(currentFocus->triggerFocusHandler(*this,iter->first,false)){ itemHit = ""; break; } } if(iter == controls.end()) iter = controls.begin(); else iter++; } if(iter == cur) // no focus change occured! currentFocus = wasFocus; // TODO: Surely something should happen here? } else if(!key.spec || key.k != key_enter || mod_contains(key.mod, mod_alt)) { dynamic_cast(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 controls) { cDialog& me = *this; for(std::string control : controls) { me[control].attachClickHandler(handler); } } void cDialog::attachFocusHandlers(focus_callback_t handler, std::vector 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(&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(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); }