guiFormSpecMenu.cpp 119 KB

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