guiFormSpecMenu.cpp 109 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891
  1. /*
  2. Minetest
  3. Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include <cstdlib>
  17. #include <algorithm>
  18. #include <iterator>
  19. #include <sstream>
  20. #include <limits>
  21. #include "guiFormSpecMenu.h"
  22. #include "guiTable.h"
  23. #include "constants.h"
  24. #include "gamedef.h"
  25. #include "keycode.h"
  26. #include "util/strfnd.h"
  27. #include <IGUICheckBox.h>
  28. #include <IGUIEditBox.h>
  29. #include <IGUIButton.h>
  30. #include <IGUIStaticText.h>
  31. #include <IGUIFont.h>
  32. #include <IGUITabControl.h>
  33. #include <IGUIComboBox.h>
  34. #include "client/renderingengine.h"
  35. #include "log.h"
  36. #include "client/tile.h" // ITextureSource
  37. #include "client/hud.h" // drawItemStack
  38. #include "filesys.h"
  39. #include "gettime.h"
  40. #include "gettext.h"
  41. #include "scripting_server.h"
  42. #include "mainmenumanager.h"
  43. #include "porting.h"
  44. #include "settings.h"
  45. #include "client.h"
  46. #include "fontengine.h"
  47. #include "util/hex.h"
  48. #include "util/numeric.h"
  49. #include "util/string.h" // for parseColorString()
  50. #include "irrlicht_changes/static_text.h"
  51. #include "guiscalingfilter.h"
  52. #include "guiEditBoxWithScrollbar.h"
  53. #include "intlGUIEditBox.h"
  54. #define MY_CHECKPOS(a,b) \
  55. if (v_pos.size() != 2) { \
  56. errorstream<< "Invalid pos for element " << a << "specified: \"" \
  57. << parts[b] << "\"" << std::endl; \
  58. return; \
  59. }
  60. #define MY_CHECKGEOM(a,b) \
  61. if (v_geom.size() != 2) { \
  62. errorstream<< "Invalid pos for element " << a << "specified: \"" \
  63. << parts[b] << "\"" << std::endl; \
  64. return; \
  65. }
  66. /*
  67. GUIFormSpecMenu
  68. */
  69. static unsigned int font_line_height(gui::IGUIFont *font)
  70. {
  71. return font->getDimension(L"Ay").Height + font->getKerningHeight();
  72. }
  73. inline u32 clamp_u8(s32 value)
  74. {
  75. return (u32) MYMIN(MYMAX(value, 0), 255);
  76. }
  77. GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
  78. gui::IGUIElement *parent, s32 id, IMenuManager *menumgr,
  79. Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst,
  80. std::string formspecPrepend,
  81. bool remap_dbl_click):
  82. GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr),
  83. m_invmgr(client),
  84. m_tsrc(tsrc),
  85. m_client(client),
  86. m_formspec_prepend(formspecPrepend),
  87. m_form_src(fsrc),
  88. m_text_dst(tdst),
  89. m_joystick(joystick),
  90. m_remap_dbl_click(remap_dbl_click)
  91. #ifdef __ANDROID__
  92. , m_JavaDialogFieldName("")
  93. #endif
  94. {
  95. current_keys_pending.key_down = false;
  96. current_keys_pending.key_up = false;
  97. current_keys_pending.key_enter = false;
  98. current_keys_pending.key_escape = false;
  99. m_doubleclickdetect[0].time = 0;
  100. m_doubleclickdetect[1].time = 0;
  101. m_doubleclickdetect[0].pos = v2s32(0, 0);
  102. m_doubleclickdetect[1].pos = v2s32(0, 0);
  103. m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
  104. m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname");
  105. }
  106. GUIFormSpecMenu::~GUIFormSpecMenu()
  107. {
  108. removeChildren();
  109. for (auto &table_it : m_tables) {
  110. table_it.second->drop();
  111. }
  112. delete m_selected_item;
  113. delete m_form_src;
  114. delete m_text_dst;
  115. }
  116. void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client,
  117. JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest,
  118. const std::string &formspecPrepend)
  119. {
  120. if (cur_formspec == nullptr) {
  121. cur_formspec = new GUIFormSpecMenu(joystick, guiroot, -1, &g_menumgr,
  122. client, client->getTextureSource(), fs_src, txt_dest, formspecPrepend);
  123. cur_formspec->doPause = false;
  124. /*
  125. Caution: do not call (*cur_formspec)->drop() here --
  126. the reference might outlive the menu, so we will
  127. periodically check if *cur_formspec is the only
  128. remaining reference (i.e. the menu was removed)
  129. and delete it in that case.
  130. */
  131. } else {
  132. cur_formspec->setFormspecPrepend(formspecPrepend);
  133. cur_formspec->setFormSource(fs_src);
  134. cur_formspec->setTextDest(txt_dest);
  135. }
  136. }
  137. void GUIFormSpecMenu::removeChildren()
  138. {
  139. const core::list<gui::IGUIElement*> &children = getChildren();
  140. while(!children.empty()) {
  141. (*children.getLast())->remove();
  142. }
  143. if(m_tooltip_element) {
  144. m_tooltip_element->remove();
  145. m_tooltip_element->drop();
  146. m_tooltip_element = NULL;
  147. }
  148. }
  149. void GUIFormSpecMenu::setInitialFocus()
  150. {
  151. // Set initial focus according to following order of precedence:
  152. // 1. first empty editbox
  153. // 2. first editbox
  154. // 3. first table
  155. // 4. last button
  156. // 5. first focusable (not statictext, not tabheader)
  157. // 6. first child element
  158. core::list<gui::IGUIElement*> children = getChildren();
  159. // in case "children" contains any NULL elements, remove them
  160. for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
  161. it != children.end();) {
  162. if (*it)
  163. ++it;
  164. else
  165. it = children.erase(it);
  166. }
  167. // 1. first empty editbox
  168. for (gui::IGUIElement *it : children) {
  169. if (it->getType() == gui::EGUIET_EDIT_BOX
  170. && it->getText()[0] == 0) {
  171. Environment->setFocus(it);
  172. return;
  173. }
  174. }
  175. // 2. first editbox
  176. for (gui::IGUIElement *it : children) {
  177. if (it->getType() == gui::EGUIET_EDIT_BOX) {
  178. Environment->setFocus(it);
  179. return;
  180. }
  181. }
  182. // 3. first table
  183. for (gui::IGUIElement *it : children) {
  184. if (it->getTypeName() == std::string("GUITable")) {
  185. Environment->setFocus(it);
  186. return;
  187. }
  188. }
  189. // 4. last button
  190. for (core::list<gui::IGUIElement*>::Iterator it = children.getLast();
  191. it != children.end(); --it) {
  192. if ((*it)->getType() == gui::EGUIET_BUTTON) {
  193. Environment->setFocus(*it);
  194. return;
  195. }
  196. }
  197. // 5. first focusable (not statictext, not tabheader)
  198. for (gui::IGUIElement *it : children) {
  199. if (it->getType() != gui::EGUIET_STATIC_TEXT &&
  200. it->getType() != gui::EGUIET_TAB_CONTROL) {
  201. Environment->setFocus(it);
  202. return;
  203. }
  204. }
  205. // 6. first child element
  206. if (children.empty())
  207. Environment->setFocus(this);
  208. else
  209. Environment->setFocus(*(children.begin()));
  210. }
  211. GUITable* GUIFormSpecMenu::getTable(const std::string &tablename)
  212. {
  213. for (auto &table : m_tables) {
  214. if (tablename == table.first.fname)
  215. return table.second;
  216. }
  217. return 0;
  218. }
  219. std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string &name)
  220. {
  221. for (auto &dropdown : m_dropdowns) {
  222. if (name == dropdown.first.fname)
  223. return &dropdown.second;
  224. }
  225. return NULL;
  226. }
  227. void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
  228. {
  229. std::vector<std::string> parts = split(element,',');
  230. if (((parts.size() == 2) || parts.size() == 3) ||
  231. ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
  232. {
  233. if (parts[1].find(';') != std::string::npos)
  234. parts[1] = parts[1].substr(0,parts[1].find(';'));
  235. data->invsize.X = MYMAX(0, stof(parts[0]));
  236. data->invsize.Y = MYMAX(0, stof(parts[1]));
  237. lockSize(false);
  238. if (parts.size() == 3) {
  239. if (parts[2] == "true") {
  240. lockSize(true,v2u32(800,600));
  241. }
  242. }
  243. data->explicit_size = true;
  244. return;
  245. }
  246. errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl;
  247. }
  248. void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &element)
  249. {
  250. std::vector<std::string> parts = split(element, ',');
  251. if (parts.size() >= 2) {
  252. if (parts[1].find(';') != std::string::npos)
  253. parts[1] = parts[1].substr(0, parts[1].find(';'));
  254. container_stack.push(pos_offset);
  255. pos_offset.X += MYMAX(0, stof(parts[0]));
  256. pos_offset.Y += MYMAX(0, stof(parts[1]));
  257. return;
  258. }
  259. errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'" << std::endl;
  260. }
  261. void GUIFormSpecMenu::parseContainerEnd(parserData* data)
  262. {
  263. if (container_stack.empty()) {
  264. errorstream<< "Invalid container end element, no matching container start element" << std::endl;
  265. } else {
  266. pos_offset = container_stack.top();
  267. container_stack.pop();
  268. }
  269. }
  270. void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
  271. {
  272. if (m_client == 0) {
  273. warningstream<<"invalid use of 'list' with m_client==0"<<std::endl;
  274. return;
  275. }
  276. std::vector<std::string> parts = split(element,';');
  277. if (((parts.size() == 4) || (parts.size() == 5)) ||
  278. ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
  279. {
  280. std::string location = parts[0];
  281. std::string listname = parts[1];
  282. std::vector<std::string> v_pos = split(parts[2],',');
  283. std::vector<std::string> v_geom = split(parts[3],',');
  284. std::string startindex;
  285. if (parts.size() == 5)
  286. startindex = parts[4];
  287. MY_CHECKPOS("list",2);
  288. MY_CHECKGEOM("list",3);
  289. InventoryLocation loc;
  290. if(location == "context" || location == "current_name")
  291. loc = m_current_inventory_location;
  292. else
  293. loc.deSerialize(location);
  294. v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
  295. pos.X += stof(v_pos[0]) * (float)spacing.X;
  296. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  297. v2s32 geom;
  298. geom.X = stoi(v_geom[0]);
  299. geom.Y = stoi(v_geom[1]);
  300. s32 start_i = 0;
  301. if (!startindex.empty())
  302. start_i = stoi(startindex);
  303. if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
  304. errorstream<< "Invalid list element: '" << element << "'" << std::endl;
  305. return;
  306. }
  307. if(!data->explicit_size)
  308. warningstream<<"invalid use of list without a size[] element"<<std::endl;
  309. m_inventorylists.emplace_back(loc, listname, pos, geom, start_i);
  310. return;
  311. }
  312. errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl;
  313. }
  314. void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element)
  315. {
  316. if (m_client == 0) {
  317. errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl;
  318. return;
  319. }
  320. std::vector<std::string> parts = split(element, ';');
  321. if (parts.size() == 2) {
  322. std::string location = parts[0];
  323. std::string listname = parts[1];
  324. InventoryLocation loc;
  325. if (location == "context" || location == "current_name")
  326. loc = m_current_inventory_location;
  327. else
  328. loc.deSerialize(location);
  329. m_inventory_rings.emplace_back(loc, listname);
  330. return;
  331. }
  332. if (element.empty() && m_inventorylists.size() > 1) {
  333. size_t siz = m_inventorylists.size();
  334. // insert the last two inv list elements into the list ring
  335. const ListDrawSpec &spa = m_inventorylists[siz - 2];
  336. const ListDrawSpec &spb = m_inventorylists[siz - 1];
  337. m_inventory_rings.emplace_back(spa.inventoryloc, spa.listname);
  338. m_inventory_rings.emplace_back(spb.inventoryloc, spb.listname);
  339. return;
  340. }
  341. errorstream<< "Invalid list ring element(" << parts.size() << ", "
  342. << m_inventorylists.size() << "): '" << element << "'" << std::endl;
  343. }
  344. void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element)
  345. {
  346. std::vector<std::string> parts = split(element,';');
  347. if (((parts.size() >= 3) && (parts.size() <= 4)) ||
  348. ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
  349. {
  350. std::vector<std::string> v_pos = split(parts[0],',');
  351. std::string name = parts[1];
  352. std::string label = parts[2];
  353. std::string selected;
  354. if (parts.size() >= 4)
  355. selected = parts[3];
  356. MY_CHECKPOS("checkbox",0);
  357. v2s32 pos = padding + pos_offset * spacing;
  358. pos.X += stof(v_pos[0]) * (float) spacing.X;
  359. pos.Y += stof(v_pos[1]) * (float) spacing.Y;
  360. bool fselected = false;
  361. if (selected == "true")
  362. fselected = true;
  363. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  364. core::rect<s32> rect = core::rect<s32>(
  365. pos.X, pos.Y + ((imgsize.Y/2) - m_btn_height),
  366. pos.X + m_font->getDimension(wlabel.c_str()).Width + 25, // text size + size of checkbox
  367. pos.Y + ((imgsize.Y/2) + m_btn_height));
  368. FieldSpec spec(
  369. name,
  370. wlabel, //Needed for displaying text on MSVC
  371. wlabel,
  372. 258+m_fields.size()
  373. );
  374. spec.ftype = f_CheckBox;
  375. gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
  376. spec.fid, spec.flabel.c_str());
  377. if (spec.fname == data->focused_fieldname) {
  378. Environment->setFocus(e);
  379. }
  380. m_checkboxes.emplace_back(spec,e);
  381. m_fields.push_back(spec);
  382. return;
  383. }
  384. errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl;
  385. }
  386. void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element)
  387. {
  388. std::vector<std::string> parts = split(element,';');
  389. if (parts.size() >= 5) {
  390. std::vector<std::string> v_pos = split(parts[0],',');
  391. std::vector<std::string> v_dim = split(parts[1],',');
  392. std::string name = parts[3];
  393. std::string value = parts[4];
  394. MY_CHECKPOS("scrollbar",0);
  395. v2s32 pos = padding + pos_offset * spacing;
  396. pos.X += stof(v_pos[0]) * (float) spacing.X;
  397. pos.Y += stof(v_pos[1]) * (float) spacing.Y;
  398. if (v_dim.size() != 2) {
  399. errorstream<< "Invalid size for element " << "scrollbar"
  400. << "specified: \"" << parts[1] << "\"" << std::endl;
  401. return;
  402. }
  403. v2s32 dim;
  404. dim.X = stof(v_dim[0]) * (float) spacing.X;
  405. dim.Y = stof(v_dim[1]) * (float) spacing.Y;
  406. core::rect<s32> rect =
  407. core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
  408. FieldSpec spec(
  409. name,
  410. L"",
  411. L"",
  412. 258+m_fields.size()
  413. );
  414. bool is_horizontal = true;
  415. if (parts[2] == "vertical")
  416. is_horizontal = false;
  417. spec.ftype = f_ScrollBar;
  418. spec.send = true;
  419. gui::IGUIScrollBar* e =
  420. Environment->addScrollBar(is_horizontal,rect,this,spec.fid);
  421. e->setMax(1000);
  422. e->setMin(0);
  423. e->setPos(stoi(parts[4]));
  424. e->setSmallStep(10);
  425. e->setLargeStep(100);
  426. m_scrollbars.emplace_back(spec,e);
  427. m_fields.push_back(spec);
  428. return;
  429. }
  430. errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'" << std::endl;
  431. }
  432. void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
  433. {
  434. std::vector<std::string> parts = split(element,';');
  435. if ((parts.size() == 3) ||
  436. ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
  437. {
  438. std::vector<std::string> v_pos = split(parts[0],',');
  439. std::vector<std::string> v_geom = split(parts[1],',');
  440. std::string name = unescape_string(parts[2]);
  441. MY_CHECKPOS("image", 0);
  442. MY_CHECKGEOM("image", 1);
  443. v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
  444. pos.X += stof(v_pos[0]) * (float) spacing.X;
  445. pos.Y += stof(v_pos[1]) * (float) spacing.Y;
  446. v2s32 geom;
  447. geom.X = stof(v_geom[0]) * (float)imgsize.X;
  448. geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
  449. if (!data->explicit_size)
  450. warningstream<<"invalid use of image without a size[] element"<<std::endl;
  451. m_images.emplace_back(name, pos, geom);
  452. return;
  453. }
  454. if (parts.size() == 2) {
  455. std::vector<std::string> v_pos = split(parts[0],',');
  456. std::string name = unescape_string(parts[1]);
  457. MY_CHECKPOS("image", 0);
  458. v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
  459. pos.X += stof(v_pos[0]) * (float) spacing.X;
  460. pos.Y += stof(v_pos[1]) * (float) spacing.Y;
  461. if (!data->explicit_size)
  462. warningstream<<"invalid use of image without a size[] element"<<std::endl;
  463. m_images.emplace_back(name, pos);
  464. return;
  465. }
  466. errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl;
  467. }
  468. void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element)
  469. {
  470. std::vector<std::string> parts = split(element,';');
  471. if ((parts.size() == 3) ||
  472. ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
  473. {
  474. std::vector<std::string> v_pos = split(parts[0],',');
  475. std::vector<std::string> v_geom = split(parts[1],',');
  476. std::string name = parts[2];
  477. MY_CHECKPOS("itemimage",0);
  478. MY_CHECKGEOM("itemimage",1);
  479. v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
  480. pos.X += stof(v_pos[0]) * (float) spacing.X;
  481. pos.Y += stof(v_pos[1]) * (float) spacing.Y;
  482. v2s32 geom;
  483. geom.X = stof(v_geom[0]) * (float)imgsize.X;
  484. geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
  485. if(!data->explicit_size)
  486. warningstream<<"invalid use of item_image without a size[] element"<<std::endl;
  487. m_itemimages.emplace_back("", name, pos, geom);
  488. return;
  489. }
  490. errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl;
  491. }
  492. void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
  493. const std::string &type)
  494. {
  495. std::vector<std::string> parts = split(element,';');
  496. if ((parts.size() == 4) ||
  497. ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
  498. {
  499. std::vector<std::string> v_pos = split(parts[0],',');
  500. std::vector<std::string> v_geom = split(parts[1],',');
  501. std::string name = parts[2];
  502. std::string label = parts[3];
  503. MY_CHECKPOS("button",0);
  504. MY_CHECKGEOM("button",1);
  505. v2s32 pos = padding + pos_offset * spacing;
  506. pos.X += stof(v_pos[0]) * (float)spacing.X;
  507. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  508. v2s32 geom;
  509. geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
  510. pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
  511. core::rect<s32> rect =
  512. core::rect<s32>(pos.X, pos.Y - m_btn_height,
  513. pos.X + geom.X, pos.Y + m_btn_height);
  514. if(!data->explicit_size)
  515. warningstream<<"invalid use of button without a size[] element"<<std::endl;
  516. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  517. FieldSpec spec(
  518. name,
  519. wlabel,
  520. L"",
  521. 258+m_fields.size()
  522. );
  523. spec.ftype = f_Button;
  524. if(type == "button_exit")
  525. spec.is_exit = true;
  526. gui::IGUIButton* e = Environment->addButton(rect, this, spec.fid,
  527. spec.flabel.c_str());
  528. if (spec.fname == data->focused_fieldname) {
  529. Environment->setFocus(e);
  530. }
  531. m_fields.push_back(spec);
  532. return;
  533. }
  534. errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl;
  535. }
  536. void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element)
  537. {
  538. std::vector<std::string> parts = split(element,';');
  539. if (((parts.size() == 3) || (parts.size() == 4)) ||
  540. ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
  541. {
  542. std::vector<std::string> v_pos = split(parts[0],',');
  543. std::vector<std::string> v_geom = split(parts[1],',');
  544. std::string name = unescape_string(parts[2]);
  545. MY_CHECKPOS("background",0);
  546. MY_CHECKGEOM("background",1);
  547. v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
  548. pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X - (float)imgsize.X)/2;
  549. pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y - (float)imgsize.Y)/2;
  550. v2s32 geom;
  551. geom.X = stof(v_geom[0]) * (float)spacing.X;
  552. geom.Y = stof(v_geom[1]) * (float)spacing.Y;
  553. if (!data->explicit_size)
  554. warningstream<<"invalid use of background without a size[] element"<<std::endl;
  555. bool clip = false;
  556. if (parts.size() == 4 && is_yes(parts[3])) {
  557. pos.X = stoi(v_pos[0]); //acts as offset
  558. pos.Y = stoi(v_pos[1]); //acts as offset
  559. clip = true;
  560. }
  561. m_backgrounds.emplace_back(name, pos, geom, clip);
  562. return;
  563. }
  564. errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl;
  565. }
  566. void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element)
  567. {
  568. std::vector<std::string> parts = split(element,';');
  569. data->table_options.clear();
  570. for (const std::string &part : parts) {
  571. // Parse table option
  572. std::string opt = unescape_string(part);
  573. data->table_options.push_back(GUITable::splitOption(opt));
  574. }
  575. }
  576. void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &element)
  577. {
  578. std::vector<std::string> parts = split(element,';');
  579. data->table_columns.clear();
  580. for (const std::string &part : parts) {
  581. std::vector<std::string> col_parts = split(part,',');
  582. GUITable::TableColumn column;
  583. // Parse column type
  584. if (!col_parts.empty())
  585. column.type = col_parts[0];
  586. // Parse column options
  587. for (size_t j = 1; j < col_parts.size(); ++j) {
  588. std::string opt = unescape_string(col_parts[j]);
  589. column.options.push_back(GUITable::splitOption(opt));
  590. }
  591. data->table_columns.push_back(column);
  592. }
  593. }
  594. void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
  595. {
  596. std::vector<std::string> parts = split(element,';');
  597. if (((parts.size() == 4) || (parts.size() == 5)) ||
  598. ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
  599. {
  600. std::vector<std::string> v_pos = split(parts[0],',');
  601. std::vector<std::string> v_geom = split(parts[1],',');
  602. std::string name = parts[2];
  603. std::vector<std::string> items = split(parts[3],',');
  604. std::string str_initial_selection;
  605. std::string str_transparent = "false";
  606. if (parts.size() >= 5)
  607. str_initial_selection = parts[4];
  608. MY_CHECKPOS("table",0);
  609. MY_CHECKGEOM("table",1);
  610. v2s32 pos = padding + pos_offset * spacing;
  611. pos.X += stof(v_pos[0]) * (float)spacing.X;
  612. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  613. v2s32 geom;
  614. geom.X = stof(v_geom[0]) * (float)spacing.X;
  615. geom.Y = stof(v_geom[1]) * (float)spacing.Y;
  616. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  617. FieldSpec spec(
  618. name,
  619. L"",
  620. L"",
  621. 258+m_fields.size()
  622. );
  623. spec.ftype = f_Table;
  624. for (std::string &item : items) {
  625. item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
  626. }
  627. //now really show table
  628. GUITable *e = new GUITable(Environment, this, spec.fid, rect,
  629. m_tsrc);
  630. if (spec.fname == data->focused_fieldname) {
  631. Environment->setFocus(e);
  632. }
  633. e->setTable(data->table_options, data->table_columns, items);
  634. if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
  635. e->setDynamicData(data->table_dyndata[name]);
  636. }
  637. if (!str_initial_selection.empty() && str_initial_selection != "0")
  638. e->setSelected(stoi(str_initial_selection));
  639. m_tables.emplace_back(spec, e);
  640. m_fields.push_back(spec);
  641. return;
  642. }
  643. errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'" << std::endl;
  644. }
  645. void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element)
  646. {
  647. std::vector<std::string> parts = split(element,';');
  648. if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) ||
  649. ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
  650. {
  651. std::vector<std::string> v_pos = split(parts[0],',');
  652. std::vector<std::string> v_geom = split(parts[1],',');
  653. std::string name = parts[2];
  654. std::vector<std::string> items = split(parts[3],',');
  655. std::string str_initial_selection;
  656. std::string str_transparent = "false";
  657. if (parts.size() >= 5)
  658. str_initial_selection = parts[4];
  659. if (parts.size() >= 6)
  660. str_transparent = parts[5];
  661. MY_CHECKPOS("textlist",0);
  662. MY_CHECKGEOM("textlist",1);
  663. v2s32 pos = padding + pos_offset * spacing;
  664. pos.X += stof(v_pos[0]) * (float)spacing.X;
  665. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  666. v2s32 geom;
  667. geom.X = stof(v_geom[0]) * (float)spacing.X;
  668. geom.Y = stof(v_geom[1]) * (float)spacing.Y;
  669. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  670. FieldSpec spec(
  671. name,
  672. L"",
  673. L"",
  674. 258+m_fields.size()
  675. );
  676. spec.ftype = f_Table;
  677. for (std::string &item : items) {
  678. item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
  679. }
  680. //now really show list
  681. GUITable *e = new GUITable(Environment, this, spec.fid, rect,
  682. m_tsrc);
  683. if (spec.fname == data->focused_fieldname) {
  684. Environment->setFocus(e);
  685. }
  686. e->setTextList(items, is_yes(str_transparent));
  687. if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
  688. e->setDynamicData(data->table_dyndata[name]);
  689. }
  690. if (!str_initial_selection.empty() && str_initial_selection != "0")
  691. e->setSelected(stoi(str_initial_selection));
  692. m_tables.emplace_back(spec, e);
  693. m_fields.push_back(spec);
  694. return;
  695. }
  696. errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl;
  697. }
  698. void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element)
  699. {
  700. std::vector<std::string> parts = split(element,';');
  701. if ((parts.size() == 5) ||
  702. ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
  703. {
  704. std::vector<std::string> v_pos = split(parts[0],',');
  705. std::string name = parts[2];
  706. std::vector<std::string> items = split(parts[3],',');
  707. std::string str_initial_selection;
  708. str_initial_selection = parts[4];
  709. MY_CHECKPOS("dropdown",0);
  710. v2s32 pos = padding + pos_offset * spacing;
  711. pos.X += stof(v_pos[0]) * (float)spacing.X;
  712. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  713. s32 width = stof(parts[1]) * (float)spacing.Y;
  714. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
  715. pos.X + width, pos.Y + (m_btn_height * 2));
  716. FieldSpec spec(
  717. name,
  718. L"",
  719. L"",
  720. 258+m_fields.size()
  721. );
  722. spec.ftype = f_DropDown;
  723. spec.send = true;
  724. //now really show list
  725. gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid);
  726. if (spec.fname == data->focused_fieldname) {
  727. Environment->setFocus(e);
  728. }
  729. for (const std::string &item : items) {
  730. e->addItem(unescape_translate(unescape_string(
  731. utf8_to_wide(item))).c_str());
  732. }
  733. if (!str_initial_selection.empty())
  734. e->setSelected(stoi(str_initial_selection)-1);
  735. m_fields.push_back(spec);
  736. m_dropdowns.emplace_back(spec, std::vector<std::string>());
  737. std::vector<std::string> &values = m_dropdowns.back().second;
  738. for (const std::string &item : items) {
  739. values.push_back(unescape_string(item));
  740. }
  741. return;
  742. }
  743. errorstream << "Invalid dropdown element(" << parts.size() << "): '"
  744. << element << "'" << std::endl;
  745. }
  746. void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element)
  747. {
  748. std::vector<std::string> parts = split(element,';');
  749. if (parts.size() == 2 ||
  750. (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) {
  751. field_close_on_enter[parts[0]] = is_yes(parts[1]);
  752. }
  753. }
  754. void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element)
  755. {
  756. std::vector<std::string> parts = split(element,';');
  757. if ((parts.size() == 4) || (parts.size() == 5) ||
  758. ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
  759. {
  760. std::vector<std::string> v_pos = split(parts[0],',');
  761. std::vector<std::string> v_geom = split(parts[1],',');
  762. std::string name = parts[2];
  763. std::string label = parts[3];
  764. MY_CHECKPOS("pwdfield",0);
  765. MY_CHECKGEOM("pwdfield",1);
  766. v2s32 pos = pos_offset * spacing;
  767. pos.X += stof(v_pos[0]) * (float)spacing.X;
  768. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  769. v2s32 geom;
  770. geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
  771. pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
  772. pos.Y -= m_btn_height;
  773. geom.Y = m_btn_height*2;
  774. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  775. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  776. FieldSpec spec(
  777. name,
  778. wlabel,
  779. L"",
  780. 258+m_fields.size()
  781. );
  782. spec.send = true;
  783. gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);
  784. if (spec.fname == data->focused_fieldname) {
  785. Environment->setFocus(e);
  786. }
  787. if (label.length() >= 1) {
  788. int font_height = g_fontengine->getTextHeight();
  789. rect.UpperLeftCorner.Y -= font_height;
  790. rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
  791. gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
  792. this, 0);
  793. }
  794. e->setPasswordBox(true,L'*');
  795. irr::SEvent evt;
  796. evt.EventType = EET_KEY_INPUT_EVENT;
  797. evt.KeyInput.Key = KEY_END;
  798. evt.KeyInput.Char = 0;
  799. evt.KeyInput.Control = false;
  800. evt.KeyInput.Shift = false;
  801. evt.KeyInput.PressedDown = true;
  802. e->OnEvent(evt);
  803. if (parts.size() >= 5) {
  804. // TODO: remove after 2016-11-03
  805. warningstream << "pwdfield: use field_close_on_enter[name, enabled]" <<
  806. " instead of the 5th param" << std::endl;
  807. field_close_on_enter[name] = is_yes(parts[4]);
  808. }
  809. m_fields.push_back(spec);
  810. return;
  811. }
  812. errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl;
  813. }
  814. void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
  815. core::rect<s32> &rect, bool is_multiline)
  816. {
  817. bool is_editable = !spec.fname.empty();
  818. if (!is_editable && !is_multiline) {
  819. // spec field id to 0, this stops submit searching for a value that isn't there
  820. gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
  821. this, spec.fid);
  822. return;
  823. }
  824. if (is_editable) {
  825. spec.send = true;
  826. } else if (is_multiline &&
  827. spec.fdefault.empty() && !spec.flabel.empty()) {
  828. // Multiline textareas: swap default and label for backwards compat
  829. spec.flabel.swap(spec.fdefault);
  830. }
  831. gui::IGUIEditBox *e = nullptr;
  832. static constexpr bool use_intl_edit_box = USE_FREETYPE &&
  833. IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9;
  834. if (use_intl_edit_box && g_settings->getBool("freetype")) {
  835. e = new gui::intlGUIEditBox(spec.fdefault.c_str(),
  836. true, Environment, this, spec.fid, rect, is_editable, is_multiline);
  837. e->drop();
  838. } else {
  839. if (is_multiline)
  840. e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true,
  841. Environment, this, spec.fid, rect, is_editable, true);
  842. else if (is_editable)
  843. e = Environment->addEditBox(spec.fdefault.c_str(), rect, true,
  844. this, spec.fid);
  845. }
  846. if (e) {
  847. if (is_editable && spec.fname == data->focused_fieldname)
  848. Environment->setFocus(e);
  849. if (is_multiline) {
  850. e->setMultiLine(true);
  851. e->setWordWrap(true);
  852. e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
  853. } else {
  854. irr::SEvent evt;
  855. evt.EventType = EET_KEY_INPUT_EVENT;
  856. evt.KeyInput.Key = KEY_END;
  857. evt.KeyInput.Char = 0;
  858. evt.KeyInput.Control = 0;
  859. evt.KeyInput.Shift = 0;
  860. evt.KeyInput.PressedDown = true;
  861. e->OnEvent(evt);
  862. }
  863. }
  864. if (!spec.flabel.empty()) {
  865. int font_height = g_fontengine->getTextHeight();
  866. rect.UpperLeftCorner.Y -= font_height;
  867. rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
  868. gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
  869. this, 0);
  870. }
  871. }
  872. void GUIFormSpecMenu::parseSimpleField(parserData* data,
  873. std::vector<std::string> &parts)
  874. {
  875. std::string name = parts[0];
  876. std::string label = parts[1];
  877. std::string default_val = parts[2];
  878. core::rect<s32> rect;
  879. if(data->explicit_size)
  880. warningstream<<"invalid use of unpositioned \"field\" in inventory"<<std::endl;
  881. v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
  882. pos.Y = ((m_fields.size()+2)*60);
  883. v2s32 size = DesiredRect.getSize();
  884. rect = core::rect<s32>(size.X / 2 - 150, pos.Y,
  885. (size.X / 2 - 150) + 300, pos.Y + (m_btn_height*2));
  886. if(m_form_src)
  887. default_val = m_form_src->resolveText(default_val);
  888. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  889. FieldSpec spec(
  890. name,
  891. wlabel,
  892. utf8_to_wide(unescape_string(default_val)),
  893. 258+m_fields.size()
  894. );
  895. createTextField(data, spec, rect, false);
  896. if (parts.size() >= 4) {
  897. // TODO: remove after 2016-11-03
  898. warningstream << "field/simple: use field_close_on_enter[name, enabled]" <<
  899. " instead of the 4th param" << std::endl;
  900. field_close_on_enter[name] = is_yes(parts[3]);
  901. }
  902. m_fields.push_back(spec);
  903. }
  904. void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
  905. const std::string &type)
  906. {
  907. std::vector<std::string> v_pos = split(parts[0],',');
  908. std::vector<std::string> v_geom = split(parts[1],',');
  909. std::string name = parts[2];
  910. std::string label = parts[3];
  911. std::string default_val = parts[4];
  912. MY_CHECKPOS(type,0);
  913. MY_CHECKGEOM(type,1);
  914. v2s32 pos = pos_offset * spacing;
  915. pos.X += stof(v_pos[0]) * (float) spacing.X;
  916. pos.Y += stof(v_pos[1]) * (float) spacing.Y;
  917. v2s32 geom;
  918. geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
  919. if (type == "textarea")
  920. {
  921. geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
  922. pos.Y += m_btn_height;
  923. }
  924. else
  925. {
  926. pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
  927. pos.Y -= m_btn_height;
  928. geom.Y = m_btn_height*2;
  929. }
  930. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  931. if(!data->explicit_size)
  932. warningstream<<"invalid use of positioned "<<type<<" without a size[] element"<<std::endl;
  933. if(m_form_src)
  934. default_val = m_form_src->resolveText(default_val);
  935. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  936. FieldSpec spec(
  937. name,
  938. wlabel,
  939. utf8_to_wide(unescape_string(default_val)),
  940. 258+m_fields.size()
  941. );
  942. createTextField(data, spec, rect, type == "textarea");
  943. if (parts.size() >= 6) {
  944. // TODO: remove after 2016-11-03
  945. warningstream << "field/textarea: use field_close_on_enter[name, enabled]" <<
  946. " instead of the 6th param" << std::endl;
  947. field_close_on_enter[name] = is_yes(parts[5]);
  948. }
  949. m_fields.push_back(spec);
  950. }
  951. void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
  952. const std::string &type)
  953. {
  954. std::vector<std::string> parts = split(element,';');
  955. if (parts.size() == 3 || parts.size() == 4) {
  956. parseSimpleField(data,parts);
  957. return;
  958. }
  959. if ((parts.size() == 5) || (parts.size() == 6) ||
  960. ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
  961. {
  962. parseTextArea(data,parts,type);
  963. return;
  964. }
  965. errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl;
  966. }
  967. void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
  968. {
  969. std::vector<std::string> parts = split(element,';');
  970. if ((parts.size() == 2) ||
  971. ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
  972. {
  973. std::vector<std::string> v_pos = split(parts[0],',');
  974. std::string text = parts[1];
  975. MY_CHECKPOS("label",0);
  976. v2s32 pos = padding + pos_offset * spacing;
  977. pos.X += stof(v_pos[0]) * (float)spacing.X;
  978. pos.Y += (stof(v_pos[1]) + 7.0/30.0) * (float)spacing.Y;
  979. if(!data->explicit_size)
  980. warningstream<<"invalid use of label without a size[] element"<<std::endl;
  981. std::vector<std::string> lines = split(text, '\n');
  982. for (unsigned int i = 0; i != lines.size(); i++) {
  983. // Lines are spaced at the nominal distance of
  984. // 2/5 inventory slot, even if the font doesn't
  985. // quite match that. This provides consistent
  986. // form layout, at the expense of sometimes
  987. // having sub-optimal spacing for the font.
  988. // We multiply by 2 and then divide by 5, rather
  989. // than multiply by 0.4, to get exact results
  990. // in the integer cases: 0.4 is not exactly
  991. // representable in binary floating point.
  992. s32 posy = pos.Y + ((float)i) * spacing.Y * 2.0 / 5.0;
  993. std::wstring wlabel = utf8_to_wide(unescape_string(lines[i]));
  994. core::rect<s32> rect = core::rect<s32>(
  995. pos.X, posy - m_btn_height,
  996. pos.X + m_font->getDimension(wlabel.c_str()).Width,
  997. posy + m_btn_height);
  998. FieldSpec spec(
  999. "",
  1000. wlabel,
  1001. L"",
  1002. 258+m_fields.size()
  1003. );
  1004. gui::IGUIStaticText *e = gui::StaticText::add(Environment,
  1005. spec.flabel.c_str(), rect, false, false, this, spec.fid);
  1006. e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
  1007. m_fields.push_back(spec);
  1008. }
  1009. return;
  1010. }
  1011. errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'" << std::endl;
  1012. }
  1013. void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element)
  1014. {
  1015. std::vector<std::string> parts = split(element,';');
  1016. if ((parts.size() == 2) ||
  1017. ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
  1018. {
  1019. std::vector<std::string> v_pos = split(parts[0],',');
  1020. std::wstring text = unescape_translate(
  1021. unescape_string(utf8_to_wide(parts[1])));
  1022. MY_CHECKPOS("vertlabel",1);
  1023. v2s32 pos = padding + pos_offset * spacing;
  1024. pos.X += stof(v_pos[0]) * (float)spacing.X;
  1025. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  1026. core::rect<s32> rect = core::rect<s32>(
  1027. pos.X, pos.Y+((imgsize.Y/2)- m_btn_height),
  1028. pos.X+15, pos.Y +
  1029. font_line_height(m_font)
  1030. * (text.length()+1)
  1031. +((imgsize.Y/2)- m_btn_height));
  1032. //actually text.length() would be correct but adding +1 avoids to break all mods
  1033. if(!data->explicit_size)
  1034. warningstream<<"invalid use of label without a size[] element"<<std::endl;
  1035. std::wstring label;
  1036. for (wchar_t i : text) {
  1037. label += i;
  1038. label += L"\n";
  1039. }
  1040. FieldSpec spec(
  1041. "",
  1042. label,
  1043. L"",
  1044. 258+m_fields.size()
  1045. );
  1046. gui::IGUIStaticText *t = gui::StaticText::add(Environment, spec.flabel.c_str(),
  1047. rect, false, false, this, spec.fid);
  1048. t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
  1049. m_fields.push_back(spec);
  1050. return;
  1051. }
  1052. errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl;
  1053. }
  1054. void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element,
  1055. const std::string &type)
  1056. {
  1057. std::vector<std::string> parts = split(element,';');
  1058. if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) ||
  1059. ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION)))
  1060. {
  1061. std::vector<std::string> v_pos = split(parts[0],',');
  1062. std::vector<std::string> v_geom = split(parts[1],',');
  1063. std::string image_name = parts[2];
  1064. std::string name = parts[3];
  1065. std::string label = parts[4];
  1066. MY_CHECKPOS("imagebutton",0);
  1067. MY_CHECKGEOM("imagebutton",1);
  1068. v2s32 pos = padding + pos_offset * spacing;
  1069. pos.X += stof(v_pos[0]) * (float)spacing.X;
  1070. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  1071. v2s32 geom;
  1072. geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
  1073. geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
  1074. bool noclip = false;
  1075. bool drawborder = true;
  1076. std::string pressed_image_name;
  1077. if (parts.size() >= 7) {
  1078. if (parts[5] == "true")
  1079. noclip = true;
  1080. if (parts[6] == "false")
  1081. drawborder = false;
  1082. }
  1083. if (parts.size() >= 8) {
  1084. pressed_image_name = parts[7];
  1085. }
  1086. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  1087. if(!data->explicit_size)
  1088. warningstream<<"invalid use of image_button without a size[] element"<<std::endl;
  1089. image_name = unescape_string(image_name);
  1090. pressed_image_name = unescape_string(pressed_image_name);
  1091. std::wstring wlabel = utf8_to_wide(unescape_string(label));
  1092. FieldSpec spec(
  1093. name,
  1094. wlabel,
  1095. utf8_to_wide(image_name),
  1096. 258+m_fields.size()
  1097. );
  1098. spec.ftype = f_Button;
  1099. if(type == "image_button_exit")
  1100. spec.is_exit = true;
  1101. video::ITexture *texture = 0;
  1102. video::ITexture *pressed_texture = 0;
  1103. texture = m_tsrc->getTexture(image_name);
  1104. if (!pressed_image_name.empty())
  1105. pressed_texture = m_tsrc->getTexture(pressed_image_name);
  1106. else
  1107. pressed_texture = texture;
  1108. gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
  1109. if (spec.fname == data->focused_fieldname) {
  1110. Environment->setFocus(e);
  1111. }
  1112. e->setUseAlphaChannel(true);
  1113. e->setImage(guiScalingImageButton(
  1114. Environment->getVideoDriver(), texture, geom.X, geom.Y));
  1115. e->setPressedImage(guiScalingImageButton(
  1116. Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
  1117. e->setScaleImage(true);
  1118. e->setNotClipped(noclip);
  1119. e->setDrawBorder(drawborder);
  1120. m_fields.push_back(spec);
  1121. return;
  1122. }
  1123. errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
  1124. }
  1125. void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element)
  1126. {
  1127. std::vector<std::string> parts = split(element,';');
  1128. if (((parts.size() == 4) || (parts.size() == 6)) ||
  1129. ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
  1130. {
  1131. std::vector<std::string> v_pos = split(parts[0],',');
  1132. std::string name = parts[1];
  1133. std::vector<std::string> buttons = split(parts[2],',');
  1134. std::string str_index = parts[3];
  1135. bool show_background = true;
  1136. bool show_border = true;
  1137. int tab_index = stoi(str_index) -1;
  1138. MY_CHECKPOS("tabheader",0);
  1139. if (parts.size() == 6) {
  1140. if (parts[4] == "true")
  1141. show_background = false;
  1142. if (parts[5] == "false")
  1143. show_border = false;
  1144. }
  1145. FieldSpec spec(
  1146. name,
  1147. L"",
  1148. L"",
  1149. 258+m_fields.size()
  1150. );
  1151. spec.ftype = f_TabHeader;
  1152. v2s32 pos = pos_offset * spacing;
  1153. pos.X += stof(v_pos[0]) * (float)spacing.X;
  1154. pos.Y += stof(v_pos[1]) * (float)spacing.Y - m_btn_height * 2;
  1155. v2s32 geom;
  1156. geom.X = DesiredRect.getWidth();
  1157. geom.Y = m_btn_height*2;
  1158. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
  1159. pos.Y+geom.Y);
  1160. gui::IGUITabControl *e = Environment->addTabControl(rect, this,
  1161. show_background, show_border, spec.fid);
  1162. e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
  1163. irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
  1164. e->setTabHeight(m_btn_height*2);
  1165. if (spec.fname == data->focused_fieldname) {
  1166. Environment->setFocus(e);
  1167. }
  1168. e->setNotClipped(true);
  1169. for (const std::string &button : buttons) {
  1170. e->addTab(unescape_translate(unescape_string(
  1171. utf8_to_wide(button))).c_str(), -1);
  1172. }
  1173. if ((tab_index >= 0) &&
  1174. (buttons.size() < INT_MAX) &&
  1175. (tab_index < (int) buttons.size()))
  1176. e->setActiveTab(tab_index);
  1177. m_fields.push_back(spec);
  1178. return;
  1179. }
  1180. errorstream << "Invalid TabHeader element(" << parts.size() << "): '"
  1181. << element << "'" << std::endl;
  1182. }
  1183. void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
  1184. {
  1185. if (m_client == 0) {
  1186. warningstream << "invalid use of item_image_button with m_client==0"
  1187. << std::endl;
  1188. return;
  1189. }
  1190. std::vector<std::string> parts = split(element,';');
  1191. if ((parts.size() == 5) ||
  1192. ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
  1193. {
  1194. std::vector<std::string> v_pos = split(parts[0],',');
  1195. std::vector<std::string> v_geom = split(parts[1],',');
  1196. std::string item_name = parts[2];
  1197. std::string name = parts[3];
  1198. std::string label = parts[4];
  1199. label = unescape_string(label);
  1200. item_name = unescape_string(item_name);
  1201. MY_CHECKPOS("itemimagebutton",0);
  1202. MY_CHECKGEOM("itemimagebutton",1);
  1203. v2s32 pos = padding + pos_offset * spacing;
  1204. pos.X += stof(v_pos[0]) * (float)spacing.X;
  1205. pos.Y += stof(v_pos[1]) * (float)spacing.Y;
  1206. v2s32 geom;
  1207. geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
  1208. geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
  1209. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  1210. if(!data->explicit_size)
  1211. warningstream<<"invalid use of item_image_button without a size[] element"<<std::endl;
  1212. IItemDefManager *idef = m_client->idef();
  1213. ItemStack item;
  1214. item.deSerialize(item_name, idef);
  1215. m_tooltips[name] =
  1216. TooltipSpec(utf8_to_wide(item.getDefinition(idef).description),
  1217. m_default_tooltip_bgcolor,
  1218. m_default_tooltip_color);
  1219. FieldSpec spec(
  1220. name,
  1221. utf8_to_wide(label),
  1222. utf8_to_wide(item_name),
  1223. 258 + m_fields.size()
  1224. );
  1225. gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, L"");
  1226. if (spec.fname == data->focused_fieldname) {
  1227. Environment->setFocus(e);
  1228. }
  1229. spec.ftype = f_Button;
  1230. rect+=data->basepos-padding;
  1231. spec.rect=rect;
  1232. m_fields.push_back(spec);
  1233. pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
  1234. pos.X += stof(v_pos[0]) * (float) spacing.X;
  1235. pos.Y += stof(v_pos[1]) * (float) spacing.Y;
  1236. m_itemimages.emplace_back("", item_name, e, pos, geom);
  1237. m_static_texts.emplace_back(utf8_to_wide(label), rect, e);
  1238. return;
  1239. }
  1240. errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
  1241. }
  1242. void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
  1243. {
  1244. std::vector<std::string> parts = split(element,';');
  1245. if ((parts.size() == 3) ||
  1246. ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
  1247. {
  1248. std::vector<std::string> v_pos = split(parts[0],',');
  1249. std::vector<std::string> v_geom = split(parts[1],',');
  1250. MY_CHECKPOS("box",0);
  1251. MY_CHECKGEOM("box",1);
  1252. v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
  1253. pos.X += stof(v_pos[0]) * (float) spacing.X;
  1254. pos.Y += stof(v_pos[1]) * (float) spacing.Y;
  1255. v2s32 geom;
  1256. geom.X = stof(v_geom[0]) * (float)spacing.X;
  1257. geom.Y = stof(v_geom[1]) * (float)spacing.Y;
  1258. video::SColor tmp_color;
  1259. if (parseColorString(parts[2], tmp_color, false)) {
  1260. BoxDrawSpec spec(pos, geom, tmp_color);
  1261. m_boxes.push_back(spec);
  1262. }
  1263. else {
  1264. errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl;
  1265. }
  1266. return;
  1267. }
  1268. errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl;
  1269. }
  1270. void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)
  1271. {
  1272. std::vector<std::string> parts = split(element,';');
  1273. if (((parts.size() == 1) || (parts.size() == 2)) ||
  1274. ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) {
  1275. parseColorString(parts[0], m_bgcolor, false);
  1276. if (parts.size() == 2) {
  1277. std::string fullscreen = parts[1];
  1278. m_bgfullscreen = is_yes(fullscreen);
  1279. }
  1280. return;
  1281. }
  1282. errorstream << "Invalid bgcolor element(" << parts.size() << "): '" << element << "'"
  1283. << std::endl;
  1284. }
  1285. void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element)
  1286. {
  1287. std::vector<std::string> parts = split(element,';');
  1288. if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) ||
  1289. ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
  1290. {
  1291. parseColorString(parts[0], m_slotbg_n, false);
  1292. parseColorString(parts[1], m_slotbg_h, false);
  1293. if (parts.size() >= 3) {
  1294. if (parseColorString(parts[2], m_slotbordercolor, false)) {
  1295. m_slotborder = true;
  1296. }
  1297. }
  1298. if (parts.size() == 5) {
  1299. video::SColor tmp_color;
  1300. if (parseColorString(parts[3], tmp_color, false))
  1301. m_default_tooltip_bgcolor = tmp_color;
  1302. if (parseColorString(parts[4], tmp_color, false))
  1303. m_default_tooltip_color = tmp_color;
  1304. }
  1305. return;
  1306. }
  1307. errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl;
  1308. }
  1309. void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
  1310. {
  1311. std::vector<std::string> parts = split(element,';');
  1312. if (parts.size() == 2) {
  1313. std::string name = parts[0];
  1314. m_tooltips[name] = TooltipSpec(utf8_to_wide(unescape_string(parts[1])),
  1315. m_default_tooltip_bgcolor, m_default_tooltip_color);
  1316. return;
  1317. }
  1318. if (parts.size() == 4) {
  1319. std::string name = parts[0];
  1320. video::SColor tmp_color1, tmp_color2;
  1321. if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) {
  1322. m_tooltips[name] = TooltipSpec(utf8_to_wide(unescape_string(parts[1])),
  1323. tmp_color1, tmp_color2);
  1324. return;
  1325. }
  1326. }
  1327. errorstream<< "Invalid tooltip element(" << parts.size() << "): '" << element << "'" << std::endl;
  1328. }
  1329. bool GUIFormSpecMenu::parseVersionDirect(const std::string &data)
  1330. {
  1331. //some prechecks
  1332. if (data.empty())
  1333. return false;
  1334. std::vector<std::string> parts = split(data,'[');
  1335. if (parts.size() < 2) {
  1336. return false;
  1337. }
  1338. if (parts[0] != "formspec_version") {
  1339. return false;
  1340. }
  1341. if (is_number(parts[1])) {
  1342. m_formspec_version = mystoi(parts[1]);
  1343. return true;
  1344. }
  1345. return false;
  1346. }
  1347. bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &element)
  1348. {
  1349. if (element.empty())
  1350. return false;
  1351. std::vector<std::string> parts = split(element,'[');
  1352. if (parts.size() < 2)
  1353. return false;
  1354. std::string type = trim(parts[0]);
  1355. std::string description = trim(parts[1]);
  1356. if (type != "size" && type != "invsize")
  1357. return false;
  1358. if (type == "invsize")
  1359. log_deprecated("Deprecated formspec element \"invsize\" is used");
  1360. parseSize(data, description);
  1361. return true;
  1362. }
  1363. bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &element)
  1364. {
  1365. if (element.empty())
  1366. return false;
  1367. std::vector<std::string> parts = split(element, '[');
  1368. if (parts.size() != 2)
  1369. return false;
  1370. std::string type = trim(parts[0]);
  1371. std::string description = trim(parts[1]);
  1372. if (type != "position")
  1373. return false;
  1374. parsePosition(data, description);
  1375. return true;
  1376. }
  1377. void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element)
  1378. {
  1379. std::vector<std::string> parts = split(element, ',');
  1380. if (parts.size() == 2) {
  1381. data->offset.X = stof(parts[0]);
  1382. data->offset.Y = stof(parts[1]);
  1383. return;
  1384. }
  1385. errorstream << "Invalid position element (" << parts.size() << "): '" << element << "'" << std::endl;
  1386. }
  1387. bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &element)
  1388. {
  1389. if (element.empty())
  1390. return false;
  1391. std::vector<std::string> parts = split(element, '[');
  1392. if (parts.size() != 2)
  1393. return false;
  1394. std::string type = trim(parts[0]);
  1395. std::string description = trim(parts[1]);
  1396. if (type != "anchor")
  1397. return false;
  1398. parseAnchor(data, description);
  1399. return true;
  1400. }
  1401. void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element)
  1402. {
  1403. std::vector<std::string> parts = split(element, ',');
  1404. if (parts.size() == 2) {
  1405. data->anchor.X = stof(parts[0]);
  1406. data->anchor.Y = stof(parts[1]);
  1407. return;
  1408. }
  1409. errorstream << "Invalid anchor element (" << parts.size() << "): '" << element
  1410. << "'" << std::endl;
  1411. }
  1412. void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
  1413. {
  1414. //some prechecks
  1415. if (element.empty())
  1416. return;
  1417. std::vector<std::string> parts = split(element,'[');
  1418. // ugly workaround to keep compatibility
  1419. if (parts.size() > 2) {
  1420. if (trim(parts[0]) == "image") {
  1421. for (unsigned int i=2;i< parts.size(); i++) {
  1422. parts[1] += "[" + parts[i];
  1423. }
  1424. }
  1425. else { return; }
  1426. }
  1427. if (parts.size() < 2) {
  1428. return;
  1429. }
  1430. std::string type = trim(parts[0]);
  1431. std::string description = trim(parts[1]);
  1432. if (type == "container") {
  1433. parseContainer(data, description);
  1434. return;
  1435. }
  1436. if (type == "container_end") {
  1437. parseContainerEnd(data);
  1438. return;
  1439. }
  1440. if (type == "list") {
  1441. parseList(data, description);
  1442. return;
  1443. }
  1444. if (type == "listring") {
  1445. parseListRing(data, description);
  1446. return;
  1447. }
  1448. if (type == "checkbox") {
  1449. parseCheckbox(data, description);
  1450. return;
  1451. }
  1452. if (type == "image") {
  1453. parseImage(data, description);
  1454. return;
  1455. }
  1456. if (type == "item_image") {
  1457. parseItemImage(data, description);
  1458. return;
  1459. }
  1460. if (type == "button" || type == "button_exit") {
  1461. parseButton(data, description, type);
  1462. return;
  1463. }
  1464. if (type == "background") {
  1465. parseBackground(data,description);
  1466. return;
  1467. }
  1468. if (type == "tableoptions"){
  1469. parseTableOptions(data,description);
  1470. return;
  1471. }
  1472. if (type == "tablecolumns"){
  1473. parseTableColumns(data,description);
  1474. return;
  1475. }
  1476. if (type == "table"){
  1477. parseTable(data,description);
  1478. return;
  1479. }
  1480. if (type == "textlist"){
  1481. parseTextList(data,description);
  1482. return;
  1483. }
  1484. if (type == "dropdown"){
  1485. parseDropDown(data,description);
  1486. return;
  1487. }
  1488. if (type == "field_close_on_enter") {
  1489. parseFieldCloseOnEnter(data, description);
  1490. return;
  1491. }
  1492. if (type == "pwdfield") {
  1493. parsePwdField(data,description);
  1494. return;
  1495. }
  1496. if ((type == "field") || (type == "textarea")){
  1497. parseField(data,description,type);
  1498. return;
  1499. }
  1500. if (type == "label") {
  1501. parseLabel(data,description);
  1502. return;
  1503. }
  1504. if (type == "vertlabel") {
  1505. parseVertLabel(data,description);
  1506. return;
  1507. }
  1508. if (type == "item_image_button") {
  1509. parseItemImageButton(data,description);
  1510. return;
  1511. }
  1512. if ((type == "image_button") || (type == "image_button_exit")) {
  1513. parseImageButton(data,description,type);
  1514. return;
  1515. }
  1516. if (type == "tabheader") {
  1517. parseTabHeader(data,description);
  1518. return;
  1519. }
  1520. if (type == "box") {
  1521. parseBox(data,description);
  1522. return;
  1523. }
  1524. if (type == "bgcolor") {
  1525. parseBackgroundColor(data,description);
  1526. return;
  1527. }
  1528. if (type == "listcolors") {
  1529. parseListColors(data,description);
  1530. return;
  1531. }
  1532. if (type == "tooltip") {
  1533. parseTooltip(data,description);
  1534. return;
  1535. }
  1536. if (type == "scrollbar") {
  1537. parseScrollBar(data, description);
  1538. return;
  1539. }
  1540. // Ignore others
  1541. infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
  1542. << std::endl;
  1543. }
  1544. void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
  1545. {
  1546. /* useless to regenerate without a screensize */
  1547. if ((screensize.X <= 0) || (screensize.Y <= 0)) {
  1548. return;
  1549. }
  1550. parserData mydata;
  1551. //preserve tables
  1552. for (auto &m_table : m_tables) {
  1553. std::string tablename = m_table.first.fname;
  1554. GUITable *table = m_table.second;
  1555. mydata.table_dyndata[tablename] = table->getDynamicData();
  1556. }
  1557. //set focus
  1558. if (!m_focused_element.empty())
  1559. mydata.focused_fieldname = m_focused_element;
  1560. //preserve focus
  1561. gui::IGUIElement *focused_element = Environment->getFocus();
  1562. if (focused_element && focused_element->getParent() == this) {
  1563. s32 focused_id = focused_element->getID();
  1564. if (focused_id > 257) {
  1565. for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
  1566. if (field.fid == focused_id) {
  1567. mydata.focused_fieldname = field.fname;
  1568. break;
  1569. }
  1570. }
  1571. }
  1572. }
  1573. // Remove children
  1574. removeChildren();
  1575. for (auto &table_it : m_tables) {
  1576. table_it.second->drop();
  1577. }
  1578. mydata.size= v2s32(100,100);
  1579. mydata.screensize = screensize;
  1580. mydata.offset = v2f32(0.5f, 0.5f);
  1581. mydata.anchor = v2f32(0.5f, 0.5f);
  1582. // Base position of contents of form
  1583. mydata.basepos = getBasePos();
  1584. /* Convert m_init_draw_spec to m_inventorylists */
  1585. m_inventorylists.clear();
  1586. m_images.clear();
  1587. m_backgrounds.clear();
  1588. m_itemimages.clear();
  1589. m_tables.clear();
  1590. m_checkboxes.clear();
  1591. m_scrollbars.clear();
  1592. m_fields.clear();
  1593. m_boxes.clear();
  1594. m_tooltips.clear();
  1595. m_inventory_rings.clear();
  1596. m_static_texts.clear();
  1597. m_dropdowns.clear();
  1598. m_bgfullscreen = false;
  1599. {
  1600. v3f formspec_bgcolor = g_settings->getV3F("formspec_default_bg_color");
  1601. m_bgcolor = video::SColor(
  1602. (u8) clamp_u8(g_settings->getS32("formspec_default_bg_opacity")),
  1603. clamp_u8(myround(formspec_bgcolor.X)),
  1604. clamp_u8(myround(formspec_bgcolor.Y)),
  1605. clamp_u8(myround(formspec_bgcolor.Z))
  1606. );
  1607. }
  1608. {
  1609. v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color");
  1610. m_fullscreen_bgcolor = video::SColor(
  1611. (u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")),
  1612. clamp_u8(myround(formspec_bgcolor.X)),
  1613. clamp_u8(myround(formspec_bgcolor.Y)),
  1614. clamp_u8(myround(formspec_bgcolor.Z))
  1615. );
  1616. }
  1617. m_slotbg_n = video::SColor(255,128,128,128);
  1618. m_slotbg_h = video::SColor(255,192,192,192);
  1619. m_default_tooltip_bgcolor = video::SColor(255,110,130,60);
  1620. m_default_tooltip_color = video::SColor(255,255,255,255);
  1621. m_slotbordercolor = video::SColor(200,0,0,0);
  1622. m_slotborder = false;
  1623. // Add tooltip
  1624. {
  1625. assert(!m_tooltip_element);
  1626. // Note: parent != this so that the tooltip isn't clipped by the menu rectangle
  1627. m_tooltip_element = gui::StaticText::add(Environment, L"",
  1628. core::rect<s32>(0, 0, 110, 18));
  1629. m_tooltip_element->enableOverrideColor(true);
  1630. m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
  1631. m_tooltip_element->setDrawBackground(true);
  1632. m_tooltip_element->setDrawBorder(true);
  1633. m_tooltip_element->setOverrideColor(m_default_tooltip_color);
  1634. m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
  1635. m_tooltip_element->setWordWrap(false);
  1636. //we're not parent so no autograb for this one!
  1637. m_tooltip_element->grab();
  1638. }
  1639. std::vector<std::string> elements = split(m_formspec_string,']');
  1640. unsigned int i = 0;
  1641. /* try to read version from first element only */
  1642. if (!elements.empty()) {
  1643. if (parseVersionDirect(elements[0])) {
  1644. i++;
  1645. }
  1646. }
  1647. /* we need size first in order to calculate image scale */
  1648. mydata.explicit_size = false;
  1649. for (; i< elements.size(); i++) {
  1650. if (!parseSizeDirect(&mydata, elements[i])) {
  1651. break;
  1652. }
  1653. }
  1654. /* "position" element is always after "size" element if it used */
  1655. for (; i< elements.size(); i++) {
  1656. if (!parsePositionDirect(&mydata, elements[i])) {
  1657. break;
  1658. }
  1659. }
  1660. /* "anchor" element is always after "position" (or "size" element) if it used */
  1661. for (; i< elements.size(); i++) {
  1662. if (!parseAnchorDirect(&mydata, elements[i])) {
  1663. break;
  1664. }
  1665. }
  1666. /* "no_prepend" element is always after "position" (or "size" element) if it used */
  1667. bool enable_prepends = true;
  1668. for (; i < elements.size(); i++) {
  1669. if (elements[i].empty())
  1670. break;
  1671. std::vector<std::string> parts = split(elements[i], '[');
  1672. if (parts[0] == "no_prepend")
  1673. enable_prepends = false;
  1674. else
  1675. break;
  1676. }
  1677. if (mydata.explicit_size) {
  1678. // compute scaling for specified form size
  1679. if (m_lock) {
  1680. v2u32 current_screensize = RenderingEngine::get_video_driver()->getScreenSize();
  1681. v2u32 delta = current_screensize - m_lockscreensize;
  1682. if (current_screensize.Y > m_lockscreensize.Y)
  1683. delta.Y /= 2;
  1684. else
  1685. delta.Y = 0;
  1686. if (current_screensize.X > m_lockscreensize.X)
  1687. delta.X /= 2;
  1688. else
  1689. delta.X = 0;
  1690. offset = v2s32(delta.X,delta.Y);
  1691. mydata.screensize = m_lockscreensize;
  1692. } else {
  1693. offset = v2s32(0,0);
  1694. }
  1695. double gui_scaling = g_settings->getFloat("gui_scaling");
  1696. double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
  1697. double use_imgsize;
  1698. if (m_lock) {
  1699. // In fixed-size mode, inventory image size
  1700. // is 0.53 inch multiplied by the gui_scaling
  1701. // config parameter. This magic size is chosen
  1702. // to make the main menu (15.5 inventory images
  1703. // wide, including border) just fit into the
  1704. // default window (800 pixels wide) at 96 DPI
  1705. // and default scaling (1.00).
  1706. use_imgsize = 0.5555 * screen_dpi * gui_scaling;
  1707. } else {
  1708. // In variable-size mode, we prefer to make the
  1709. // inventory image size 1/15 of screen height,
  1710. // multiplied by the gui_scaling config parameter.
  1711. // If the preferred size won't fit the whole
  1712. // form on the screen, either horizontally or
  1713. // vertically, then we scale it down to fit.
  1714. // (The magic numbers in the computation of what
  1715. // fits arise from the scaling factors in the
  1716. // following stanza, including the form border,
  1717. // help text space, and 0.1 inventory slot spare.)
  1718. // However, a minimum size is also set, that
  1719. // the image size can't be less than 0.3 inch
  1720. // multiplied by gui_scaling, even if this means
  1721. // the form doesn't fit the screen.
  1722. double prefer_imgsize = mydata.screensize.Y / 15 *
  1723. gui_scaling;
  1724. double fitx_imgsize = mydata.screensize.X /
  1725. ((5.0/4.0) * (0.5 + mydata.invsize.X));
  1726. double fity_imgsize = mydata.screensize.Y /
  1727. ((15.0/13.0) * (0.85 * mydata.invsize.Y));
  1728. double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
  1729. double min_imgsize = 0.3 * screen_dpi * gui_scaling;
  1730. use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize,
  1731. MYMIN(fitx_imgsize, fity_imgsize)));
  1732. }
  1733. // Everything else is scaled in proportion to the
  1734. // inventory image size. The inventory slot spacing
  1735. // is 5/4 image size horizontally and 15/13 image size
  1736. // vertically. The padding around the form (incorporating
  1737. // the border of the outer inventory slots) is 3/8
  1738. // image size. Font height (baseline to baseline)
  1739. // is 2/5 vertical inventory slot spacing, and button
  1740. // half-height is 7/8 of font height.
  1741. imgsize = v2s32(use_imgsize, use_imgsize);
  1742. spacing = v2s32(use_imgsize*5.0/4, use_imgsize*15.0/13);
  1743. padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8);
  1744. m_btn_height = use_imgsize*15.0/13 * 0.35;
  1745. m_font = g_fontengine->getFont();
  1746. mydata.size = v2s32(
  1747. padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
  1748. padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
  1749. );
  1750. DesiredRect = mydata.rect = core::rect<s32>(
  1751. (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X,
  1752. (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y,
  1753. (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * (f32)mydata.size.X) + offset.X,
  1754. (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * (f32)mydata.size.Y) + offset.Y
  1755. );
  1756. } else {
  1757. // Non-size[] form must consist only of text fields and
  1758. // implicit "Proceed" button. Use default font, and
  1759. // temporary form size which will be recalculated below.
  1760. m_font = g_fontengine->getFont();
  1761. m_btn_height = font_line_height(m_font) * 0.875;
  1762. DesiredRect = core::rect<s32>(
  1763. (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * 580.0),
  1764. (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * 300.0),
  1765. (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * 580.0),
  1766. (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * 300.0)
  1767. );
  1768. }
  1769. recalculateAbsolutePosition(false);
  1770. mydata.basepos = getBasePos();
  1771. m_tooltip_element->setOverrideFont(m_font);
  1772. gui::IGUISkin *skin = Environment->getSkin();
  1773. sanity_check(skin);
  1774. gui::IGUIFont *old_font = skin->getFont();
  1775. skin->setFont(m_font);
  1776. pos_offset = v2s32();
  1777. if (enable_prepends) {
  1778. std::vector<std::string> prepend_elements = split(m_formspec_prepend, ']');
  1779. for (const auto &element : prepend_elements)
  1780. parseElement(&mydata, element);
  1781. }
  1782. for (; i< elements.size(); i++) {
  1783. parseElement(&mydata, elements[i]);
  1784. }
  1785. if (!container_stack.empty()) {
  1786. errorstream << "Invalid formspec string: container was never closed!"
  1787. << std::endl;
  1788. }
  1789. // If there are fields without explicit size[], add a "Proceed"
  1790. // button and adjust size to fit all the fields.
  1791. if (!m_fields.empty() && !mydata.explicit_size) {
  1792. mydata.rect = core::rect<s32>(
  1793. mydata.screensize.X/2 - 580/2,
  1794. mydata.screensize.Y/2 - 300/2,
  1795. mydata.screensize.X/2 + 580/2,
  1796. mydata.screensize.Y/2 + 240/2+(m_fields.size()*60)
  1797. );
  1798. DesiredRect = mydata.rect;
  1799. recalculateAbsolutePosition(false);
  1800. mydata.basepos = getBasePos();
  1801. {
  1802. v2s32 pos = mydata.basepos;
  1803. pos.Y = ((m_fields.size()+2)*60);
  1804. v2s32 size = DesiredRect.getSize();
  1805. mydata.rect =
  1806. core::rect<s32>(size.X/2-70, pos.Y,
  1807. (size.X/2-70)+140, pos.Y + (m_btn_height*2));
  1808. const wchar_t *text = wgettext("Proceed");
  1809. Environment->addButton(mydata.rect, this, 257, text);
  1810. delete[] text;
  1811. }
  1812. }
  1813. //set initial focus if parser didn't set it
  1814. focused_element = Environment->getFocus();
  1815. if (!focused_element
  1816. || !isMyChild(focused_element)
  1817. || focused_element->getType() == gui::EGUIET_TAB_CONTROL)
  1818. setInitialFocus();
  1819. skin->setFont(old_font);
  1820. }
  1821. #ifdef __ANDROID__
  1822. bool GUIFormSpecMenu::getAndroidUIInput()
  1823. {
  1824. /* no dialog shown */
  1825. if (m_JavaDialogFieldName == "") {
  1826. return false;
  1827. }
  1828. /* still waiting */
  1829. if (porting::getInputDialogState() == -1) {
  1830. return true;
  1831. }
  1832. std::string fieldname = m_JavaDialogFieldName;
  1833. m_JavaDialogFieldName = "";
  1834. /* no value abort dialog processing */
  1835. if (porting::getInputDialogState() != 0) {
  1836. return false;
  1837. }
  1838. for(std::vector<FieldSpec>::iterator iter = m_fields.begin();
  1839. iter != m_fields.end(); ++iter) {
  1840. if (iter->fname != fieldname) {
  1841. continue;
  1842. }
  1843. IGUIElement* tochange = getElementFromId(iter->fid);
  1844. if (tochange == 0) {
  1845. return false;
  1846. }
  1847. if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) {
  1848. return false;
  1849. }
  1850. std::string text = porting::getInputDialogValue();
  1851. ((gui::IGUIEditBox*) tochange)->
  1852. setText(utf8_to_wide(text).c_str());
  1853. }
  1854. return false;
  1855. }
  1856. #endif
  1857. GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
  1858. {
  1859. core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
  1860. for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
  1861. for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
  1862. s32 item_i = i + s.start_item_i;
  1863. s32 x = (i%s.geom.X) * spacing.X;
  1864. s32 y = (i/s.geom.X) * spacing.Y;
  1865. v2s32 p0(x,y);
  1866. core::rect<s32> rect = imgrect + s.pos + p0;
  1867. if(rect.isPointInside(p))
  1868. {
  1869. return ItemSpec(s.inventoryloc, s.listname, item_i);
  1870. }
  1871. }
  1872. }
  1873. return ItemSpec(InventoryLocation(), "", -1);
  1874. }
  1875. void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase,
  1876. bool &item_hovered)
  1877. {
  1878. video::IVideoDriver* driver = Environment->getVideoDriver();
  1879. Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
  1880. if(!inv){
  1881. warningstream<<"GUIFormSpecMenu::drawList(): "
  1882. <<"The inventory location "
  1883. <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
  1884. <<std::endl;
  1885. return;
  1886. }
  1887. InventoryList *ilist = inv->getList(s.listname);
  1888. if(!ilist){
  1889. warningstream<<"GUIFormSpecMenu::drawList(): "
  1890. <<"The inventory list \""<<s.listname<<"\" @ \""
  1891. <<s.inventoryloc.dump()<<"\" doesn't exist"
  1892. <<std::endl;
  1893. return;
  1894. }
  1895. core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
  1896. for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
  1897. {
  1898. s32 item_i = i + s.start_item_i;
  1899. if(item_i >= (s32) ilist->getSize())
  1900. break;
  1901. s32 x = (i%s.geom.X) * spacing.X;
  1902. s32 y = (i/s.geom.X) * spacing.Y;
  1903. v2s32 p(x,y);
  1904. core::rect<s32> rect = imgrect + s.pos + p;
  1905. ItemStack item;
  1906. if(ilist)
  1907. item = ilist->getItem(item_i);
  1908. bool selected = m_selected_item
  1909. && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
  1910. && m_selected_item->listname == s.listname
  1911. && m_selected_item->i == item_i;
  1912. bool hovering = rect.isPointInside(m_pointer);
  1913. ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED :
  1914. (hovering ? IT_ROT_HOVERED : IT_ROT_NONE);
  1915. if (phase == 0) {
  1916. if (hovering) {
  1917. item_hovered = true;
  1918. driver->draw2DRectangle(m_slotbg_h, rect, &AbsoluteClippingRect);
  1919. } else {
  1920. driver->draw2DRectangle(m_slotbg_n, rect, &AbsoluteClippingRect);
  1921. }
  1922. }
  1923. //Draw inv slot borders
  1924. if (m_slotborder) {
  1925. s32 x1 = rect.UpperLeftCorner.X;
  1926. s32 y1 = rect.UpperLeftCorner.Y;
  1927. s32 x2 = rect.LowerRightCorner.X;
  1928. s32 y2 = rect.LowerRightCorner.Y;
  1929. s32 border = 1;
  1930. driver->draw2DRectangle(m_slotbordercolor,
  1931. core::rect<s32>(v2s32(x1 - border, y1 - border),
  1932. v2s32(x2 + border, y1)), NULL);
  1933. driver->draw2DRectangle(m_slotbordercolor,
  1934. core::rect<s32>(v2s32(x1 - border, y2),
  1935. v2s32(x2 + border, y2 + border)), NULL);
  1936. driver->draw2DRectangle(m_slotbordercolor,
  1937. core::rect<s32>(v2s32(x1 - border, y1),
  1938. v2s32(x1, y2)), NULL);
  1939. driver->draw2DRectangle(m_slotbordercolor,
  1940. core::rect<s32>(v2s32(x2, y1),
  1941. v2s32(x2 + border, y2)), NULL);
  1942. }
  1943. if(phase == 1)
  1944. {
  1945. // Draw item stack
  1946. if(selected)
  1947. {
  1948. item.takeItem(m_selected_amount);
  1949. }
  1950. if(!item.empty())
  1951. {
  1952. drawItemStack(driver, m_font, item,
  1953. rect, &AbsoluteClippingRect, m_client,
  1954. rotation_kind);
  1955. }
  1956. // Draw tooltip
  1957. std::wstring tooltip_text;
  1958. if (hovering && !m_selected_item) {
  1959. const std::string &desc = item.metadata.getString("description");
  1960. if (desc.empty())
  1961. tooltip_text =
  1962. utf8_to_wide(item.getDefinition(m_client->idef()).description);
  1963. else
  1964. tooltip_text = utf8_to_wide(desc);
  1965. if (!item.name.empty()) {
  1966. if (tooltip_text.empty())
  1967. tooltip_text = utf8_to_wide(item.name);
  1968. if (m_tooltip_append_itemname)
  1969. tooltip_text += utf8_to_wide(" [" + item.name + "]");
  1970. }
  1971. }
  1972. if (!tooltip_text.empty()) {
  1973. showTooltip(tooltip_text, m_default_tooltip_color,
  1974. m_default_tooltip_bgcolor);
  1975. }
  1976. }
  1977. }
  1978. }
  1979. void GUIFormSpecMenu::drawSelectedItem()
  1980. {
  1981. video::IVideoDriver* driver = Environment->getVideoDriver();
  1982. if (!m_selected_item) {
  1983. drawItemStack(driver, m_font, ItemStack(),
  1984. core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
  1985. NULL, m_client, IT_ROT_DRAGGED);
  1986. return;
  1987. }
  1988. Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
  1989. sanity_check(inv);
  1990. InventoryList *list = inv->getList(m_selected_item->listname);
  1991. sanity_check(list);
  1992. ItemStack stack = list->getItem(m_selected_item->i);
  1993. stack.count = m_selected_amount;
  1994. core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
  1995. core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
  1996. rect.constrainTo(driver->getViewPort());
  1997. drawItemStack(driver, m_font, stack, rect, NULL, m_client, IT_ROT_DRAGGED);
  1998. }
  1999. void GUIFormSpecMenu::drawMenu()
  2000. {
  2001. if (m_form_src) {
  2002. const std::string &newform = m_form_src->getForm();
  2003. if (newform != m_formspec_string) {
  2004. m_formspec_string = newform;
  2005. regenerateGui(m_screensize_old);
  2006. }
  2007. }
  2008. gui::IGUISkin* skin = Environment->getSkin();
  2009. sanity_check(skin != NULL);
  2010. gui::IGUIFont *old_font = skin->getFont();
  2011. skin->setFont(m_font);
  2012. updateSelectedItem();
  2013. video::IVideoDriver* driver = Environment->getVideoDriver();
  2014. v2u32 screenSize = driver->getScreenSize();
  2015. core::rect<s32> allbg(0, 0, screenSize.X, screenSize.Y);
  2016. if (m_bgfullscreen)
  2017. driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg);
  2018. else
  2019. driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);
  2020. m_tooltip_element->setVisible(false);
  2021. /*
  2022. Draw backgrounds
  2023. */
  2024. for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_backgrounds) {
  2025. video::ITexture *texture = m_tsrc->getTexture(spec.name);
  2026. if (texture != 0) {
  2027. // Image size on screen
  2028. core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
  2029. // Image rectangle on screen
  2030. core::rect<s32> rect = imgrect + spec.pos;
  2031. if (spec.clip) {
  2032. core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
  2033. rect = core::rect<s32>(AbsoluteRect.UpperLeftCorner.X - spec.pos.X,
  2034. AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y,
  2035. AbsoluteRect.UpperLeftCorner.X + absrec_size.Width + spec.pos.X,
  2036. AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
  2037. }
  2038. const video::SColor color(255,255,255,255);
  2039. const video::SColor colors[] = {color,color,color,color};
  2040. draw2DImageFilterScaled(driver, texture, rect,
  2041. core::rect<s32>(core::position2d<s32>(0,0),
  2042. core::dimension2di(texture->getOriginalSize())),
  2043. NULL/*&AbsoluteClippingRect*/, colors, true);
  2044. } else {
  2045. errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
  2046. errorstream << "\t" << spec.name << std::endl;
  2047. }
  2048. }
  2049. /*
  2050. Draw Boxes
  2051. */
  2052. for (const GUIFormSpecMenu::BoxDrawSpec &spec : m_boxes) {
  2053. irr::video::SColor todraw = spec.color;
  2054. todraw.setAlpha(140);
  2055. core::rect<s32> rect(spec.pos.X,spec.pos.Y,
  2056. spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y);
  2057. driver->draw2DRectangle(todraw, rect, 0);
  2058. }
  2059. /*
  2060. Call base class
  2061. */
  2062. gui::IGUIElement::draw();
  2063. /*
  2064. Draw images
  2065. */
  2066. for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_images) {
  2067. video::ITexture *texture = m_tsrc->getTexture(spec.name);
  2068. if (texture != 0) {
  2069. const core::dimension2d<u32>& img_origsize = texture->getOriginalSize();
  2070. // Image size on screen
  2071. core::rect<s32> imgrect;
  2072. if (spec.scale)
  2073. imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y);
  2074. else {
  2075. imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height);
  2076. }
  2077. // Image rectangle on screen
  2078. core::rect<s32> rect = imgrect + spec.pos;
  2079. const video::SColor color(255,255,255,255);
  2080. const video::SColor colors[] = {color,color,color,color};
  2081. draw2DImageFilterScaled(driver, texture, rect,
  2082. core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
  2083. NULL/*&AbsoluteClippingRect*/, colors, true);
  2084. }
  2085. else {
  2086. errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl;
  2087. errorstream << "\t" << spec.name << std::endl;
  2088. }
  2089. }
  2090. /*
  2091. Draw item images
  2092. */
  2093. for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_itemimages) {
  2094. if (m_client == 0)
  2095. break;
  2096. IItemDefManager *idef = m_client->idef();
  2097. ItemStack item;
  2098. item.deSerialize(spec.item_name, idef);
  2099. core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
  2100. // Viewport rectangle on screen
  2101. core::rect<s32> rect = imgrect + spec.pos;
  2102. if (spec.parent_button && spec.parent_button->isPressed()) {
  2103. #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
  2104. rect += core::dimension2d<s32>(
  2105. 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
  2106. #else
  2107. rect += core::dimension2d<s32>(
  2108. skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
  2109. skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
  2110. #endif
  2111. }
  2112. drawItemStack(driver, m_font, item, rect, &AbsoluteClippingRect,
  2113. m_client, IT_ROT_NONE);
  2114. }
  2115. /*
  2116. Draw items
  2117. Phase 0: Item slot rectangles
  2118. Phase 1: Item images; prepare tooltip
  2119. */
  2120. bool item_hovered = false;
  2121. int start_phase = 0;
  2122. for (int phase = start_phase; phase <= 1; phase++) {
  2123. for (const GUIFormSpecMenu::ListDrawSpec &spec : m_inventorylists) {
  2124. drawList(spec, phase, item_hovered);
  2125. }
  2126. }
  2127. if (!item_hovered) {
  2128. drawItemStack(driver, m_font, ItemStack(),
  2129. core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
  2130. NULL, m_client, IT_ROT_HOVERED);
  2131. }
  2132. /* TODO find way to show tooltips on touchscreen */
  2133. #ifndef HAVE_TOUCHSCREENGUI
  2134. m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition();
  2135. #endif
  2136. /*
  2137. Draw static text elements
  2138. */
  2139. for (const GUIFormSpecMenu::StaticTextSpec &spec : m_static_texts) {
  2140. core::rect<s32> rect = spec.rect;
  2141. if (spec.parent_button && spec.parent_button->isPressed()) {
  2142. #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
  2143. rect += core::dimension2d<s32>(
  2144. 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
  2145. #else
  2146. // Use image offset instead of text's because its a bit smaller
  2147. // and fits better, also TEXT_OFFSET_X is always 0
  2148. rect += core::dimension2d<s32>(
  2149. skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
  2150. skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
  2151. #endif
  2152. }
  2153. video::SColor color(255, 255, 255, 255);
  2154. m_font->draw(spec.text.c_str(), rect, color, true, true, &rect);
  2155. }
  2156. /*
  2157. Draw fields/buttons tooltips
  2158. */
  2159. gui::IGUIElement *hovered =
  2160. Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
  2161. if (hovered != NULL) {
  2162. s32 id = hovered->getID();
  2163. u64 delta = 0;
  2164. if (id == -1) {
  2165. m_old_tooltip_id = id;
  2166. } else {
  2167. if (id == m_old_tooltip_id) {
  2168. delta = porting::getDeltaMs(m_hovered_time, porting::getTimeMs());
  2169. } else {
  2170. m_hovered_time = porting::getTimeMs();
  2171. m_old_tooltip_id = id;
  2172. }
  2173. }
  2174. // Find and update the current tooltip
  2175. if (id != -1 && delta >= m_tooltip_show_delay) {
  2176. for (const FieldSpec &field : m_fields) {
  2177. if (field.fid != id)
  2178. continue;
  2179. const std::wstring &text = m_tooltips[field.fname].tooltip;
  2180. if (!text.empty())
  2181. showTooltip(text, m_tooltips[field.fname].color,
  2182. m_tooltips[field.fname].bgcolor);
  2183. break;
  2184. }
  2185. }
  2186. }
  2187. m_tooltip_element->draw();
  2188. /*
  2189. Draw dragged item stack
  2190. */
  2191. drawSelectedItem();
  2192. skin->setFont(old_font);
  2193. }
  2194. void GUIFormSpecMenu::showTooltip(const std::wstring &text,
  2195. const irr::video::SColor &color, const irr::video::SColor &bgcolor)
  2196. {
  2197. const std::wstring ntext = translate_string(text);
  2198. m_tooltip_element->setOverrideColor(color);
  2199. m_tooltip_element->setBackgroundColor(bgcolor);
  2200. setStaticText(m_tooltip_element, ntext.c_str());
  2201. // Tooltip size and offset
  2202. s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
  2203. #if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1
  2204. std::vector<std::wstring> text_rows = str_split(ntext, L'\n');
  2205. s32 tooltip_height = m_tooltip_element->getTextHeight() * text_rows.size() + 5;
  2206. #else
  2207. s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
  2208. #endif
  2209. v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
  2210. int tooltip_offset_x = m_btn_height;
  2211. int tooltip_offset_y = m_btn_height;
  2212. #ifdef __ANDROID__
  2213. tooltip_offset_x *= 3;
  2214. tooltip_offset_y = 0;
  2215. if (m_pointer.X > (s32)screenSize.X / 2)
  2216. tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
  2217. #endif
  2218. // Calculate and set the tooltip position
  2219. s32 tooltip_x = m_pointer.X + tooltip_offset_x;
  2220. s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
  2221. if (tooltip_x + tooltip_width > (s32)screenSize.X)
  2222. tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height;
  2223. if (tooltip_y + tooltip_height > (s32)screenSize.Y)
  2224. tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
  2225. m_tooltip_element->setRelativePosition(
  2226. core::rect<s32>(
  2227. core::position2d<s32>(tooltip_x, tooltip_y),
  2228. core::dimension2d<s32>(tooltip_width, tooltip_height)
  2229. )
  2230. );
  2231. // Display the tooltip
  2232. m_tooltip_element->setVisible(true);
  2233. bringToFront(m_tooltip_element);
  2234. }
  2235. void GUIFormSpecMenu::updateSelectedItem()
  2236. {
  2237. // If the selected stack has become empty for some reason, deselect it.
  2238. // If the selected stack has become inaccessible, deselect it.
  2239. // If the selected stack has become smaller, adjust m_selected_amount.
  2240. ItemStack selected = verifySelectedItem();
  2241. // WARNING: BLACK MAGIC
  2242. // See if there is a stack suited for our current guess.
  2243. // If such stack does not exist, clear the guess.
  2244. if (!m_selected_content_guess.name.empty() &&
  2245. selected.name == m_selected_content_guess.name &&
  2246. selected.count == m_selected_content_guess.count){
  2247. // Selected item fits the guess. Skip the black magic.
  2248. } else if (!m_selected_content_guess.name.empty()) {
  2249. bool found = false;
  2250. for(u32 i=0; i<m_inventorylists.size() && !found; i++){
  2251. const ListDrawSpec &s = m_inventorylists[i];
  2252. Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
  2253. if(!inv)
  2254. continue;
  2255. InventoryList *list = inv->getList(s.listname);
  2256. if(!list)
  2257. continue;
  2258. for(s32 i=0; i<s.geom.X*s.geom.Y && !found; i++){
  2259. u32 item_i = i + s.start_item_i;
  2260. if(item_i >= list->getSize())
  2261. continue;
  2262. ItemStack stack = list->getItem(item_i);
  2263. if(stack.name == m_selected_content_guess.name &&
  2264. stack.count == m_selected_content_guess.count){
  2265. found = true;
  2266. infostream<<"Client: Changing selected content guess to "
  2267. <<s.inventoryloc.dump()<<" "<<s.listname
  2268. <<" "<<item_i<<std::endl;
  2269. delete m_selected_item;
  2270. m_selected_item = new ItemSpec(s.inventoryloc, s.listname, item_i);
  2271. m_selected_amount = stack.count;
  2272. }
  2273. }
  2274. }
  2275. if(!found){
  2276. infostream<<"Client: Discarding selected content guess: "
  2277. <<m_selected_content_guess.getItemString()<<std::endl;
  2278. m_selected_content_guess.name = "";
  2279. }
  2280. }
  2281. // If craftresult is nonempty and nothing else is selected, select it now.
  2282. if(!m_selected_item)
  2283. {
  2284. for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
  2285. if(s.listname == "craftpreview")
  2286. {
  2287. Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
  2288. InventoryList *list = inv->getList("craftresult");
  2289. if(list && list->getSize() >= 1 && !list->getItem(0).empty())
  2290. {
  2291. m_selected_item = new ItemSpec;
  2292. m_selected_item->inventoryloc = s.inventoryloc;
  2293. m_selected_item->listname = "craftresult";
  2294. m_selected_item->i = 0;
  2295. m_selected_amount = 0;
  2296. m_selected_dragging = false;
  2297. break;
  2298. }
  2299. }
  2300. }
  2301. }
  2302. // If craftresult is selected, keep the whole stack selected
  2303. if(m_selected_item && m_selected_item->listname == "craftresult")
  2304. {
  2305. m_selected_amount = verifySelectedItem().count;
  2306. }
  2307. }
  2308. ItemStack GUIFormSpecMenu::verifySelectedItem()
  2309. {
  2310. // If the selected stack has become empty for some reason, deselect it.
  2311. // If the selected stack has become inaccessible, deselect it.
  2312. // If the selected stack has become smaller, adjust m_selected_amount.
  2313. // Return the selected stack.
  2314. if(m_selected_item)
  2315. {
  2316. if(m_selected_item->isValid())
  2317. {
  2318. Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
  2319. if(inv)
  2320. {
  2321. InventoryList *list = inv->getList(m_selected_item->listname);
  2322. if(list && (u32) m_selected_item->i < list->getSize())
  2323. {
  2324. ItemStack stack = list->getItem(m_selected_item->i);
  2325. if(m_selected_amount > stack.count)
  2326. m_selected_amount = stack.count;
  2327. if(!stack.empty())
  2328. return stack;
  2329. }
  2330. }
  2331. }
  2332. // selection was not valid
  2333. delete m_selected_item;
  2334. m_selected_item = NULL;
  2335. m_selected_amount = 0;
  2336. m_selected_dragging = false;
  2337. }
  2338. return ItemStack();
  2339. }
  2340. void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
  2341. {
  2342. if(m_text_dst)
  2343. {
  2344. StringMap fields;
  2345. if (quitmode == quit_mode_accept) {
  2346. fields["quit"] = "true";
  2347. }
  2348. if (quitmode == quit_mode_cancel) {
  2349. fields["quit"] = "true";
  2350. m_text_dst->gotText(fields);
  2351. return;
  2352. }
  2353. if (current_keys_pending.key_down) {
  2354. fields["key_down"] = "true";
  2355. current_keys_pending.key_down = false;
  2356. }
  2357. if (current_keys_pending.key_up) {
  2358. fields["key_up"] = "true";
  2359. current_keys_pending.key_up = false;
  2360. }
  2361. if (current_keys_pending.key_enter) {
  2362. fields["key_enter"] = "true";
  2363. current_keys_pending.key_enter = false;
  2364. }
  2365. if (!current_field_enter_pending.empty()) {
  2366. fields["key_enter_field"] = current_field_enter_pending;
  2367. current_field_enter_pending = "";
  2368. }
  2369. if (current_keys_pending.key_escape) {
  2370. fields["key_escape"] = "true";
  2371. current_keys_pending.key_escape = false;
  2372. }
  2373. for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
  2374. if(s.send) {
  2375. std::string name = s.fname;
  2376. if (s.ftype == f_Button) {
  2377. fields[name] = wide_to_utf8(s.flabel);
  2378. } else if (s.ftype == f_Table) {
  2379. GUITable *table = getTable(s.fname);
  2380. if (table) {
  2381. fields[name] = table->checkEvent();
  2382. }
  2383. }
  2384. else if(s.ftype == f_DropDown) {
  2385. // no dynamic cast possible due to some distributions shipped
  2386. // without rtti support in irrlicht
  2387. IGUIElement * element = getElementFromId(s.fid);
  2388. gui::IGUIComboBox *e = NULL;
  2389. if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
  2390. e = static_cast<gui::IGUIComboBox*>(element);
  2391. }
  2392. s32 selected = e->getSelected();
  2393. if (selected >= 0) {
  2394. std::vector<std::string> *dropdown_values =
  2395. getDropDownValues(s.fname);
  2396. if (dropdown_values && selected < (s32)dropdown_values->size()) {
  2397. fields[name] = (*dropdown_values)[selected];
  2398. }
  2399. }
  2400. }
  2401. else if (s.ftype == f_TabHeader) {
  2402. // no dynamic cast possible due to some distributions shipped
  2403. // without rttzi support in irrlicht
  2404. IGUIElement * element = getElementFromId(s.fid);
  2405. gui::IGUITabControl *e = NULL;
  2406. if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
  2407. e = static_cast<gui::IGUITabControl *>(element);
  2408. }
  2409. if (e != 0) {
  2410. std::stringstream ss;
  2411. ss << (e->getActiveTab() +1);
  2412. fields[name] = ss.str();
  2413. }
  2414. }
  2415. else if (s.ftype == f_CheckBox) {
  2416. // no dynamic cast possible due to some distributions shipped
  2417. // without rtti support in irrlicht
  2418. IGUIElement * element = getElementFromId(s.fid);
  2419. gui::IGUICheckBox *e = NULL;
  2420. if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
  2421. e = static_cast<gui::IGUICheckBox*>(element);
  2422. }
  2423. if (e != 0) {
  2424. if (e->isChecked())
  2425. fields[name] = "true";
  2426. else
  2427. fields[name] = "false";
  2428. }
  2429. }
  2430. else if (s.ftype == f_ScrollBar) {
  2431. // no dynamic cast possible due to some distributions shipped
  2432. // without rtti support in irrlicht
  2433. IGUIElement * element = getElementFromId(s.fid);
  2434. gui::IGUIScrollBar *e = NULL;
  2435. if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) {
  2436. e = static_cast<gui::IGUIScrollBar*>(element);
  2437. }
  2438. if (e != 0) {
  2439. std::stringstream os;
  2440. os << e->getPos();
  2441. if (s.fdefault == L"Changed")
  2442. fields[name] = "CHG:" + os.str();
  2443. else
  2444. fields[name] = "VAL:" + os.str();
  2445. }
  2446. }
  2447. else
  2448. {
  2449. IGUIElement* e = getElementFromId(s.fid);
  2450. if(e != NULL) {
  2451. fields[name] = wide_to_utf8(e->getText());
  2452. }
  2453. }
  2454. }
  2455. }
  2456. m_text_dst->gotText(fields);
  2457. }
  2458. }
  2459. static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent)
  2460. {
  2461. while(tocheck != NULL) {
  2462. if (tocheck == parent) {
  2463. return true;
  2464. }
  2465. tocheck = tocheck->getParent();
  2466. }
  2467. return false;
  2468. }
  2469. bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
  2470. {
  2471. // The IGUITabControl renders visually using the skin's selected
  2472. // font, which we override for the duration of form drawing,
  2473. // but computes tab hotspots based on how it would have rendered
  2474. // using the font that is selected at the time of button release.
  2475. // To make these two consistent, temporarily override the skin's
  2476. // font while the IGUITabControl is processing the event.
  2477. if (event.EventType == EET_MOUSE_INPUT_EVENT &&
  2478. event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
  2479. s32 x = event.MouseInput.X;
  2480. s32 y = event.MouseInput.Y;
  2481. gui::IGUIElement *hovered =
  2482. Environment->getRootGUIElement()->getElementFromPoint(
  2483. core::position2d<s32>(x, y));
  2484. if (hovered && isMyChild(hovered) &&
  2485. hovered->getType() == gui::EGUIET_TAB_CONTROL) {
  2486. gui::IGUISkin* skin = Environment->getSkin();
  2487. sanity_check(skin != NULL);
  2488. gui::IGUIFont *old_font = skin->getFont();
  2489. skin->setFont(m_font);
  2490. bool retval = hovered->OnEvent(event);
  2491. skin->setFont(old_font);
  2492. return retval;
  2493. }
  2494. }
  2495. // Fix Esc/Return key being eaten by checkboxen and tables
  2496. if(event.EventType==EET_KEY_INPUT_EVENT) {
  2497. KeyPress kp(event.KeyInput);
  2498. if (kp == EscapeKey || kp == CancelKey
  2499. || kp == getKeySetting("keymap_inventory")
  2500. || event.KeyInput.Key==KEY_RETURN) {
  2501. gui::IGUIElement *focused = Environment->getFocus();
  2502. if (focused && isMyChild(focused) &&
  2503. (focused->getType() == gui::EGUIET_LIST_BOX ||
  2504. focused->getType() == gui::EGUIET_CHECK_BOX)) {
  2505. OnEvent(event);
  2506. return true;
  2507. }
  2508. }
  2509. }
  2510. // Mouse wheel events: send to hovered element instead of focused
  2511. if(event.EventType==EET_MOUSE_INPUT_EVENT
  2512. && event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
  2513. s32 x = event.MouseInput.X;
  2514. s32 y = event.MouseInput.Y;
  2515. gui::IGUIElement *hovered =
  2516. Environment->getRootGUIElement()->getElementFromPoint(
  2517. core::position2d<s32>(x, y));
  2518. if (hovered && isMyChild(hovered)) {
  2519. hovered->OnEvent(event);
  2520. return true;
  2521. }
  2522. }
  2523. if (event.EventType == EET_MOUSE_INPUT_EVENT) {
  2524. s32 x = event.MouseInput.X;
  2525. s32 y = event.MouseInput.Y;
  2526. gui::IGUIElement *hovered =
  2527. Environment->getRootGUIElement()->getElementFromPoint(
  2528. core::position2d<s32>(x, y));
  2529. if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
  2530. m_old_tooltip_id = -1;
  2531. }
  2532. if (!isChild(hovered,this)) {
  2533. if (DoubleClickDetection(event)) {
  2534. return true;
  2535. }
  2536. }
  2537. }
  2538. #ifdef __ANDROID__
  2539. // display software keyboard when clicking edit boxes
  2540. if (event.EventType == EET_MOUSE_INPUT_EVENT
  2541. && event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
  2542. gui::IGUIElement *hovered =
  2543. Environment->getRootGUIElement()->getElementFromPoint(
  2544. core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
  2545. if ((hovered) && (hovered->getType() == irr::gui::EGUIET_EDIT_BOX)) {
  2546. bool retval = hovered->OnEvent(event);
  2547. if (retval) {
  2548. Environment->setFocus(hovered);
  2549. }
  2550. m_JavaDialogFieldName = getNameByID(hovered->getID());
  2551. std::string message = gettext("Enter ");
  2552. std::string label = wide_to_utf8(getLabelByID(hovered->getID()));
  2553. if (label == "") {
  2554. label = "text";
  2555. }
  2556. message += gettext(label) + ":";
  2557. /* single line text input */
  2558. int type = 2;
  2559. /* multi line text input */
  2560. if (((gui::IGUIEditBox*) hovered)->isMultiLineEnabled()) {
  2561. type = 1;
  2562. }
  2563. /* passwords are always single line */
  2564. if (((gui::IGUIEditBox*) hovered)->isPasswordBox()) {
  2565. type = 3;
  2566. }
  2567. porting::showInputDialog(gettext("ok"), "",
  2568. wide_to_utf8(((gui::IGUIEditBox*) hovered)->getText()),
  2569. type);
  2570. return retval;
  2571. }
  2572. }
  2573. if (event.EventType == EET_TOUCH_INPUT_EVENT)
  2574. {
  2575. SEvent translated;
  2576. memset(&translated, 0, sizeof(SEvent));
  2577. translated.EventType = EET_MOUSE_INPUT_EVENT;
  2578. gui::IGUIElement* root = Environment->getRootGUIElement();
  2579. if (!root) {
  2580. errorstream
  2581. << "GUIFormSpecMenu::preprocessEvent unable to get root element"
  2582. << std::endl;
  2583. return false;
  2584. }
  2585. gui::IGUIElement* hovered = root->getElementFromPoint(
  2586. core::position2d<s32>(
  2587. event.TouchInput.X,
  2588. event.TouchInput.Y));
  2589. translated.MouseInput.X = event.TouchInput.X;
  2590. translated.MouseInput.Y = event.TouchInput.Y;
  2591. translated.MouseInput.Control = false;
  2592. bool dont_send_event = false;
  2593. if (event.TouchInput.touchedCount == 1) {
  2594. switch (event.TouchInput.Event) {
  2595. case ETIE_PRESSED_DOWN:
  2596. m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
  2597. translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
  2598. translated.MouseInput.ButtonStates = EMBSM_LEFT;
  2599. m_down_pos = m_pointer;
  2600. break;
  2601. case ETIE_MOVED:
  2602. m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
  2603. translated.MouseInput.Event = EMIE_MOUSE_MOVED;
  2604. translated.MouseInput.ButtonStates = EMBSM_LEFT;
  2605. break;
  2606. case ETIE_LEFT_UP:
  2607. translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
  2608. translated.MouseInput.ButtonStates = 0;
  2609. hovered = root->getElementFromPoint(m_down_pos);
  2610. /* we don't have a valid pointer element use last
  2611. * known pointer pos */
  2612. translated.MouseInput.X = m_pointer.X;
  2613. translated.MouseInput.Y = m_pointer.Y;
  2614. /* reset down pos */
  2615. m_down_pos = v2s32(0,0);
  2616. break;
  2617. default:
  2618. dont_send_event = true;
  2619. //this is not supposed to happen
  2620. errorstream
  2621. << "GUIFormSpecMenu::preprocessEvent unexpected usecase Event="
  2622. << event.TouchInput.Event << std::endl;
  2623. }
  2624. } else if ( (event.TouchInput.touchedCount == 2) &&
  2625. (event.TouchInput.Event == ETIE_PRESSED_DOWN) ) {
  2626. hovered = root->getElementFromPoint(m_down_pos);
  2627. translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
  2628. translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT;
  2629. translated.MouseInput.X = m_pointer.X;
  2630. translated.MouseInput.Y = m_pointer.Y;
  2631. if (hovered) {
  2632. hovered->OnEvent(translated);
  2633. }
  2634. translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
  2635. translated.MouseInput.ButtonStates = EMBSM_LEFT;
  2636. if (hovered) {
  2637. hovered->OnEvent(translated);
  2638. }
  2639. dont_send_event = true;
  2640. }
  2641. /* ignore unhandled 2 touch events ... accidental moving for example */
  2642. else if (event.TouchInput.touchedCount == 2) {
  2643. dont_send_event = true;
  2644. }
  2645. else if (event.TouchInput.touchedCount > 2) {
  2646. errorstream
  2647. << "GUIFormSpecMenu::preprocessEvent to many multitouch events "
  2648. << event.TouchInput.touchedCount << " ignoring them" << std::endl;
  2649. }
  2650. if (dont_send_event) {
  2651. return true;
  2652. }
  2653. /* check if translated event needs to be preprocessed again */
  2654. if (preprocessEvent(translated)) {
  2655. return true;
  2656. }
  2657. if (hovered) {
  2658. grab();
  2659. bool retval = hovered->OnEvent(translated);
  2660. if (event.TouchInput.Event == ETIE_LEFT_UP) {
  2661. /* reset pointer */
  2662. m_pointer = v2s32(0,0);
  2663. }
  2664. drop();
  2665. return retval;
  2666. }
  2667. }
  2668. #endif
  2669. if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
  2670. /* TODO add a check like:
  2671. if (event.JoystickEvent != joystick_we_listen_for)
  2672. return false;
  2673. */
  2674. bool handled = m_joystick->handleEvent(event.JoystickEvent);
  2675. if (handled) {
  2676. if (m_joystick->wasKeyDown(KeyType::ESC)) {
  2677. tryClose();
  2678. } else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
  2679. if (m_allowclose) {
  2680. acceptInput(quit_mode_accept);
  2681. quitMenu();
  2682. }
  2683. }
  2684. }
  2685. return handled;
  2686. }
  2687. return false;
  2688. }
  2689. /******************************************************************************/
  2690. bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event)
  2691. {
  2692. /* The following code is for capturing double-clicks of the mouse button
  2693. * and translating the double-click into an EET_KEY_INPUT_EVENT event
  2694. * -- which closes the form -- under some circumstances.
  2695. *
  2696. * There have been many github issues reporting this as a bug even though it
  2697. * was an intended feature. For this reason, remapping the double-click as
  2698. * an ESC must be explicitly set when creating this class via the
  2699. * /p remap_dbl_click parameter of the constructor.
  2700. */
  2701. if (!m_remap_dbl_click)
  2702. return false;
  2703. if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
  2704. m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos;
  2705. m_doubleclickdetect[0].time = m_doubleclickdetect[1].time;
  2706. m_doubleclickdetect[1].pos = m_pointer;
  2707. m_doubleclickdetect[1].time = porting::getTimeMs();
  2708. }
  2709. else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
  2710. u64 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, porting::getTimeMs());
  2711. if (delta > 400) {
  2712. return false;
  2713. }
  2714. double squaredistance =
  2715. m_doubleclickdetect[0].pos
  2716. .getDistanceFromSQ(m_doubleclickdetect[1].pos);
  2717. if (squaredistance > (30*30)) {
  2718. return false;
  2719. }
  2720. SEvent* translated = new SEvent();
  2721. assert(translated != 0);
  2722. //translate doubleclick to escape
  2723. memset(translated, 0, sizeof(SEvent));
  2724. translated->EventType = irr::EET_KEY_INPUT_EVENT;
  2725. translated->KeyInput.Key = KEY_ESCAPE;
  2726. translated->KeyInput.Control = false;
  2727. translated->KeyInput.Shift = false;
  2728. translated->KeyInput.PressedDown = true;
  2729. translated->KeyInput.Char = 0;
  2730. OnEvent(*translated);
  2731. // no need to send the key up event as we're already deleted
  2732. // and no one else did notice this event
  2733. delete translated;
  2734. return true;
  2735. }
  2736. return false;
  2737. }
  2738. void GUIFormSpecMenu::tryClose()
  2739. {
  2740. if (m_allowclose) {
  2741. doPause = false;
  2742. acceptInput(quit_mode_cancel);
  2743. quitMenu();
  2744. } else {
  2745. m_text_dst->gotText(L"MenuQuit");
  2746. }
  2747. }
  2748. bool GUIFormSpecMenu::OnEvent(const SEvent& event)
  2749. {
  2750. if (event.EventType==EET_KEY_INPUT_EVENT) {
  2751. KeyPress kp(event.KeyInput);
  2752. if (event.KeyInput.PressedDown && (
  2753. (kp == EscapeKey) || (kp == CancelKey) ||
  2754. ((m_client != NULL) && (kp == getKeySetting("keymap_inventory"))))) {
  2755. tryClose();
  2756. return true;
  2757. }
  2758. if (m_client != NULL && event.KeyInput.PressedDown &&
  2759. (kp == getKeySetting("keymap_screenshot"))) {
  2760. m_client->makeScreenshot();
  2761. }
  2762. if (event.KeyInput.PressedDown &&
  2763. (event.KeyInput.Key==KEY_RETURN ||
  2764. event.KeyInput.Key==KEY_UP ||
  2765. event.KeyInput.Key==KEY_DOWN)
  2766. ) {
  2767. switch (event.KeyInput.Key) {
  2768. case KEY_RETURN:
  2769. current_keys_pending.key_enter = true;
  2770. break;
  2771. case KEY_UP:
  2772. current_keys_pending.key_up = true;
  2773. break;
  2774. case KEY_DOWN:
  2775. current_keys_pending.key_down = true;
  2776. break;
  2777. break;
  2778. default:
  2779. //can't happen at all!
  2780. FATAL_ERROR("Reached a source line that can't ever been reached");
  2781. break;
  2782. }
  2783. if (current_keys_pending.key_enter && m_allowclose) {
  2784. acceptInput(quit_mode_accept);
  2785. quitMenu();
  2786. } else {
  2787. acceptInput();
  2788. }
  2789. return true;
  2790. }
  2791. }
  2792. /* Mouse event other than movement, or crossing the border of inventory
  2793. field while holding right mouse button
  2794. */
  2795. if (event.EventType == EET_MOUSE_INPUT_EVENT &&
  2796. (event.MouseInput.Event != EMIE_MOUSE_MOVED ||
  2797. (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
  2798. event.MouseInput.isRightPressed() &&
  2799. getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) {
  2800. // Get selected item and hovered/clicked item (s)
  2801. m_old_tooltip_id = -1;
  2802. updateSelectedItem();
  2803. ItemSpec s = getItemAtPos(m_pointer);
  2804. Inventory *inv_selected = NULL;
  2805. Inventory *inv_s = NULL;
  2806. InventoryList *list_s = NULL;
  2807. if (m_selected_item) {
  2808. inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
  2809. sanity_check(inv_selected);
  2810. sanity_check(inv_selected->getList(m_selected_item->listname) != NULL);
  2811. }
  2812. u32 s_count = 0;
  2813. if (s.isValid())
  2814. do { // breakable
  2815. inv_s = m_invmgr->getInventory(s.inventoryloc);
  2816. if (!inv_s) {
  2817. errorstream << "InventoryMenu: The selected inventory location "
  2818. << "\"" << s.inventoryloc.dump() << "\" doesn't exist"
  2819. << std::endl;
  2820. s.i = -1; // make it invalid again
  2821. break;
  2822. }
  2823. list_s = inv_s->getList(s.listname);
  2824. if (list_s == NULL) {
  2825. verbosestream << "InventoryMenu: The selected inventory list \""
  2826. << s.listname << "\" does not exist" << std::endl;
  2827. s.i = -1; // make it invalid again
  2828. break;
  2829. }
  2830. if ((u32)s.i >= list_s->getSize()) {
  2831. infostream << "InventoryMenu: The selected inventory list \""
  2832. << s.listname << "\" is too small (i=" << s.i << ", size="
  2833. << list_s->getSize() << ")" << std::endl;
  2834. s.i = -1; // make it invalid again
  2835. break;
  2836. }
  2837. s_count = list_s->getItem(s.i).count;
  2838. } while(0);
  2839. bool identical = (m_selected_item != NULL) && s.isValid() &&
  2840. (inv_selected == inv_s) &&
  2841. (m_selected_item->listname == s.listname) &&
  2842. (m_selected_item->i == s.i);
  2843. // buttons: 0 = left, 1 = right, 2 = middle
  2844. // up/down: 0 = down (press), 1 = up (release), 2 = unknown event, -1 movement
  2845. int button = 0;
  2846. int updown = 2;
  2847. if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
  2848. { button = 0; updown = 0; }
  2849. else if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
  2850. { button = 1; updown = 0; }
  2851. else if (event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
  2852. { button = 2; updown = 0; }
  2853. else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
  2854. { button = 0; updown = 1; }
  2855. else if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
  2856. { button = 1; updown = 1; }
  2857. else if (event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP)
  2858. { button = 2; updown = 1; }
  2859. else if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
  2860. { updown = -1;}
  2861. // Set this number to a positive value to generate a move action
  2862. // from m_selected_item to s.
  2863. u32 move_amount = 0;
  2864. // Set this number to a positive value to generate a move action
  2865. // from s to the next inventory ring.
  2866. u32 shift_move_amount = 0;
  2867. // Set this number to a positive value to generate a drop action
  2868. // from m_selected_item.
  2869. u32 drop_amount = 0;
  2870. // Set this number to a positive value to generate a craft action at s.
  2871. u32 craft_amount = 0;
  2872. if (updown == 0) {
  2873. // Some mouse button has been pressed
  2874. //infostream<<"Mouse button "<<button<<" pressed at p=("
  2875. // <<p.X<<","<<p.Y<<")"<<std::endl;
  2876. m_selected_dragging = false;
  2877. if (s.isValid() && s.listname == "craftpreview") {
  2878. // Craft preview has been clicked: craft
  2879. craft_amount = (button == 2 ? 10 : 1);
  2880. } else if (m_selected_item == NULL) {
  2881. if (s_count != 0) {
  2882. // Non-empty stack has been clicked: select or shift-move it
  2883. m_selected_item = new ItemSpec(s);
  2884. u32 count;
  2885. if (button == 1) // right
  2886. count = (s_count + 1) / 2;
  2887. else if (button == 2) // middle
  2888. count = MYMIN(s_count, 10);
  2889. else // left
  2890. count = s_count;
  2891. if (!event.MouseInput.Shift) {
  2892. // no shift: select item
  2893. m_selected_amount = count;
  2894. m_selected_dragging = true;
  2895. m_auto_place = false;
  2896. } else {
  2897. // shift pressed: move item
  2898. if (button != 1)
  2899. shift_move_amount = count;
  2900. else // count of 1 at left click like after drag & drop
  2901. shift_move_amount = 1;
  2902. }
  2903. }
  2904. } else { // m_selected_item != NULL
  2905. assert(m_selected_amount >= 1);
  2906. if (s.isValid()) {
  2907. // Clicked a slot: move
  2908. if (button == 1) // right
  2909. move_amount = 1;
  2910. else if (button == 2) // middle
  2911. move_amount = MYMIN(m_selected_amount, 10);
  2912. else // left
  2913. move_amount = m_selected_amount;
  2914. if (identical) {
  2915. if (move_amount >= m_selected_amount)
  2916. m_selected_amount = 0;
  2917. else
  2918. m_selected_amount -= move_amount;
  2919. move_amount = 0;
  2920. }
  2921. }
  2922. else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) {
  2923. // Clicked outside of the window: drop
  2924. if (button == 1) // right
  2925. drop_amount = 1;
  2926. else if (button == 2) // middle
  2927. drop_amount = MYMIN(m_selected_amount, 10);
  2928. else // left
  2929. drop_amount = m_selected_amount;
  2930. }
  2931. }
  2932. }
  2933. else if (updown == 1) {
  2934. // Some mouse button has been released
  2935. //infostream<<"Mouse button "<<button<<" released at p=("
  2936. // <<p.X<<","<<p.Y<<")"<<std::endl;
  2937. if (m_selected_item != NULL && m_selected_dragging && s.isValid()) {
  2938. if (!identical) {
  2939. // Dragged to different slot: move all selected
  2940. move_amount = m_selected_amount;
  2941. }
  2942. } else if (m_selected_item != NULL && m_selected_dragging &&
  2943. !(getAbsoluteClippingRect().isPointInside(m_pointer))) {
  2944. // Dragged outside of window: drop all selected
  2945. drop_amount = m_selected_amount;
  2946. }
  2947. m_selected_dragging = false;
  2948. // Keep track of whether the mouse button be released
  2949. // One click is drag without dropping. Click + release
  2950. // + click changes to drop item when moved mode
  2951. if (m_selected_item)
  2952. m_auto_place = true;
  2953. } else if (updown == -1) {
  2954. // Mouse has been moved and rmb is down and mouse pointer just
  2955. // entered a new inventory field (checked in the entry-if, this
  2956. // is the only action here that is generated by mouse movement)
  2957. if (m_selected_item != NULL && s.isValid()) {
  2958. // Move 1 item
  2959. // TODO: middle mouse to move 10 items might be handy
  2960. if (m_auto_place) {
  2961. // Only move an item if the destination slot is empty
  2962. // or contains the same item type as what is going to be
  2963. // moved
  2964. InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
  2965. InventoryList *list_to = list_s;
  2966. assert(list_from && list_to);
  2967. ItemStack stack_from = list_from->getItem(m_selected_item->i);
  2968. ItemStack stack_to = list_to->getItem(s.i);
  2969. if (stack_to.empty() || stack_to.name == stack_from.name)
  2970. move_amount = 1;
  2971. }
  2972. }
  2973. }
  2974. // Possibly send inventory action to server
  2975. if (move_amount > 0) {
  2976. // Send IAction::Move
  2977. assert(m_selected_item && m_selected_item->isValid());
  2978. assert(s.isValid());
  2979. assert(inv_selected && inv_s);
  2980. InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
  2981. InventoryList *list_to = list_s;
  2982. assert(list_from && list_to);
  2983. ItemStack stack_from = list_from->getItem(m_selected_item->i);
  2984. ItemStack stack_to = list_to->getItem(s.i);
  2985. // Check how many items can be moved
  2986. move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
  2987. ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
  2988. // If source stack cannot be added to destination stack at all,
  2989. // they are swapped
  2990. if ((leftover.count == stack_from.count) &&
  2991. (leftover.name == stack_from.name)) {
  2992. m_selected_amount = stack_to.count;
  2993. // In case the server doesn't directly swap them but instead
  2994. // moves stack_to somewhere else, set this
  2995. m_selected_content_guess = stack_to;
  2996. m_selected_content_guess_inventory = s.inventoryloc;
  2997. }
  2998. // Source stack goes fully into destination stack
  2999. else if (leftover.empty()) {
  3000. m_selected_amount -= move_amount;
  3001. m_selected_content_guess = ItemStack(); // Clear
  3002. }
  3003. // Source stack goes partly into destination stack
  3004. else {
  3005. move_amount -= leftover.count;
  3006. m_selected_amount -= move_amount;
  3007. m_selected_content_guess = ItemStack(); // Clear
  3008. }
  3009. infostream << "Handing IAction::Move to manager" << std::endl;
  3010. IMoveAction *a = new IMoveAction();
  3011. a->count = move_amount;
  3012. a->from_inv = m_selected_item->inventoryloc;
  3013. a->from_list = m_selected_item->listname;
  3014. a->from_i = m_selected_item->i;
  3015. a->to_inv = s.inventoryloc;
  3016. a->to_list = s.listname;
  3017. a->to_i = s.i;
  3018. m_invmgr->inventoryAction(a);
  3019. } else if (shift_move_amount > 0) {
  3020. u32 mis = m_inventory_rings.size();
  3021. u32 i = 0;
  3022. for (; i < mis; i++) {
  3023. const ListRingSpec &sp = m_inventory_rings[i];
  3024. if (sp.inventoryloc == s.inventoryloc
  3025. && sp.listname == s.listname)
  3026. break;
  3027. }
  3028. do {
  3029. if (i >= mis) // if not found
  3030. break;
  3031. u32 to_inv_ind = (i + 1) % mis;
  3032. const ListRingSpec &to_inv_sp = m_inventory_rings[to_inv_ind];
  3033. InventoryList *list_from = list_s;
  3034. if (!s.isValid())
  3035. break;
  3036. Inventory *inv_to = m_invmgr->getInventory(to_inv_sp.inventoryloc);
  3037. if (!inv_to)
  3038. break;
  3039. InventoryList *list_to = inv_to->getList(to_inv_sp.listname);
  3040. if (!list_to)
  3041. break;
  3042. ItemStack stack_from = list_from->getItem(s.i);
  3043. assert(shift_move_amount <= stack_from.count);
  3044. if (m_client->getProtoVersion() >= 25) {
  3045. infostream << "Handing IAction::Move to manager" << std::endl;
  3046. IMoveAction *a = new IMoveAction();
  3047. a->count = shift_move_amount;
  3048. a->from_inv = s.inventoryloc;
  3049. a->from_list = s.listname;
  3050. a->from_i = s.i;
  3051. a->to_inv = to_inv_sp.inventoryloc;
  3052. a->to_list = to_inv_sp.listname;
  3053. a->move_somewhere = true;
  3054. m_invmgr->inventoryAction(a);
  3055. } else {
  3056. // find a place (or more than one) to add the new item
  3057. u32 ilt_size = list_to->getSize();
  3058. ItemStack leftover;
  3059. for (u32 slot_to = 0; slot_to < ilt_size
  3060. && shift_move_amount > 0; slot_to++) {
  3061. list_to->itemFits(slot_to, stack_from, &leftover);
  3062. if (leftover.count < stack_from.count) {
  3063. infostream << "Handing IAction::Move to manager" << std::endl;
  3064. IMoveAction *a = new IMoveAction();
  3065. a->count = MYMIN(shift_move_amount,
  3066. (u32) (stack_from.count - leftover.count));
  3067. shift_move_amount -= a->count;
  3068. a->from_inv = s.inventoryloc;
  3069. a->from_list = s.listname;
  3070. a->from_i = s.i;
  3071. a->to_inv = to_inv_sp.inventoryloc;
  3072. a->to_list = to_inv_sp.listname;
  3073. a->to_i = slot_to;
  3074. m_invmgr->inventoryAction(a);
  3075. stack_from = leftover;
  3076. }
  3077. }
  3078. }
  3079. } while (0);
  3080. } else if (drop_amount > 0) {
  3081. m_selected_content_guess = ItemStack(); // Clear
  3082. // Send IAction::Drop
  3083. assert(m_selected_item && m_selected_item->isValid());
  3084. assert(inv_selected);
  3085. InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
  3086. assert(list_from);
  3087. ItemStack stack_from = list_from->getItem(m_selected_item->i);
  3088. // Check how many items can be dropped
  3089. drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
  3090. assert(drop_amount > 0 && drop_amount <= m_selected_amount);
  3091. m_selected_amount -= drop_amount;
  3092. infostream << "Handing IAction::Drop to manager" << std::endl;
  3093. IDropAction *a = new IDropAction();
  3094. a->count = drop_amount;
  3095. a->from_inv = m_selected_item->inventoryloc;
  3096. a->from_list = m_selected_item->listname;
  3097. a->from_i = m_selected_item->i;
  3098. m_invmgr->inventoryAction(a);
  3099. } else if (craft_amount > 0) {
  3100. assert(s.isValid());
  3101. // if there are no items selected or the selected item
  3102. // belongs to craftresult list, proceed with crafting
  3103. if (m_selected_item == NULL ||
  3104. !m_selected_item->isValid() || m_selected_item->listname == "craftresult") {
  3105. m_selected_content_guess = ItemStack(); // Clear
  3106. assert(inv_s);
  3107. // Send IACTION_CRAFT
  3108. infostream << "Handing IACTION_CRAFT to manager" << std::endl;
  3109. ICraftAction *a = new ICraftAction();
  3110. a->count = craft_amount;
  3111. a->craft_inv = s.inventoryloc;
  3112. m_invmgr->inventoryAction(a);
  3113. }
  3114. }
  3115. // If m_selected_amount has been decreased to zero, deselect
  3116. if (m_selected_amount == 0) {
  3117. delete m_selected_item;
  3118. m_selected_item = NULL;
  3119. m_selected_amount = 0;
  3120. m_selected_dragging = false;
  3121. m_selected_content_guess = ItemStack();
  3122. }
  3123. m_old_pointer = m_pointer;
  3124. }
  3125. if (event.EventType == EET_GUI_EVENT) {
  3126. if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
  3127. && isVisible()) {
  3128. // find the element that was clicked
  3129. for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
  3130. if ((s.ftype == f_TabHeader) &&
  3131. (s.fid == event.GUIEvent.Caller->getID())) {
  3132. s.send = true;
  3133. acceptInput();
  3134. s.send = false;
  3135. return true;
  3136. }
  3137. }
  3138. }
  3139. if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
  3140. && isVisible()) {
  3141. if (!canTakeFocus(event.GUIEvent.Element)) {
  3142. infostream<<"GUIFormSpecMenu: Not allowing focus change."
  3143. <<std::endl;
  3144. // Returning true disables focus change
  3145. return true;
  3146. }
  3147. }
  3148. if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
  3149. (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
  3150. (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
  3151. (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
  3152. unsigned int btn_id = event.GUIEvent.Caller->getID();
  3153. if (btn_id == 257) {
  3154. if (m_allowclose) {
  3155. acceptInput(quit_mode_accept);
  3156. quitMenu();
  3157. } else {
  3158. acceptInput();
  3159. m_text_dst->gotText(L"ExitButton");
  3160. }
  3161. // quitMenu deallocates menu
  3162. return true;
  3163. }
  3164. // find the element that was clicked
  3165. for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
  3166. // if its a button, set the send field so
  3167. // lua knows which button was pressed
  3168. if ((s.ftype == f_Button || s.ftype == f_CheckBox) &&
  3169. s.fid == event.GUIEvent.Caller->getID()) {
  3170. s.send = true;
  3171. if (s.is_exit) {
  3172. if (m_allowclose) {
  3173. acceptInput(quit_mode_accept);
  3174. quitMenu();
  3175. } else {
  3176. m_text_dst->gotText(L"ExitButton");
  3177. }
  3178. return true;
  3179. }
  3180. acceptInput(quit_mode_no);
  3181. s.send = false;
  3182. return true;
  3183. } else if ((s.ftype == f_DropDown) &&
  3184. (s.fid == event.GUIEvent.Caller->getID())) {
  3185. // only send the changed dropdown
  3186. for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
  3187. if (s2.ftype == f_DropDown) {
  3188. s2.send = false;
  3189. }
  3190. }
  3191. s.send = true;
  3192. acceptInput(quit_mode_no);
  3193. // revert configuration to make sure dropdowns are sent on
  3194. // regular button click
  3195. for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
  3196. if (s2.ftype == f_DropDown) {
  3197. s2.send = true;
  3198. }
  3199. }
  3200. return true;
  3201. } else if ((s.ftype == f_ScrollBar) &&
  3202. (s.fid == event.GUIEvent.Caller->getID())) {
  3203. s.fdefault = L"Changed";
  3204. acceptInput(quit_mode_no);
  3205. s.fdefault = L"";
  3206. }
  3207. }
  3208. }
  3209. if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
  3210. if (event.GUIEvent.Caller->getID() > 257) {
  3211. bool close_on_enter = true;
  3212. for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
  3213. if (s.ftype == f_Unknown &&
  3214. s.fid == event.GUIEvent.Caller->getID()) {
  3215. current_field_enter_pending = s.fname;
  3216. std::unordered_map<std::string, bool>::const_iterator it =
  3217. field_close_on_enter.find(s.fname);
  3218. if (it != field_close_on_enter.end())
  3219. close_on_enter = (*it).second;
  3220. break;
  3221. }
  3222. }
  3223. if (m_allowclose && close_on_enter) {
  3224. current_keys_pending.key_enter = true;
  3225. acceptInput(quit_mode_accept);
  3226. quitMenu();
  3227. } else {
  3228. current_keys_pending.key_enter = true;
  3229. acceptInput();
  3230. }
  3231. // quitMenu deallocates menu
  3232. return true;
  3233. }
  3234. }
  3235. if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
  3236. int current_id = event.GUIEvent.Caller->getID();
  3237. if (current_id > 257) {
  3238. // find the element that was clicked
  3239. for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
  3240. // if it's a table, set the send field
  3241. // so lua knows which table was changed
  3242. if ((s.ftype == f_Table) && (s.fid == current_id)) {
  3243. s.send = true;
  3244. acceptInput();
  3245. s.send=false;
  3246. }
  3247. }
  3248. return true;
  3249. }
  3250. }
  3251. }
  3252. return Parent ? Parent->OnEvent(event) : false;
  3253. }
  3254. /**
  3255. * get name of element by element id
  3256. * @param id of element
  3257. * @return name string or empty string
  3258. */
  3259. std::string GUIFormSpecMenu::getNameByID(s32 id)
  3260. {
  3261. for (FieldSpec &spec : m_fields) {
  3262. if (spec.fid == id) {
  3263. return spec.fname;
  3264. }
  3265. }
  3266. return "";
  3267. }
  3268. /**
  3269. * get label of element by id
  3270. * @param id of element
  3271. * @return label string or empty string
  3272. */
  3273. std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
  3274. {
  3275. for (FieldSpec &spec : m_fields) {
  3276. if (spec.fid == id) {
  3277. return spec.flabel;
  3278. }
  3279. }
  3280. return L"";
  3281. }