guiFormSpecMenu.cpp 142 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161
  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 <cmath>
  18. #include <algorithm>
  19. #include <iterator>
  20. #include <limits>
  21. #include <sstream>
  22. #include "guiFormSpecMenu.h"
  23. #include "constants.h"
  24. #include "gamedef.h"
  25. #include "client/keycode.h"
  26. #include "util/strfnd.h"
  27. #include <IGUIButton.h>
  28. #include <IGUICheckBox.h>
  29. #include <IGUIComboBox.h>
  30. #include <IGUIEditBox.h>
  31. #include <IGUIFont.h>
  32. #include <IGUITabControl.h>
  33. #include <IGUIImage.h>
  34. #include <IAnimatedMeshSceneNode.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 "client/sound.h"
  49. #include "util/hex.h"
  50. #include "util/numeric.h"
  51. #include "util/string.h" // for parseColorString()
  52. #include "irrlicht_changes/static_text.h"
  53. #include "client/guiscalingfilter.h"
  54. #include "guiAnimatedImage.h"
  55. #include "guiBackgroundImage.h"
  56. #include "guiBox.h"
  57. #include "guiButton.h"
  58. #include "guiButtonImage.h"
  59. #include "guiButtonItemImage.h"
  60. #include "guiEditBoxWithScrollbar.h"
  61. #include "guiInventoryList.h"
  62. #include "guiItemImage.h"
  63. #include "guiScrollContainer.h"
  64. #include "guiHyperText.h"
  65. #include "guiScene.h"
  66. #define MY_CHECKPOS(a,b) \
  67. if (v_pos.size() != 2) { \
  68. errorstream<< "Invalid pos for element " << a << " specified: \"" \
  69. << parts[b] << "\"" << std::endl; \
  70. return; \
  71. }
  72. #define MY_CHECKGEOM(a,b) \
  73. if (v_geom.size() != 2) { \
  74. errorstream<< "Invalid geometry for element " << a << \
  75. " specified: \"" << parts[b] << "\"" << std::endl; \
  76. return; \
  77. }
  78. #define MY_CHECKCLIENT(a) \
  79. if (!m_client) { \
  80. errorstream << "Attempted to use element " << a << " with m_client == nullptr." << std::endl; \
  81. return; \
  82. }
  83. /*
  84. GUIFormSpecMenu
  85. */
  86. static unsigned int font_line_height(gui::IGUIFont *font)
  87. {
  88. return font->getDimension(L"Ay").Height + font->getKerningHeight();
  89. }
  90. inline u32 clamp_u8(s32 value)
  91. {
  92. return (u32) MYMIN(MYMAX(value, 0), 255);
  93. }
  94. GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
  95. gui::IGUIElement *parent, s32 id, IMenuManager *menumgr,
  96. Client *client, gui::IGUIEnvironment *guienv, ISimpleTextureSource *tsrc,
  97. ISoundManager *sound_manager, IFormSource *fsrc, TextDest *tdst,
  98. const std::string &formspecPrepend, bool remap_dbl_click):
  99. GUIModalMenu(guienv, parent, id, menumgr, remap_dbl_click),
  100. m_invmgr(client),
  101. m_tsrc(tsrc),
  102. m_sound_manager(sound_manager),
  103. m_client(client),
  104. m_formspec_prepend(formspecPrepend),
  105. m_form_src(fsrc),
  106. m_text_dst(tdst),
  107. m_joystick(joystick)
  108. {
  109. current_keys_pending.key_down = false;
  110. current_keys_pending.key_up = false;
  111. current_keys_pending.key_enter = false;
  112. current_keys_pending.key_escape = false;
  113. m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
  114. m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname");
  115. }
  116. GUIFormSpecMenu::~GUIFormSpecMenu()
  117. {
  118. removeAll();
  119. delete m_selected_item;
  120. delete m_form_src;
  121. delete m_text_dst;
  122. }
  123. void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client,
  124. gui::IGUIEnvironment *guienv, JoystickController *joystick, IFormSource *fs_src,
  125. TextDest *txt_dest, const std::string &formspecPrepend, ISoundManager *sound_manager)
  126. {
  127. if (cur_formspec && cur_formspec->getReferenceCount() == 1) {
  128. /*
  129. Why reference count == 1? Reason:
  130. 1 on creation (see "drop()" remark below)
  131. +1 for being a guiroot child
  132. +1 when focused (CGUIEnvironment::setFocus)
  133. Hence re-create the formspec when it's existing without any parent.
  134. */
  135. cur_formspec->drop();
  136. cur_formspec = nullptr;
  137. }
  138. if (cur_formspec == nullptr) {
  139. cur_formspec = new GUIFormSpecMenu(joystick, guiroot, -1, &g_menumgr,
  140. client, guienv, client->getTextureSource(), sound_manager, fs_src,
  141. txt_dest, formspecPrepend);
  142. /*
  143. Caution: do not call (*cur_formspec)->drop() here --
  144. the reference might outlive the menu, so we will
  145. periodically check if *cur_formspec is the only
  146. remaining reference (i.e. the menu was removed)
  147. and delete it in that case.
  148. */
  149. } else {
  150. cur_formspec->setFormspecPrepend(formspecPrepend);
  151. cur_formspec->setFormSource(fs_src);
  152. cur_formspec->setTextDest(txt_dest);
  153. }
  154. cur_formspec->doPause = false;
  155. }
  156. void GUIFormSpecMenu::removeTooltip()
  157. {
  158. if (m_tooltip_element) {
  159. m_tooltip_element->remove();
  160. m_tooltip_element->drop();
  161. m_tooltip_element = nullptr;
  162. }
  163. }
  164. void GUIFormSpecMenu::setInitialFocus()
  165. {
  166. // Set initial focus according to following order of precedence:
  167. // 1. first empty editbox
  168. // 2. first editbox
  169. // 3. first table
  170. // 4. last button
  171. // 5. first focusable (not statictext, not tabheader)
  172. // 6. first child element
  173. const auto& children = getChildren();
  174. // 1. first empty editbox
  175. for (gui::IGUIElement *it : children) {
  176. if (it->getType() == gui::EGUIET_EDIT_BOX
  177. && it->getText()[0] == 0) {
  178. Environment->setFocus(it);
  179. return;
  180. }
  181. }
  182. // 2. first editbox
  183. for (gui::IGUIElement *it : children) {
  184. if (it->getType() == gui::EGUIET_EDIT_BOX) {
  185. Environment->setFocus(it);
  186. return;
  187. }
  188. }
  189. // 3. first table
  190. for (gui::IGUIElement *it : children) {
  191. if (it->getTypeName() == std::string("GUITable")) {
  192. Environment->setFocus(it);
  193. return;
  194. }
  195. }
  196. // 4. last button
  197. for (auto it = children.rbegin(); it != children.rend(); ++it) {
  198. if ((*it)->getType() == gui::EGUIET_BUTTON) {
  199. Environment->setFocus(*it);
  200. return;
  201. }
  202. }
  203. // 5. first focusable (not statictext, not tabheader)
  204. for (gui::IGUIElement *it : children) {
  205. if (it->getType() != gui::EGUIET_STATIC_TEXT &&
  206. it->getType() != gui::EGUIET_TAB_CONTROL) {
  207. Environment->setFocus(it);
  208. return;
  209. }
  210. }
  211. // 6. first child element
  212. if (children.empty())
  213. Environment->setFocus(this);
  214. else
  215. Environment->setFocus(children.front());
  216. }
  217. GUITable* GUIFormSpecMenu::getTable(const std::string &tablename)
  218. {
  219. for (auto &table : m_tables) {
  220. if (tablename == table.first.fname)
  221. return table.second;
  222. }
  223. return 0;
  224. }
  225. std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string &name)
  226. {
  227. for (auto &dropdown : m_dropdowns) {
  228. if (name == dropdown.first.fname)
  229. return &dropdown.second;
  230. }
  231. return NULL;
  232. }
  233. // This will only return a meaningful value if called after drawMenu().
  234. core::rect<s32> GUIFormSpecMenu::getAbsoluteRect()
  235. {
  236. core::rect<s32> rect = AbsoluteRect;
  237. rect.UpperLeftCorner.Y += m_tabheader_upper_edge;
  238. return rect;
  239. }
  240. v2s32 GUIFormSpecMenu::getElementBasePos(const std::vector<std::string> *v_pos)
  241. {
  242. v2f32 pos_f = v2f32(padding.X, padding.Y) + pos_offset * spacing;
  243. if (v_pos) {
  244. pos_f.X += stof((*v_pos)[0]) * spacing.X;
  245. pos_f.Y += stof((*v_pos)[1]) * spacing.Y;
  246. }
  247. return v2s32(pos_f.X, pos_f.Y);
  248. }
  249. v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(const std::vector<std::string> &v_pos)
  250. {
  251. return v2s32((stof(v_pos[0]) + pos_offset.X) * imgsize.X,
  252. (stof(v_pos[1]) + pos_offset.Y) * imgsize.Y);
  253. }
  254. v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector<std::string> &v_geom)
  255. {
  256. return v2s32(stof(v_geom[0]) * imgsize.X, stof(v_geom[1]) * imgsize.Y);
  257. }
  258. bool GUIFormSpecMenu::precheckElement(const std::string &name, const std::string &element,
  259. size_t args_min, size_t args_max, std::vector<std::string> &parts)
  260. {
  261. parts = split(element, ';');
  262. if (parts.size() >= args_min && (parts.size() <= args_max || m_formspec_version > FORMSPEC_API_VERSION))
  263. return true;
  264. errorstream << "Invalid " << name << " element(" << parts.size() << "): '" << element << "'" << std::endl;
  265. return false;
  266. }
  267. void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
  268. {
  269. // Note: do not use precheckElement due to "," separator.
  270. std::vector<std::string> parts = split(element,',');
  271. if (((parts.size() == 2) || parts.size() == 3) ||
  272. ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
  273. {
  274. if (parts[1].find(';') != std::string::npos)
  275. parts[1] = parts[1].substr(0,parts[1].find(';'));
  276. data->invsize.X = MYMAX(0, stof(parts[0]));
  277. data->invsize.Y = MYMAX(0, stof(parts[1]));
  278. lockSize(false);
  279. #ifndef HAVE_TOUCHSCREENGUI
  280. if (parts.size() == 3) {
  281. if (parts[2] == "true") {
  282. lockSize(true,v2u32(800,600));
  283. }
  284. }
  285. #endif
  286. data->explicit_size = true;
  287. return;
  288. }
  289. errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl;
  290. }
  291. void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &element)
  292. {
  293. std::vector<std::string> parts = split(element, ',');
  294. if (parts.size() >= 2) {
  295. if (parts[1].find(';') != std::string::npos)
  296. parts[1] = parts[1].substr(0, parts[1].find(';'));
  297. container_stack.push(pos_offset);
  298. pos_offset.X += stof(parts[0]);
  299. pos_offset.Y += stof(parts[1]);
  300. return;
  301. }
  302. errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'" << std::endl;
  303. }
  304. void GUIFormSpecMenu::parseContainerEnd(parserData* data)
  305. {
  306. if (container_stack.empty()) {
  307. errorstream<< "Invalid container end element, no matching container start element" << std::endl;
  308. } else {
  309. pos_offset = container_stack.top();
  310. container_stack.pop();
  311. }
  312. }
  313. void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &element)
  314. {
  315. std::vector<std::string> parts;
  316. if (!precheckElement("scroll_container start", element, 4, 5, parts))
  317. return;
  318. std::vector<std::string> v_pos = split(parts[0], ',');
  319. std::vector<std::string> v_geom = split(parts[1], ',');
  320. std::string scrollbar_name = parts[2];
  321. std::string orientation = parts[3];
  322. f32 scroll_factor = 0.1f;
  323. if (parts.size() >= 5 && !parts[4].empty())
  324. scroll_factor = stof(parts[4]);
  325. MY_CHECKPOS("scroll_container", 0);
  326. MY_CHECKGEOM("scroll_container", 1);
  327. v2s32 pos = getRealCoordinateBasePos(v_pos);
  328. v2s32 geom = getRealCoordinateGeometry(v_geom);
  329. if (orientation == "vertical")
  330. scroll_factor *= -imgsize.Y;
  331. else if (orientation == "horizontal")
  332. scroll_factor *= -imgsize.X;
  333. else
  334. warningstream << "GUIFormSpecMenu::parseScrollContainer(): "
  335. << "Invalid scroll_container orientation: " << orientation
  336. << std::endl;
  337. // old parent (at first: this)
  338. // ^ is parent of clipper
  339. // ^ is parent of mover
  340. // ^ is parent of other elements
  341. // make clipper
  342. core::rect<s32> rect_clipper = core::rect<s32>(pos, pos + geom);
  343. gui::IGUIElement *clipper = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
  344. data->current_parent, 0, rect_clipper);
  345. // make mover
  346. FieldSpec spec_mover(
  347. "",
  348. L"",
  349. L"",
  350. 258 + m_fields.size()
  351. );
  352. core::rect<s32> rect_mover = core::rect<s32>(0, 0, geom.X, geom.Y);
  353. GUIScrollContainer *mover = new GUIScrollContainer(Environment,
  354. clipper, spec_mover.fid, rect_mover, orientation, scroll_factor);
  355. data->current_parent = mover;
  356. m_scroll_containers.emplace_back(scrollbar_name, mover);
  357. m_fields.push_back(spec_mover);
  358. clipper->drop();
  359. // remove interferring offset of normal containers
  360. container_stack.push(pos_offset);
  361. pos_offset.X = 0.0f;
  362. pos_offset.Y = 0.0f;
  363. }
  364. void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data)
  365. {
  366. if (data->current_parent == this || data->current_parent->getParent() == this ||
  367. container_stack.empty()) {
  368. errorstream << "Invalid scroll_container end element, "
  369. << "no matching scroll_container start element" << std::endl;
  370. return;
  371. }
  372. if (pos_offset.getLengthSQ() != 0.0f) {
  373. // pos_offset is only set by containers and scroll_containers.
  374. // scroll_containers always set it to 0,0 which means that if it is
  375. // not 0,0, it is a normal container that was opened last, not a
  376. // scroll_container
  377. errorstream << "Invalid scroll_container end element, "
  378. << "an inner container was left open" << std::endl;
  379. return;
  380. }
  381. data->current_parent = data->current_parent->getParent()->getParent();
  382. pos_offset = container_stack.top();
  383. container_stack.pop();
  384. }
  385. void GUIFormSpecMenu::parseList(parserData *data, const std::string &element)
  386. {
  387. MY_CHECKCLIENT("list");
  388. std::vector<std::string> parts;
  389. if (!precheckElement("list", element, 4, 5, parts))
  390. return;
  391. std::string location = parts[0];
  392. std::string listname = parts[1];
  393. std::vector<std::string> v_pos = split(parts[2],',');
  394. std::vector<std::string> v_geom = split(parts[3],',');
  395. std::string startindex;
  396. if (parts.size() == 5)
  397. startindex = parts[4];
  398. MY_CHECKPOS("list",2);
  399. MY_CHECKGEOM("list",3);
  400. InventoryLocation loc;
  401. if (location == "context" || location == "current_name")
  402. loc = m_current_inventory_location;
  403. else
  404. loc.deSerialize(location);
  405. v2s32 geom;
  406. geom.X = stoi(v_geom[0]);
  407. geom.Y = stoi(v_geom[1]);
  408. s32 start_i = 0;
  409. if (!startindex.empty())
  410. start_i = stoi(startindex);
  411. if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
  412. errorstream << "Invalid list element: '" << element << "'" << std::endl;
  413. return;
  414. }
  415. if (!data->explicit_size)
  416. warningstream << "invalid use of list without a size[] element" << std::endl;
  417. FieldSpec spec(
  418. "",
  419. L"",
  420. L"",
  421. 258 + m_fields.size(),
  422. 3
  423. );
  424. auto style = getDefaultStyleForElement("list", spec.fname);
  425. v2f32 slot_scale = style.getVector2f(StyleSpec::SIZE, v2f32(0, 0));
  426. v2f32 slot_size(
  427. slot_scale.X <= 0 ? imgsize.X : std::max<f32>(slot_scale.X * imgsize.X, 1),
  428. slot_scale.Y <= 0 ? imgsize.Y : std::max<f32>(slot_scale.Y * imgsize.Y, 1)
  429. );
  430. v2f32 slot_spacing = style.getVector2f(StyleSpec::SPACING, v2f32(-1, -1));
  431. v2f32 default_spacing = data->real_coordinates ?
  432. v2f32(imgsize.X * 0.25f, imgsize.Y * 0.25f) :
  433. v2f32(spacing.X - imgsize.X, spacing.Y - imgsize.Y);
  434. slot_spacing.X = slot_spacing.X < 0 ? default_spacing.X :
  435. imgsize.X * slot_spacing.X;
  436. slot_spacing.Y = slot_spacing.Y < 0 ? default_spacing.Y :
  437. imgsize.Y * slot_spacing.Y;
  438. slot_spacing += slot_size;
  439. v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos) :
  440. getElementBasePos(&v_pos);
  441. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
  442. pos.X + (geom.X - 1) * slot_spacing.X + slot_size.X,
  443. pos.Y + (geom.Y - 1) * slot_spacing.Y + slot_size.Y);
  444. GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent,
  445. spec.fid, rect, m_invmgr, loc, listname, geom, start_i,
  446. v2s32(slot_size.X, slot_size.Y), slot_spacing, this,
  447. data->inventorylist_options, m_font);
  448. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  449. m_inventorylists.push_back(e);
  450. m_fields.push_back(spec);
  451. }
  452. void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element)
  453. {
  454. MY_CHECKCLIENT("listring");
  455. std::vector<std::string> parts = split(element, ';');
  456. if (parts.size() == 2) {
  457. std::string location = parts[0];
  458. std::string listname = parts[1];
  459. InventoryLocation loc;
  460. if (location == "context" || location == "current_name")
  461. loc = m_current_inventory_location;
  462. else
  463. loc.deSerialize(location);
  464. m_inventory_rings.emplace_back(loc, listname);
  465. return;
  466. }
  467. if (element.empty() && m_inventorylists.size() > 1) {
  468. size_t siz = m_inventorylists.size();
  469. // insert the last two inv list elements into the list ring
  470. const GUIInventoryList *spa = m_inventorylists[siz - 2];
  471. const GUIInventoryList *spb = m_inventorylists[siz - 1];
  472. m_inventory_rings.emplace_back(spa->getInventoryloc(), spa->getListname());
  473. m_inventory_rings.emplace_back(spb->getInventoryloc(), spb->getListname());
  474. return;
  475. }
  476. errorstream<< "Invalid list ring element(" << parts.size() << ", "
  477. << m_inventorylists.size() << "): '" << element << "'" << std::endl;
  478. }
  479. void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element)
  480. {
  481. std::vector<std::string> parts;
  482. if (!precheckElement("checkbox", element, 3, 4, parts))
  483. return;
  484. std::vector<std::string> v_pos = split(parts[0],',');
  485. std::string name = parts[1];
  486. std::string label = parts[2];
  487. std::string selected;
  488. if (parts.size() >= 4)
  489. selected = parts[3];
  490. MY_CHECKPOS("checkbox",0);
  491. bool fselected = false;
  492. if (selected == "true")
  493. fselected = true;
  494. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  495. const core::dimension2d<u32> label_size = m_font->getDimension(wlabel.c_str());
  496. s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH);
  497. s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2;
  498. v2s32 pos;
  499. core::rect<s32> rect;
  500. if (data->real_coordinates) {
  501. pos = getRealCoordinateBasePos(v_pos);
  502. rect = core::rect<s32>(
  503. pos.X,
  504. pos.Y - y_center,
  505. pos.X + label_size.Width + cb_size + 7,
  506. pos.Y + y_center
  507. );
  508. } else {
  509. pos = getElementBasePos(&v_pos);
  510. rect = core::rect<s32>(
  511. pos.X,
  512. pos.Y + imgsize.Y / 2 - y_center,
  513. pos.X + label_size.Width + cb_size + 7,
  514. pos.Y + imgsize.Y / 2 + y_center
  515. );
  516. }
  517. FieldSpec spec(
  518. name,
  519. wlabel, //Needed for displaying text on MSVC
  520. wlabel,
  521. 258+m_fields.size()
  522. );
  523. spec.ftype = f_CheckBox;
  524. gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect,
  525. data->current_parent, spec.fid, spec.flabel.c_str());
  526. auto style = getDefaultStyleForElement("checkbox", name);
  527. spec.sound = style.get(StyleSpec::Property::SOUND, "");
  528. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  529. if (spec.fname == m_focused_element) {
  530. Environment->setFocus(e);
  531. }
  532. e->grab();
  533. m_checkboxes.emplace_back(spec, e);
  534. m_fields.push_back(spec);
  535. }
  536. void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element)
  537. {
  538. std::vector<std::string> parts;
  539. if (!precheckElement("scrollbar", element, 5, 5, parts))
  540. return;
  541. std::vector<std::string> v_pos = split(parts[0],',');
  542. std::vector<std::string> v_geom = split(parts[1],',');
  543. std::string name = parts[3];
  544. std::string value = parts[4];
  545. MY_CHECKPOS("scrollbar",0);
  546. MY_CHECKGEOM("scrollbar",1);
  547. v2s32 pos;
  548. v2s32 dim;
  549. if (data->real_coordinates) {
  550. pos = getRealCoordinateBasePos(v_pos);
  551. dim = getRealCoordinateGeometry(v_geom);
  552. } else {
  553. pos = getElementBasePos(&v_pos);
  554. dim.X = stof(v_geom[0]) * spacing.X;
  555. dim.Y = stof(v_geom[1]) * spacing.Y;
  556. }
  557. core::rect<s32> rect =
  558. core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
  559. FieldSpec spec(
  560. name,
  561. L"",
  562. L"",
  563. 258+m_fields.size()
  564. );
  565. bool is_horizontal = true;
  566. if (parts[2] == "vertical")
  567. is_horizontal = false;
  568. spec.ftype = f_ScrollBar;
  569. spec.send = true;
  570. GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent,
  571. spec.fid, rect, is_horizontal, true, m_tsrc);
  572. auto style = getDefaultStyleForElement("scrollbar", name);
  573. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  574. e->setArrowsVisible(data->scrollbar_options.arrow_visiblity);
  575. s32 max = data->scrollbar_options.max;
  576. s32 min = data->scrollbar_options.min;
  577. e->setMax(max);
  578. e->setMin(min);
  579. e->setPos(stoi(value));
  580. e->setSmallStep(data->scrollbar_options.small_step);
  581. e->setLargeStep(data->scrollbar_options.large_step);
  582. s32 scrollbar_size = is_horizontal ? dim.X : dim.Y;
  583. e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size);
  584. if (spec.fname == m_focused_element) {
  585. Environment->setFocus(e);
  586. }
  587. m_scrollbars.emplace_back(spec,e);
  588. m_fields.push_back(spec);
  589. }
  590. void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string &element)
  591. {
  592. std::vector<std::string> parts = split(element, ';');
  593. if (parts.size() == 0) {
  594. warningstream << "Invalid scrollbaroptions element(" << parts.size() << "): '" <<
  595. element << "'" << std::endl;
  596. return;
  597. }
  598. for (const std::string &i : parts) {
  599. std::vector<std::string> options = split(i, '=');
  600. if (options.size() != 2) {
  601. warningstream << "Invalid scrollbaroptions option syntax: '" <<
  602. element << "'" << std::endl;
  603. continue; // Go to next option
  604. }
  605. if (options[0] == "max") {
  606. data->scrollbar_options.max = stoi(options[1]);
  607. continue;
  608. } else if (options[0] == "min") {
  609. data->scrollbar_options.min = stoi(options[1]);
  610. continue;
  611. } else if (options[0] == "smallstep") {
  612. int value = stoi(options[1]);
  613. data->scrollbar_options.small_step = value < 0 ? 10 : value;
  614. continue;
  615. } else if (options[0] == "largestep") {
  616. int value = stoi(options[1]);
  617. data->scrollbar_options.large_step = value < 0 ? 100 : value;
  618. continue;
  619. } else if (options[0] == "thumbsize") {
  620. int value = stoi(options[1]);
  621. data->scrollbar_options.thumb_size = value <= 0 ? 1 : value;
  622. continue;
  623. } else if (options[0] == "arrows") {
  624. auto value = trim(options[1]);
  625. if (value == "hide")
  626. data->scrollbar_options.arrow_visiblity = GUIScrollBar::HIDE;
  627. else if (value == "show")
  628. data->scrollbar_options.arrow_visiblity = GUIScrollBar::SHOW;
  629. else // Auto hide/show
  630. data->scrollbar_options.arrow_visiblity = GUIScrollBar::DEFAULT;
  631. continue;
  632. }
  633. warningstream << "Invalid scrollbaroptions option(" << options[0] <<
  634. "): '" << element << "'" << std::endl;
  635. }
  636. }
  637. void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
  638. {
  639. std::vector<std::string> parts;
  640. if (!precheckElement("image", element, 2, 4, parts))
  641. return;
  642. size_t offset = parts.size() >= 3;
  643. std::vector<std::string> v_pos = split(parts[0],',');
  644. MY_CHECKPOS("image", 0);
  645. std::vector<std::string> v_geom;
  646. if (parts.size() >= 3) {
  647. v_geom = split(parts[1],',');
  648. MY_CHECKGEOM("image", 1);
  649. }
  650. std::string name = unescape_string(parts[1 + offset]);
  651. video::ITexture *texture = m_tsrc->getTexture(name);
  652. v2s32 pos;
  653. v2s32 geom;
  654. if (parts.size() < 3) {
  655. if (texture != nullptr) {
  656. core::dimension2du dim = texture->getOriginalSize();
  657. geom.X = dim.Width;
  658. geom.Y = dim.Height;
  659. } else {
  660. geom = v2s32(0);
  661. }
  662. }
  663. if (data->real_coordinates) {
  664. pos = getRealCoordinateBasePos(v_pos);
  665. if (parts.size() >= 3)
  666. geom = getRealCoordinateGeometry(v_geom);
  667. } else {
  668. pos = getElementBasePos(&v_pos);
  669. if (parts.size() >= 3) {
  670. geom.X = stof(v_geom[0]) * (float)imgsize.X;
  671. geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
  672. }
  673. }
  674. if (!data->explicit_size)
  675. warningstream << "Invalid use of image without a size[] element" << std::endl;
  676. FieldSpec spec(
  677. name,
  678. L"",
  679. L"",
  680. 258 + m_fields.size(),
  681. 1
  682. );
  683. core::rect<s32> rect = core::rect<s32>(pos, pos + geom);
  684. core::rect<s32> middle;
  685. if (parts.size() >= 4)
  686. parseMiddleRect(parts[3], &middle);
  687. // Temporary fix for issue #12581 in 5.6.0.
  688. // Use legacy image when not rendering 9-slice image because GUIAnimatedImage
  689. // uses NNAA filter which causes visual artifacts when image uses alpha blending.
  690. gui::IGUIElement *e;
  691. if (middle.getArea() > 0) {
  692. GUIAnimatedImage *image = new GUIAnimatedImage(Environment, data->current_parent,
  693. spec.fid, rect);
  694. image->setTexture(texture);
  695. image->setMiddleRect(middle);
  696. e = image;
  697. }
  698. else {
  699. gui::IGUIImage *image = Environment->addImage(rect, data->current_parent, spec.fid, nullptr, true);
  700. image->setImage(texture);
  701. image->setScaleImage(true);
  702. image->grab(); // compensate for drop in addImage
  703. e = image;
  704. }
  705. auto style = getDefaultStyleForElement("image", spec.fname);
  706. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
  707. // Animated images should let events through
  708. m_clickthrough_elements.push_back(e);
  709. m_fields.push_back(spec);
  710. }
  711. void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element)
  712. {
  713. std::vector<std::string> parts;
  714. if (!precheckElement("animated_image", element, 6, 8, parts))
  715. return;
  716. std::vector<std::string> v_pos = split(parts[0], ',');
  717. std::vector<std::string> v_geom = split(parts[1], ',');
  718. std::string name = parts[2];
  719. std::string texture_name = unescape_string(parts[3]);
  720. s32 frame_count = stoi(parts[4]);
  721. s32 frame_duration = stoi(parts[5]);
  722. MY_CHECKPOS("animated_image", 0);
  723. MY_CHECKGEOM("animated_image", 1);
  724. v2s32 pos;
  725. v2s32 geom;
  726. if (data->real_coordinates) {
  727. pos = getRealCoordinateBasePos(v_pos);
  728. geom = getRealCoordinateGeometry(v_geom);
  729. } else {
  730. pos = getElementBasePos(&v_pos);
  731. geom.X = stof(v_geom[0]) * (float)imgsize.X;
  732. geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
  733. }
  734. if (!data->explicit_size)
  735. warningstream << "Invalid use of animated_image without a size[] element"
  736. << std::endl;
  737. FieldSpec spec(
  738. name,
  739. L"",
  740. L"",
  741. 258 + m_fields.size()
  742. );
  743. spec.ftype = f_AnimatedImage;
  744. spec.send = true;
  745. core::rect<s32> rect = core::rect<s32>(pos, pos + geom);
  746. core::rect<s32> middle;
  747. if (parts.size() >= 8)
  748. parseMiddleRect(parts[7], &middle);
  749. GUIAnimatedImage *e = new GUIAnimatedImage(Environment, data->current_parent,
  750. spec.fid, rect);
  751. e->setTexture(m_tsrc->getTexture(texture_name));
  752. e->setMiddleRect(middle);
  753. e->setFrameDuration(frame_duration);
  754. e->setFrameCount(frame_count);
  755. if (parts.size() >= 7)
  756. e->setFrameIndex(stoi(parts[6]) - 1);
  757. auto style = getDefaultStyleForElement("animated_image", spec.fname, "image");
  758. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  759. // Animated images should let events through
  760. m_clickthrough_elements.push_back(e);
  761. m_fields.push_back(spec);
  762. }
  763. void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element)
  764. {
  765. std::vector<std::string> parts;
  766. if (!precheckElement("item_image", element, 3, 3, parts))
  767. return;
  768. std::vector<std::string> v_pos = split(parts[0],',');
  769. std::vector<std::string> v_geom = split(parts[1],',');
  770. std::string name = parts[2];
  771. MY_CHECKPOS("item_image",0);
  772. MY_CHECKGEOM("item_image",1);
  773. v2s32 pos;
  774. v2s32 geom;
  775. if (data->real_coordinates) {
  776. pos = getRealCoordinateBasePos(v_pos);
  777. geom = getRealCoordinateGeometry(v_geom);
  778. } else {
  779. pos = getElementBasePos(&v_pos);
  780. geom.X = stof(v_geom[0]) * (float)imgsize.X;
  781. geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
  782. }
  783. if(!data->explicit_size)
  784. warningstream<<"invalid use of item_image without a size[] element"<<std::endl;
  785. FieldSpec spec(
  786. "",
  787. L"",
  788. L"",
  789. 258 + m_fields.size(),
  790. 2
  791. );
  792. spec.ftype = f_ItemImage;
  793. GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, spec.fid,
  794. core::rect<s32>(pos, pos + geom), name, m_font, m_client);
  795. auto style = getDefaultStyleForElement("item_image", spec.fname);
  796. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  797. // item images should let events through
  798. m_clickthrough_elements.push_back(e);
  799. m_fields.push_back(spec);
  800. }
  801. void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
  802. const std::string &type)
  803. {
  804. std::vector<std::string> parts;
  805. if (!precheckElement("button", element, 4, 4, parts))
  806. return;
  807. std::vector<std::string> v_pos = split(parts[0],',');
  808. std::vector<std::string> v_geom = split(parts[1],',');
  809. std::string name = parts[2];
  810. std::string label = parts[3];
  811. MY_CHECKPOS("button",0);
  812. MY_CHECKGEOM("button",1);
  813. v2s32 pos;
  814. v2s32 geom;
  815. core::rect<s32> rect;
  816. if (data->real_coordinates) {
  817. pos = getRealCoordinateBasePos(v_pos);
  818. geom = getRealCoordinateGeometry(v_geom);
  819. rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
  820. pos.Y+geom.Y);
  821. } else {
  822. pos = getElementBasePos(&v_pos);
  823. geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
  824. pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
  825. rect = core::rect<s32>(pos.X, pos.Y - m_btn_height,
  826. pos.X + geom.X, pos.Y + m_btn_height);
  827. }
  828. if(!data->explicit_size)
  829. warningstream<<"invalid use of button without a size[] element"<<std::endl;
  830. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  831. FieldSpec spec(
  832. name,
  833. wlabel,
  834. L"",
  835. 258 + m_fields.size()
  836. );
  837. spec.ftype = f_Button;
  838. if(type == "button_exit")
  839. spec.is_exit = true;
  840. GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc,
  841. data->current_parent, spec.fid, spec.flabel.c_str());
  842. auto style = getStyleForElement(type, name, (type != "button") ? "button" : "");
  843. spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, "");
  844. e->setStyles(style);
  845. if (spec.fname == m_focused_element) {
  846. Environment->setFocus(e);
  847. }
  848. m_fields.push_back(spec);
  849. }
  850. bool GUIFormSpecMenu::parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect)
  851. {
  852. core::rect<s32> rect;
  853. std::vector<std::string> v_rect = split(value, ',');
  854. if (v_rect.size() == 1) {
  855. s32 x = stoi(v_rect[0]);
  856. rect.UpperLeftCorner = core::vector2di(x, x);
  857. rect.LowerRightCorner = core::vector2di(-x, -x);
  858. } else if (v_rect.size() == 2) {
  859. s32 x = stoi(v_rect[0]);
  860. s32 y = stoi(v_rect[1]);
  861. rect.UpperLeftCorner = core::vector2di(x, y);
  862. rect.LowerRightCorner = core::vector2di(-x, -y);
  863. // `-x` is interpreted as `w - x`
  864. } else if (v_rect.size() == 4) {
  865. rect.UpperLeftCorner = core::vector2di(stoi(v_rect[0]), stoi(v_rect[1]));
  866. rect.LowerRightCorner = core::vector2di(stoi(v_rect[2]), stoi(v_rect[3]));
  867. } else {
  868. warningstream << "Invalid rectangle string format: \"" << value
  869. << "\"" << std::endl;
  870. return false;
  871. }
  872. *parsed_rect = rect;
  873. return true;
  874. }
  875. void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element)
  876. {
  877. std::vector<std::string> parts;
  878. if (!precheckElement("background", element, 3, 5, parts))
  879. return;
  880. std::vector<std::string> v_pos = split(parts[0],',');
  881. std::vector<std::string> v_geom = split(parts[1],',');
  882. std::string name = unescape_string(parts[2]);
  883. MY_CHECKPOS("background",0);
  884. MY_CHECKGEOM("background",1);
  885. v2s32 pos;
  886. v2s32 geom;
  887. if (data->real_coordinates) {
  888. pos = getRealCoordinateBasePos(v_pos);
  889. geom = getRealCoordinateGeometry(v_geom);
  890. } else {
  891. pos = getElementBasePos(&v_pos);
  892. pos.X -= (spacing.X - (float)imgsize.X) / 2;
  893. pos.Y -= (spacing.Y - (float)imgsize.Y) / 2;
  894. geom.X = stof(v_geom[0]) * spacing.X;
  895. geom.Y = stof(v_geom[1]) * spacing.Y;
  896. }
  897. bool clip = false;
  898. if (parts.size() >= 4 && is_yes(parts[3])) {
  899. if (data->real_coordinates) {
  900. pos = getRealCoordinateBasePos(v_pos) * -1;
  901. geom = v2s32(0, 0);
  902. } else {
  903. pos.X = stoi(v_pos[0]); //acts as offset
  904. pos.Y = stoi(v_pos[1]);
  905. }
  906. clip = true;
  907. }
  908. core::rect<s32> middle;
  909. if (parts.size() >= 5)
  910. parseMiddleRect(parts[4], &middle);
  911. if (!data->explicit_size && !clip)
  912. warningstream << "invalid use of unclipped background without a size[] element" << std::endl;
  913. FieldSpec spec(
  914. name,
  915. L"",
  916. L"",
  917. 258 + m_fields.size()
  918. );
  919. core::rect<s32> rect{};
  920. v2s32 autoclip_offset{};
  921. if (!clip) {
  922. // no auto_clip => position like normal image
  923. rect = core::rect<s32>(pos, pos + geom);
  924. } else {
  925. // element will be auto-clipped when drawing
  926. autoclip_offset = pos;
  927. }
  928. GUIBackgroundImage *e = new GUIBackgroundImage(Environment, data->background_parent.get(),
  929. spec.fid, rect, name, middle, m_tsrc, clip, autoclip_offset);
  930. FATAL_ERROR_IF(!e, "Failed to create background formspec element");
  931. e->setNotClipped(true);
  932. m_fields.push_back(spec);
  933. e->drop();
  934. }
  935. void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element)
  936. {
  937. std::vector<std::string> parts = split(element,';');
  938. data->table_options.clear();
  939. for (const std::string &part : parts) {
  940. // Parse table option
  941. std::string opt = unescape_string(part);
  942. data->table_options.push_back(GUITable::splitOption(opt));
  943. }
  944. }
  945. void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &element)
  946. {
  947. std::vector<std::string> parts = split(element,';');
  948. data->table_columns.clear();
  949. for (const std::string &part : parts) {
  950. std::vector<std::string> col_parts = split(part,',');
  951. GUITable::TableColumn column;
  952. // Parse column type
  953. if (!col_parts.empty())
  954. column.type = col_parts[0];
  955. // Parse column options
  956. for (size_t j = 1; j < col_parts.size(); ++j) {
  957. std::string opt = unescape_string(col_parts[j]);
  958. column.options.push_back(GUITable::splitOption(opt));
  959. }
  960. data->table_columns.push_back(column);
  961. }
  962. }
  963. void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
  964. {
  965. std::vector<std::string> parts;
  966. if (!precheckElement("table", element, 4, 5, parts))
  967. return;
  968. std::vector<std::string> v_pos = split(parts[0],',');
  969. std::vector<std::string> v_geom = split(parts[1],',');
  970. std::string name = parts[2];
  971. std::vector<std::string> items = split(parts[3],',');
  972. std::string str_initial_selection;
  973. if (parts.size() >= 5)
  974. str_initial_selection = parts[4];
  975. MY_CHECKPOS("table",0);
  976. MY_CHECKGEOM("table",1);
  977. v2s32 pos;
  978. v2s32 geom;
  979. if (data->real_coordinates) {
  980. pos = getRealCoordinateBasePos(v_pos);
  981. geom = getRealCoordinateGeometry(v_geom);
  982. } else {
  983. pos = getElementBasePos(&v_pos);
  984. geom.X = stof(v_geom[0]) * spacing.X;
  985. geom.Y = stof(v_geom[1]) * spacing.Y;
  986. }
  987. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  988. FieldSpec spec(
  989. name,
  990. L"",
  991. L"",
  992. 258 + m_fields.size()
  993. );
  994. spec.ftype = f_Table;
  995. for (std::string &item : items) {
  996. item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
  997. }
  998. //now really show table
  999. GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
  1000. rect, m_tsrc);
  1001. if (spec.fname == m_focused_element) {
  1002. Environment->setFocus(e);
  1003. }
  1004. e->setTable(data->table_options, data->table_columns, items);
  1005. if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
  1006. e->setDynamicData(data->table_dyndata[name]);
  1007. }
  1008. if (!str_initial_selection.empty() && str_initial_selection != "0")
  1009. e->setSelected(stoi(str_initial_selection));
  1010. auto style = getDefaultStyleForElement("table", name);
  1011. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  1012. e->setOverrideFont(style.getFont());
  1013. m_tables.emplace_back(spec, e);
  1014. m_fields.push_back(spec);
  1015. }
  1016. void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element)
  1017. {
  1018. std::vector<std::string> parts;
  1019. if (!precheckElement("textlist", element, 4, 6, parts))
  1020. return;
  1021. std::vector<std::string> v_pos = split(parts[0],',');
  1022. std::vector<std::string> v_geom = split(parts[1],',');
  1023. std::string name = parts[2];
  1024. std::vector<std::string> items = split(parts[3],',');
  1025. std::string str_initial_selection;
  1026. std::string str_transparent = "false";
  1027. if (parts.size() >= 5)
  1028. str_initial_selection = parts[4];
  1029. if (parts.size() >= 6)
  1030. str_transparent = parts[5];
  1031. MY_CHECKPOS("textlist",0);
  1032. MY_CHECKGEOM("textlist",1);
  1033. v2s32 pos;
  1034. v2s32 geom;
  1035. if (data->real_coordinates) {
  1036. pos = getRealCoordinateBasePos(v_pos);
  1037. geom = getRealCoordinateGeometry(v_geom);
  1038. } else {
  1039. pos = getElementBasePos(&v_pos);
  1040. geom.X = stof(v_geom[0]) * spacing.X;
  1041. geom.Y = stof(v_geom[1]) * spacing.Y;
  1042. }
  1043. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  1044. FieldSpec spec(
  1045. name,
  1046. L"",
  1047. L"",
  1048. 258 + m_fields.size()
  1049. );
  1050. spec.ftype = f_Table;
  1051. for (std::string &item : items) {
  1052. item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
  1053. }
  1054. //now really show list
  1055. GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
  1056. rect, m_tsrc);
  1057. if (spec.fname == m_focused_element) {
  1058. Environment->setFocus(e);
  1059. }
  1060. e->setTextList(items, is_yes(str_transparent));
  1061. if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
  1062. e->setDynamicData(data->table_dyndata[name]);
  1063. }
  1064. if (!str_initial_selection.empty() && str_initial_selection != "0")
  1065. e->setSelected(stoi(str_initial_selection));
  1066. auto style = getDefaultStyleForElement("textlist", name);
  1067. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  1068. e->setOverrideFont(style.getFont());
  1069. m_tables.emplace_back(spec, e);
  1070. m_fields.push_back(spec);
  1071. }
  1072. void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element)
  1073. {
  1074. std::vector<std::string> parts;
  1075. if (!precheckElement("dropdown", element, 5, 6, parts))
  1076. return;
  1077. std::vector<std::string> v_pos = split(parts[0], ',');
  1078. std::string name = parts[2];
  1079. std::vector<std::string> items = split(parts[3], ',');
  1080. std::string str_initial_selection = parts[4];
  1081. if (parts.size() >= 6 && is_yes(parts[5]))
  1082. m_dropdown_index_event[name] = true;
  1083. MY_CHECKPOS("dropdown",0);
  1084. v2s32 pos;
  1085. v2s32 geom;
  1086. core::rect<s32> rect;
  1087. if (data->real_coordinates) {
  1088. std::vector<std::string> v_geom = split(parts[1],',');
  1089. if (v_geom.size() == 1)
  1090. v_geom.emplace_back("1");
  1091. MY_CHECKGEOM("dropdown",1);
  1092. pos = getRealCoordinateBasePos(v_pos);
  1093. geom = getRealCoordinateGeometry(v_geom);
  1094. rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  1095. } else {
  1096. pos = getElementBasePos(&v_pos);
  1097. s32 width = stof(parts[1]) * spacing.Y;
  1098. rect = core::rect<s32>(pos.X, pos.Y,
  1099. pos.X + width, pos.Y + (m_btn_height * 2));
  1100. }
  1101. FieldSpec spec(
  1102. name,
  1103. L"",
  1104. L"",
  1105. 258 + m_fields.size()
  1106. );
  1107. spec.ftype = f_DropDown;
  1108. spec.send = true;
  1109. //now really show list
  1110. gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent,
  1111. spec.fid);
  1112. if (spec.fname == m_focused_element) {
  1113. Environment->setFocus(e);
  1114. }
  1115. for (const std::string &item : items) {
  1116. e->addItem(unescape_translate(unescape_string(
  1117. utf8_to_wide(item))).c_str());
  1118. }
  1119. if (!str_initial_selection.empty())
  1120. e->setSelected(stoi(str_initial_selection)-1);
  1121. auto style = getDefaultStyleForElement("dropdown", name);
  1122. spec.sound = style.get(StyleSpec::Property::SOUND, "");
  1123. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  1124. m_fields.push_back(spec);
  1125. m_dropdowns.emplace_back(spec, std::vector<std::string>());
  1126. std::vector<std::string> &values = m_dropdowns.back().second;
  1127. for (const std::string &item : items) {
  1128. values.push_back(unescape_string(item));
  1129. }
  1130. }
  1131. void GUIFormSpecMenu::parseFieldEnterAfterEdit(parserData *data, const std::string &element)
  1132. {
  1133. std::vector<std::string> parts;
  1134. if (!precheckElement("field_enter_after_edit", element, 2, 2, parts))
  1135. return;
  1136. field_enter_after_edit[parts[0]] = is_yes(parts[1]);
  1137. }
  1138. void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element)
  1139. {
  1140. std::vector<std::string> parts;
  1141. if (!precheckElement("field_close_on_enter", element, 2, 2, parts))
  1142. return;
  1143. field_close_on_enter[parts[0]] = is_yes(parts[1]);
  1144. }
  1145. void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element)
  1146. {
  1147. std::vector<std::string> parts;
  1148. if (!precheckElement("pwdfield", element, 4, 4, parts))
  1149. return;
  1150. std::vector<std::string> v_pos = split(parts[0],',');
  1151. std::vector<std::string> v_geom = split(parts[1],',');
  1152. std::string name = parts[2];
  1153. std::string label = parts[3];
  1154. MY_CHECKPOS("pwdfield",0);
  1155. MY_CHECKGEOM("pwdfield",1);
  1156. v2s32 pos;
  1157. v2s32 geom;
  1158. if (data->real_coordinates) {
  1159. pos = getRealCoordinateBasePos(v_pos);
  1160. geom = getRealCoordinateGeometry(v_geom);
  1161. } else {
  1162. pos = getElementBasePos(&v_pos);
  1163. pos -= padding;
  1164. geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
  1165. pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
  1166. pos.Y -= m_btn_height;
  1167. geom.Y = m_btn_height*2;
  1168. }
  1169. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  1170. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  1171. FieldSpec spec(
  1172. name,
  1173. wlabel,
  1174. L"",
  1175. 258 + m_fields.size(),
  1176. 0,
  1177. ECI_IBEAM
  1178. );
  1179. spec.send = true;
  1180. gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true,
  1181. data->current_parent, spec.fid);
  1182. if (spec.fname == m_focused_element) {
  1183. Environment->setFocus(e);
  1184. }
  1185. if (label.length() >= 1) {
  1186. int font_height = g_fontengine->getTextHeight();
  1187. rect.UpperLeftCorner.Y -= font_height;
  1188. rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
  1189. gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
  1190. data->current_parent, 0);
  1191. }
  1192. e->setPasswordBox(true,L'*');
  1193. auto style = getDefaultStyleForElement("pwdfield", name, "field");
  1194. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  1195. e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
  1196. e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
  1197. e->setOverrideFont(style.getFont());
  1198. irr::SEvent evt;
  1199. evt.EventType = EET_KEY_INPUT_EVENT;
  1200. evt.KeyInput.Key = KEY_END;
  1201. evt.KeyInput.Char = 0;
  1202. evt.KeyInput.Control = false;
  1203. evt.KeyInput.Shift = false;
  1204. evt.KeyInput.PressedDown = true;
  1205. e->OnEvent(evt);
  1206. // Note: Before 5.2.0 "parts.size() >= 5" resulted in a
  1207. // warning referring to field_close_on_enter[]!
  1208. m_fields.push_back(spec);
  1209. }
  1210. void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
  1211. core::rect<s32> &rect, bool is_multiline)
  1212. {
  1213. bool is_editable = !spec.fname.empty();
  1214. if (!is_editable && !is_multiline) {
  1215. // spec field id to 0, this stops submit searching for a value that isn't there
  1216. gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
  1217. data->current_parent, 0);
  1218. return;
  1219. }
  1220. if (is_editable) {
  1221. spec.send = true;
  1222. } else if (is_multiline &&
  1223. spec.fdefault.empty() && !spec.flabel.empty()) {
  1224. // Multiline textareas: swap default and label for backwards compat
  1225. spec.flabel.swap(spec.fdefault);
  1226. }
  1227. gui::IGUIEditBox *e = nullptr;
  1228. if (is_multiline) {
  1229. e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment,
  1230. data->current_parent, spec.fid, rect, m_tsrc, is_editable, true);
  1231. } else if (is_editable) {
  1232. e = Environment->addEditBox(spec.fdefault.c_str(), rect, true,
  1233. data->current_parent, spec.fid);
  1234. e->grab();
  1235. }
  1236. auto style = getDefaultStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
  1237. if (e) {
  1238. if (is_editable && spec.fname == m_focused_element)
  1239. Environment->setFocus(e);
  1240. if (is_multiline) {
  1241. e->setMultiLine(true);
  1242. e->setWordWrap(true);
  1243. e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
  1244. } else {
  1245. irr::SEvent evt;
  1246. evt.EventType = EET_KEY_INPUT_EVENT;
  1247. evt.KeyInput.Key = KEY_END;
  1248. evt.KeyInput.Char = 0;
  1249. evt.KeyInput.Control = 0;
  1250. evt.KeyInput.Shift = 0;
  1251. evt.KeyInput.PressedDown = true;
  1252. e->OnEvent(evt);
  1253. }
  1254. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  1255. e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
  1256. bool border = style.getBool(StyleSpec::BORDER, true);
  1257. e->setDrawBorder(border);
  1258. e->setDrawBackground(border);
  1259. e->setOverrideFont(style.getFont());
  1260. e->drop();
  1261. }
  1262. if (!spec.flabel.empty()) {
  1263. int font_height = g_fontengine->getTextHeight();
  1264. rect.UpperLeftCorner.Y -= font_height;
  1265. rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
  1266. IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(),
  1267. rect, false, true, data->current_parent, 0);
  1268. if (t)
  1269. t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  1270. }
  1271. }
  1272. void GUIFormSpecMenu::parseSimpleField(parserData *data,
  1273. std::vector<std::string> &parts)
  1274. {
  1275. std::string name = parts[0];
  1276. std::string label = parts[1];
  1277. std::string default_val = parts[2];
  1278. core::rect<s32> rect;
  1279. if (data->explicit_size)
  1280. warningstream << "invalid use of unpositioned \"field\" in inventory" << std::endl;
  1281. v2s32 pos = getElementBasePos(nullptr);
  1282. pos.Y = (data->simple_field_count + 2) * 60;
  1283. v2s32 size = DesiredRect.getSize();
  1284. rect = core::rect<s32>(
  1285. size.X / 2 - 150, pos.Y,
  1286. size.X / 2 - 150 + 300, pos.Y + m_btn_height * 2
  1287. );
  1288. if (m_form_src)
  1289. default_val = m_form_src->resolveText(default_val);
  1290. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  1291. FieldSpec spec(
  1292. name,
  1293. wlabel,
  1294. utf8_to_wide(unescape_string(default_val)),
  1295. 258 + m_fields.size(),
  1296. 0,
  1297. ECI_IBEAM
  1298. );
  1299. createTextField(data, spec, rect, false);
  1300. m_fields.push_back(spec);
  1301. data->simple_field_count++;
  1302. }
  1303. void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
  1304. const std::string &type)
  1305. {
  1306. std::vector<std::string> v_pos = split(parts[0],',');
  1307. std::vector<std::string> v_geom = split(parts[1],',');
  1308. std::string name = parts[2];
  1309. std::string label = parts[3];
  1310. std::string default_val = parts[4];
  1311. MY_CHECKPOS(type,0);
  1312. MY_CHECKGEOM(type,1);
  1313. v2s32 pos;
  1314. v2s32 geom;
  1315. if (data->real_coordinates) {
  1316. pos = getRealCoordinateBasePos(v_pos);
  1317. geom = getRealCoordinateGeometry(v_geom);
  1318. } else {
  1319. pos = getElementBasePos(&v_pos);
  1320. pos -= padding;
  1321. geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
  1322. if (type == "textarea")
  1323. {
  1324. geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
  1325. pos.Y += m_btn_height;
  1326. }
  1327. else
  1328. {
  1329. pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
  1330. pos.Y -= m_btn_height;
  1331. geom.Y = m_btn_height*2;
  1332. }
  1333. }
  1334. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  1335. if(!data->explicit_size)
  1336. warningstream<<"invalid use of positioned "<<type<<" without a size[] element"<<std::endl;
  1337. if(m_form_src)
  1338. default_val = m_form_src->resolveText(default_val);
  1339. std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
  1340. FieldSpec spec(
  1341. name,
  1342. wlabel,
  1343. utf8_to_wide(unescape_string(default_val)),
  1344. 258 + m_fields.size(),
  1345. 0,
  1346. ECI_IBEAM
  1347. );
  1348. createTextField(data, spec, rect, type == "textarea");
  1349. // Note: Before 5.2.0 "parts.size() >= 6" resulted in a
  1350. // warning referring to field_close_on_enter[]!
  1351. m_fields.push_back(spec);
  1352. }
  1353. void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
  1354. const std::string &type)
  1355. {
  1356. std::vector<std::string> parts;
  1357. if (!precheckElement(type, element, 3, 5, parts))
  1358. return;
  1359. if (parts.size() == 3 || parts.size() == 4) {
  1360. parseSimpleField(data, parts);
  1361. return;
  1362. }
  1363. // Else: >= 5 arguments in "parts"
  1364. parseTextArea(data, parts, type);
  1365. }
  1366. void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element)
  1367. {
  1368. std::vector<std::string> parts;
  1369. if (!precheckElement("hypertext", element, 4, 4, parts))
  1370. return;
  1371. std::vector<std::string> v_pos = split(parts[0], ',');
  1372. std::vector<std::string> v_geom = split(parts[1], ',');
  1373. std::string name = parts[2];
  1374. std::string text = parts[3];
  1375. MY_CHECKPOS("hypertext", 0);
  1376. MY_CHECKGEOM("hypertext", 1);
  1377. v2s32 pos;
  1378. v2s32 geom;
  1379. if (data->real_coordinates) {
  1380. pos = getRealCoordinateBasePos(v_pos);
  1381. geom = getRealCoordinateGeometry(v_geom);
  1382. } else {
  1383. pos = getElementBasePos(&v_pos);
  1384. pos -= padding;
  1385. geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
  1386. geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y - imgsize.Y);
  1387. pos.Y += m_btn_height;
  1388. }
  1389. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y);
  1390. if(m_form_src)
  1391. text = m_form_src->resolveText(text);
  1392. FieldSpec spec(
  1393. name,
  1394. translate_string(utf8_to_wide(unescape_string(text))),
  1395. L"",
  1396. 258 + m_fields.size()
  1397. );
  1398. spec.ftype = f_HyperText;
  1399. auto style = getDefaultStyleForElement("hypertext", spec.fname);
  1400. spec.sound = style.get(StyleSpec::Property::SOUND, "");
  1401. GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment,
  1402. data->current_parent, spec.fid, rect, m_client, m_tsrc);
  1403. e->drop();
  1404. m_fields.push_back(spec);
  1405. }
  1406. void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
  1407. {
  1408. std::vector<std::string> parts;
  1409. if (!precheckElement("label", element, 2, 2, parts))
  1410. return;
  1411. std::vector<std::string> v_pos = split(parts[0],',');
  1412. MY_CHECKPOS("label",0);
  1413. if(!data->explicit_size)
  1414. warningstream<<"invalid use of label without a size[] element"<<std::endl;
  1415. auto style = getDefaultStyleForElement("label", "");
  1416. gui::IGUIFont *font = style.getFont();
  1417. if (!font)
  1418. font = m_font;
  1419. EnrichedString str(unescape_string(utf8_to_wide(parts[1])));
  1420. size_t str_pos = 0;
  1421. for (size_t i = 0; str_pos < str.size(); ++i) {
  1422. // Split per line
  1423. size_t str_nl = str.getString().find(L'\n', str_pos);
  1424. if (str_nl == std::wstring::npos)
  1425. str_nl = str.getString().size();
  1426. EnrichedString line = str.substr(str_pos, str_nl - str_pos);
  1427. str_pos += line.size() + 1;
  1428. core::rect<s32> rect;
  1429. if (data->real_coordinates) {
  1430. // Lines are spaced at the distance of 1/2 imgsize.
  1431. // This alows lines that line up with the new elements
  1432. // easily without sacrificing good line distance. If
  1433. // it was one whole imgsize, it would have too much
  1434. // spacing.
  1435. v2s32 pos = getRealCoordinateBasePos(v_pos);
  1436. // Labels are positioned by their center, not their top.
  1437. pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2);
  1438. rect = core::rect<s32>(
  1439. pos.X, pos.Y,
  1440. pos.X + font->getDimension(line.c_str()).Width,
  1441. pos.Y + imgsize.Y);
  1442. } else {
  1443. // Lines are spaced at the nominal distance of
  1444. // 2/5 inventory slot, even if the font doesn't
  1445. // quite match that. This provides consistent
  1446. // form layout, at the expense of sometimes
  1447. // having sub-optimal spacing for the font.
  1448. // We multiply by 2 and then divide by 5, rather
  1449. // than multiply by 0.4, to get exact results
  1450. // in the integer cases: 0.4 is not exactly
  1451. // representable in binary floating point.
  1452. v2s32 pos = getElementBasePos(nullptr);
  1453. pos.X += stof(v_pos[0]) * spacing.X;
  1454. pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y;
  1455. pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0;
  1456. rect = core::rect<s32>(
  1457. pos.X, pos.Y - m_btn_height,
  1458. pos.X + font->getDimension(line.c_str()).Width,
  1459. pos.Y + m_btn_height);
  1460. }
  1461. FieldSpec spec(
  1462. "",
  1463. L"",
  1464. L"",
  1465. 258 + m_fields.size(),
  1466. 4
  1467. );
  1468. gui::IGUIStaticText *e = gui::StaticText::add(Environment,
  1469. line, rect, false, false, data->current_parent,
  1470. spec.fid);
  1471. e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
  1472. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  1473. e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
  1474. e->setOverrideFont(font);
  1475. m_fields.push_back(spec);
  1476. // labels should let events through
  1477. e->grab();
  1478. m_clickthrough_elements.push_back(e);
  1479. }
  1480. }
  1481. void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element)
  1482. {
  1483. std::vector<std::string> parts;
  1484. if (!precheckElement("vertlabel", element, 2, 2, parts))
  1485. return;
  1486. std::vector<std::string> v_pos = split(parts[0],',');
  1487. std::wstring text = unescape_translate(
  1488. unescape_string(utf8_to_wide(parts[1])));
  1489. MY_CHECKPOS("vertlabel",1);
  1490. auto style = getDefaultStyleForElement("vertlabel", "", "label");
  1491. gui::IGUIFont *font = style.getFont();
  1492. if (!font)
  1493. font = m_font;
  1494. v2s32 pos;
  1495. core::rect<s32> rect;
  1496. if (data->real_coordinates) {
  1497. pos = getRealCoordinateBasePos(v_pos);
  1498. // Vertlabels are positioned by center, not left.
  1499. pos.X -= imgsize.X / 2;
  1500. // We use text.length + 1 because without it, the rect
  1501. // isn't quite tall enough and cuts off the text.
  1502. rect = core::rect<s32>(pos.X, pos.Y,
  1503. pos.X + imgsize.X,
  1504. pos.Y + font_line_height(font) *
  1505. (text.length() + 1));
  1506. } else {
  1507. pos = getElementBasePos(&v_pos);
  1508. // As above, the length must be one longer. The width of
  1509. // the rect (15 pixels) seems rather arbitrary, but
  1510. // changing it might break something.
  1511. rect = core::rect<s32>(
  1512. pos.X, pos.Y+((imgsize.Y/2) - m_btn_height),
  1513. pos.X+15, pos.Y +
  1514. font_line_height(font) *
  1515. (text.length() + 1) +
  1516. ((imgsize.Y/2) - m_btn_height));
  1517. }
  1518. if(!data->explicit_size)
  1519. warningstream<<"invalid use of label without a size[] element"<<std::endl;
  1520. std::wstring label;
  1521. for (wchar_t i : text) {
  1522. label += i;
  1523. label += L"\n";
  1524. }
  1525. FieldSpec spec(
  1526. "",
  1527. label,
  1528. L"",
  1529. 258 + m_fields.size()
  1530. );
  1531. gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(),
  1532. rect, false, false, data->current_parent, spec.fid);
  1533. e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
  1534. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
  1535. e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
  1536. e->setOverrideFont(font);
  1537. m_fields.push_back(spec);
  1538. // vertlabels should let events through
  1539. e->grab();
  1540. m_clickthrough_elements.push_back(e);
  1541. }
  1542. void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element,
  1543. const std::string &type)
  1544. {
  1545. std::vector<std::string> parts;
  1546. if (!precheckElement("image_button", element, 5, 8, parts))
  1547. return;
  1548. if (parts.size() == 6) {
  1549. // Invalid argument count.
  1550. errorstream << "Invalid image_button element(" << parts.size() << "): '" << element << "'" << std::endl;
  1551. return;
  1552. }
  1553. std::vector<std::string> v_pos = split(parts[0],',');
  1554. std::vector<std::string> v_geom = split(parts[1],',');
  1555. std::string image_name = parts[2];
  1556. std::string name = parts[3];
  1557. std::string label = parts[4];
  1558. MY_CHECKPOS("image_button",0);
  1559. MY_CHECKGEOM("image_button",1);
  1560. std::string pressed_image_name;
  1561. if (parts.size() >= 8) {
  1562. pressed_image_name = parts[7];
  1563. }
  1564. v2s32 pos;
  1565. v2s32 geom;
  1566. if (data->real_coordinates) {
  1567. pos = getRealCoordinateBasePos(v_pos);
  1568. geom = getRealCoordinateGeometry(v_geom);
  1569. } else {
  1570. pos = getElementBasePos(&v_pos);
  1571. geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
  1572. geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
  1573. }
  1574. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
  1575. pos.Y+geom.Y);
  1576. if (!data->explicit_size)
  1577. warningstream<<"invalid use of image_button without a size[] element"<<std::endl;
  1578. image_name = unescape_string(image_name);
  1579. pressed_image_name = unescape_string(pressed_image_name);
  1580. std::wstring wlabel = utf8_to_wide(unescape_string(label));
  1581. FieldSpec spec(
  1582. name,
  1583. wlabel,
  1584. utf8_to_wide(image_name),
  1585. 258 + m_fields.size()
  1586. );
  1587. spec.ftype = f_Button;
  1588. if (type == "image_button_exit")
  1589. spec.is_exit = true;
  1590. GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc,
  1591. data->current_parent, spec.fid, spec.flabel.c_str());
  1592. if (spec.fname == m_focused_element) {
  1593. Environment->setFocus(e);
  1594. }
  1595. auto style = getStyleForElement("image_button", spec.fname);
  1596. spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, "");
  1597. // Override style properties with values specified directly in the element
  1598. if (!image_name.empty())
  1599. style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name);
  1600. if (!pressed_image_name.empty())
  1601. style[StyleSpec::STATE_PRESSED].set(StyleSpec::FGIMG, pressed_image_name);
  1602. if (parts.size() >= 7) {
  1603. style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]);
  1604. style[StyleSpec::STATE_DEFAULT].set(StyleSpec::BORDER, parts[6]);
  1605. }
  1606. e->setStyles(style);
  1607. e->setScaleImage(true);
  1608. m_fields.push_back(spec);
  1609. }
  1610. void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element)
  1611. {
  1612. std::vector<std::string> parts;
  1613. if (!precheckElement("tabheader", element, 4, 7, parts))
  1614. return;
  1615. // Length 7: Additional "height" parameter after "pos". Only valid with real_coordinates.
  1616. // Note: New arguments for the "height" syntax cannot be added without breaking older clients.
  1617. if (parts.size() == 5 || (parts.size() == 7 && !data->real_coordinates)) {
  1618. errorstream << "Invalid tabheader element(" << parts.size() << "): '"
  1619. << element << "'" << std::endl;
  1620. return;
  1621. }
  1622. std::vector<std::string> v_pos = split(parts[0],',');
  1623. // If we're using real coordinates, add an extra field for height.
  1624. // Width is not here because tabs are the width of the text, and
  1625. // there's no reason to change that.
  1626. unsigned int i = 0;
  1627. std::vector<std::string> v_geom = {"1", "1"}; // Dummy width and height
  1628. bool auto_width = true;
  1629. if (parts.size() == 7) {
  1630. i++;
  1631. v_geom = split(parts[1], ',');
  1632. if (v_geom.size() == 1)
  1633. v_geom.insert(v_geom.begin(), "1"); // Dummy value
  1634. else
  1635. auto_width = false;
  1636. }
  1637. std::string name = parts[i+1];
  1638. std::vector<std::string> buttons = split(parts[i+2], ',');
  1639. std::string str_index = parts[i+3];
  1640. bool show_background = true;
  1641. bool show_border = true;
  1642. int tab_index = stoi(str_index) - 1;
  1643. MY_CHECKPOS("tabheader", 0);
  1644. if (parts.size() == 6 + i) {
  1645. if (parts[4+i] == "true")
  1646. show_background = false;
  1647. if (parts[5+i] == "false")
  1648. show_border = false;
  1649. }
  1650. FieldSpec spec(
  1651. name,
  1652. L"",
  1653. L"",
  1654. 258 + m_fields.size()
  1655. );
  1656. spec.ftype = f_TabHeader;
  1657. v2s32 pos;
  1658. v2s32 geom;
  1659. if (data->real_coordinates) {
  1660. pos = getRealCoordinateBasePos(v_pos);
  1661. geom = getRealCoordinateGeometry(v_geom);
  1662. // Set default height
  1663. if (parts.size() <= 6)
  1664. geom.Y = m_btn_height * 2;
  1665. pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top.
  1666. if (auto_width)
  1667. geom.X = DesiredRect.getWidth(); // Set automatic width
  1668. MY_CHECKGEOM("tabheader", 1);
  1669. } else {
  1670. v2f32 pos_f = pos_offset * spacing;
  1671. pos_f.X += stof(v_pos[0]) * spacing.X;
  1672. pos_f.Y += stof(v_pos[1]) * spacing.Y - m_btn_height * 2;
  1673. pos = v2s32(pos_f.X, pos_f.Y);
  1674. geom.Y = m_btn_height * 2;
  1675. geom.X = DesiredRect.getWidth();
  1676. }
  1677. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
  1678. pos.Y+geom.Y);
  1679. gui::IGUITabControl *e = Environment->addTabControl(rect,
  1680. data->current_parent, show_background, show_border, spec.fid);
  1681. e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
  1682. irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
  1683. e->setTabHeight(geom.Y);
  1684. auto style = getDefaultStyleForElement("tabheader", name);
  1685. spec.sound = style.get(StyleSpec::Property::SOUND, "");
  1686. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true));
  1687. for (const std::string &button : buttons) {
  1688. auto tab = e->addTab(unescape_translate(unescape_string(
  1689. utf8_to_wide(button))).c_str(), -1);
  1690. if (style.isNotDefault(StyleSpec::BGCOLOR))
  1691. tab->setBackgroundColor(style.getColor(StyleSpec::BGCOLOR));
  1692. tab->setTextColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
  1693. }
  1694. if ((tab_index >= 0) &&
  1695. (buttons.size() < INT_MAX) &&
  1696. (tab_index < (int) buttons.size()))
  1697. e->setActiveTab(tab_index);
  1698. m_fields.push_back(spec);
  1699. m_tabheader_upper_edge = MYMIN(m_tabheader_upper_edge, rect.UpperLeftCorner.Y);
  1700. }
  1701. void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
  1702. {
  1703. MY_CHECKCLIENT("item_image_button");
  1704. std::vector<std::string> parts;
  1705. if (!precheckElement("item_image_button", element, 5, 5, parts))
  1706. return;
  1707. std::vector<std::string> v_pos = split(parts[0],',');
  1708. std::vector<std::string> v_geom = split(parts[1],',');
  1709. std::string item_name = parts[2];
  1710. std::string name = parts[3];
  1711. std::string label = parts[4];
  1712. label = unescape_string(label);
  1713. item_name = unescape_string(item_name);
  1714. MY_CHECKPOS("item_image_button",0);
  1715. MY_CHECKGEOM("item_image_button",1);
  1716. v2s32 pos;
  1717. v2s32 geom;
  1718. if (data->real_coordinates) {
  1719. pos = getRealCoordinateBasePos(v_pos);
  1720. geom = getRealCoordinateGeometry(v_geom);
  1721. } else {
  1722. pos = getElementBasePos(&v_pos);
  1723. geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
  1724. geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
  1725. }
  1726. core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
  1727. if(!data->explicit_size)
  1728. warningstream<<"invalid use of item_image_button without a size[] element"<<std::endl;
  1729. IItemDefManager *idef = m_client->idef();
  1730. ItemStack item;
  1731. item.deSerialize(item_name, idef);
  1732. m_tooltips[name] =
  1733. TooltipSpec(utf8_to_wide(item.getDefinition(idef).description),
  1734. m_default_tooltip_bgcolor,
  1735. m_default_tooltip_color);
  1736. // the spec for the button
  1737. FieldSpec spec_btn(
  1738. name,
  1739. utf8_to_wide(label),
  1740. utf8_to_wide(item_name),
  1741. 258 + m_fields.size(),
  1742. 2
  1743. );
  1744. GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment,
  1745. rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(),
  1746. item_name, m_client);
  1747. auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button");
  1748. spec_btn.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, "");
  1749. e_btn->setStyles(style);
  1750. if (spec_btn.fname == m_focused_element) {
  1751. Environment->setFocus(e_btn);
  1752. }
  1753. spec_btn.ftype = f_Button;
  1754. rect += data->basepos-padding;
  1755. spec_btn.rect = rect;
  1756. m_fields.push_back(spec_btn);
  1757. }
  1758. void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
  1759. {
  1760. std::vector<std::string> parts;
  1761. if (!precheckElement("box", element, 3, 3, parts))
  1762. return;
  1763. std::vector<std::string> v_pos = split(parts[0], ',');
  1764. std::vector<std::string> v_geom = split(parts[1], ',');
  1765. MY_CHECKPOS("box", 0);
  1766. MY_CHECKGEOM("box", 1);
  1767. v2s32 pos;
  1768. v2s32 geom;
  1769. if (data->real_coordinates) {
  1770. pos = getRealCoordinateBasePos(v_pos);
  1771. geom = getRealCoordinateGeometry(v_geom);
  1772. } else {
  1773. pos = getElementBasePos(&v_pos);
  1774. geom.X = stof(v_geom[0]) * spacing.X;
  1775. geom.Y = stof(v_geom[1]) * spacing.Y;
  1776. }
  1777. FieldSpec spec(
  1778. "",
  1779. L"",
  1780. L"",
  1781. 258 + m_fields.size(),
  1782. -2
  1783. );
  1784. spec.ftype = f_Box;
  1785. auto style = getDefaultStyleForElement("box", spec.fname);
  1786. video::SColor tmp_color;
  1787. std::array<video::SColor, 4> colors;
  1788. std::array<video::SColor, 4> bordercolors = {0x0, 0x0, 0x0, 0x0};
  1789. std::array<s32, 4> borderwidths = {0, 0, 0, 0};
  1790. if (parseColorString(parts[2], tmp_color, true, 0x8C)) {
  1791. colors = {tmp_color, tmp_color, tmp_color, tmp_color};
  1792. } else {
  1793. colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0});
  1794. bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS,
  1795. {0x0, 0x0, 0x0, 0x0});
  1796. borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0});
  1797. }
  1798. core::rect<s32> rect(pos, pos + geom);
  1799. GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect,
  1800. colors, bordercolors, borderwidths);
  1801. e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
  1802. e->drop();
  1803. m_fields.push_back(spec);
  1804. }
  1805. void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)
  1806. {
  1807. std::vector<std::string> parts;
  1808. if (!precheckElement("bgcolor", element, 1, 3, parts))
  1809. return;
  1810. const u32 parameter_count = parts.size();
  1811. if (parameter_count > 2 && m_formspec_version < 3) {
  1812. errorstream << "Invalid bgcolor element(" << parameter_count << "): '"
  1813. << element << "'" << std::endl;
  1814. return;
  1815. }
  1816. // bgcolor
  1817. if (parameter_count >= 1 && !parts[0].empty())
  1818. parseColorString(parts[0], m_bgcolor, false);
  1819. // fullscreen
  1820. if (parameter_count >= 2) {
  1821. if (parts[1] == "both") {
  1822. m_bgnonfullscreen = true;
  1823. m_bgfullscreen = true;
  1824. } else if (parts[1] == "neither") {
  1825. m_bgnonfullscreen = false;
  1826. m_bgfullscreen = false;
  1827. } else if (!parts[1].empty() || m_formspec_version < 3) {
  1828. m_bgfullscreen = is_yes(parts[1]);
  1829. m_bgnonfullscreen = !m_bgfullscreen;
  1830. }
  1831. }
  1832. // fbgcolor
  1833. if (parameter_count >= 3 && !parts[2].empty())
  1834. parseColorString(parts[2], m_fullscreen_bgcolor, false);
  1835. }
  1836. void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element)
  1837. {
  1838. std::vector<std::string> parts;
  1839. // Legacy Note: If clients older than 5.5.0-dev are supplied with additional arguments,
  1840. // the tooltip colors will be ignored.
  1841. if (!precheckElement("listcolors", element, 2, 5, parts))
  1842. return;
  1843. if (parts.size() == 4) {
  1844. // Invalid argument combination
  1845. errorstream << "Invalid listcolors element(" << parts.size() << "): '"
  1846. << element << "'" << std::endl;
  1847. return;
  1848. }
  1849. parseColorString(parts[0], data->inventorylist_options.slotbg_n, false);
  1850. parseColorString(parts[1], data->inventorylist_options.slotbg_h, false);
  1851. if (parts.size() >= 3) {
  1852. if (parseColorString(parts[2], data->inventorylist_options.slotbordercolor,
  1853. false)) {
  1854. data->inventorylist_options.slotborder = true;
  1855. }
  1856. }
  1857. if (parts.size() >= 5) {
  1858. video::SColor tmp_color;
  1859. if (parseColorString(parts[3], tmp_color, false))
  1860. m_default_tooltip_bgcolor = tmp_color;
  1861. if (parseColorString(parts[4], tmp_color, false))
  1862. m_default_tooltip_color = tmp_color;
  1863. }
  1864. // update all already parsed inventorylists
  1865. for (GUIInventoryList *e : m_inventorylists) {
  1866. e->setSlotBGColors(data->inventorylist_options.slotbg_n,
  1867. data->inventorylist_options.slotbg_h);
  1868. e->setSlotBorders(data->inventorylist_options.slotborder,
  1869. data->inventorylist_options.slotbordercolor);
  1870. }
  1871. }
  1872. void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
  1873. {
  1874. std::vector<std::string> parts;
  1875. if (!precheckElement("tooltip", element, 2, 5, parts))
  1876. return;
  1877. // Get mode and check size
  1878. bool rect_mode = parts[0].find(',') != std::string::npos;
  1879. size_t base_size = rect_mode ? 3 : 2;
  1880. if (parts.size() != base_size && parts.size() != base_size + 2) {
  1881. errorstream << "Invalid tooltip element(" << parts.size() << "): '"
  1882. << element << "'" << std::endl;
  1883. return;
  1884. }
  1885. // Read colors
  1886. video::SColor bgcolor = m_default_tooltip_bgcolor;
  1887. video::SColor color = m_default_tooltip_color;
  1888. if (parts.size() == base_size + 2 &&
  1889. (!parseColorString(parts[base_size], bgcolor, false) ||
  1890. !parseColorString(parts[base_size + 1], color, false))) {
  1891. errorstream << "Invalid color in tooltip element(" << parts.size()
  1892. << "): '" << element << "'" << std::endl;
  1893. return;
  1894. }
  1895. // Make tooltip spec
  1896. std::string text = unescape_string(parts[rect_mode ? 2 : 1]);
  1897. TooltipSpec spec(utf8_to_wide(text), bgcolor, color);
  1898. // Add tooltip
  1899. if (rect_mode) {
  1900. std::vector<std::string> v_pos = split(parts[0], ',');
  1901. std::vector<std::string> v_geom = split(parts[1], ',');
  1902. MY_CHECKPOS("tooltip", 0);
  1903. MY_CHECKGEOM("tooltip", 1);
  1904. v2s32 pos;
  1905. v2s32 geom;
  1906. if (data->real_coordinates) {
  1907. pos = getRealCoordinateBasePos(v_pos);
  1908. geom = getRealCoordinateGeometry(v_geom);
  1909. } else {
  1910. pos = getElementBasePos(&v_pos);
  1911. geom.X = stof(v_geom[0]) * spacing.X;
  1912. geom.Y = stof(v_geom[1]) * spacing.Y;
  1913. }
  1914. FieldSpec fieldspec(
  1915. "",
  1916. L"",
  1917. L"",
  1918. 258 + m_fields.size()
  1919. );
  1920. core::rect<s32> rect(pos, pos + geom);
  1921. gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
  1922. data->current_parent, fieldspec.fid, rect);
  1923. // the element the rect tooltip is bound to should not block mouse-clicks
  1924. e->setVisible(false);
  1925. m_fields.push_back(fieldspec);
  1926. m_tooltip_rects.emplace_back(e, spec);
  1927. } else {
  1928. m_tooltips[parts[0]] = spec;
  1929. }
  1930. }
  1931. bool GUIFormSpecMenu::parseVersionDirect(const std::string &data)
  1932. {
  1933. //some prechecks
  1934. if (data.empty())
  1935. return false;
  1936. std::vector<std::string> parts = split(data,'[');
  1937. if (parts.size() < 2) {
  1938. return false;
  1939. }
  1940. if (trim(parts[0]) != "formspec_version") {
  1941. return false;
  1942. }
  1943. if (is_number(parts[1])) {
  1944. m_formspec_version = mystoi(parts[1]);
  1945. return true;
  1946. }
  1947. return false;
  1948. }
  1949. bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &element)
  1950. {
  1951. if (element.empty())
  1952. return false;
  1953. std::vector<std::string> parts = split(element,'[');
  1954. if (parts.size() < 2)
  1955. return false;
  1956. auto type = trim(parts[0]);
  1957. std::string description(trim(parts[1]));
  1958. if (type != "size" && type != "invsize")
  1959. return false;
  1960. if (type == "invsize")
  1961. warningstream << "Deprecated formspec element \"invsize\" is used" << std::endl;
  1962. parseSize(data, description);
  1963. return true;
  1964. }
  1965. bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &element)
  1966. {
  1967. if (element.empty())
  1968. return false;
  1969. std::vector<std::string> parts = split(element, '[');
  1970. if (parts.size() != 2)
  1971. return false;
  1972. auto type = trim(parts[0]);
  1973. std::string description(trim(parts[1]));
  1974. if (type != "position")
  1975. return false;
  1976. parsePosition(data, description);
  1977. return true;
  1978. }
  1979. void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element)
  1980. {
  1981. std::vector<std::string> parts = split(element, ';');
  1982. if (parts.size() == 1 ||
  1983. (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) {
  1984. std::vector<std::string> v_geom = split(parts[0], ',');
  1985. MY_CHECKGEOM("position", 0);
  1986. data->offset.X = stof(v_geom[0]);
  1987. data->offset.Y = stof(v_geom[1]);
  1988. return;
  1989. }
  1990. errorstream << "Invalid position element (" << parts.size() << "): '" << element << "'" << std::endl;
  1991. }
  1992. bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &element)
  1993. {
  1994. if (element.empty())
  1995. return false;
  1996. std::vector<std::string> parts = split(element, '[');
  1997. if (parts.size() != 2)
  1998. return false;
  1999. auto type = trim(parts[0]);
  2000. std::string description(trim(parts[1]));
  2001. if (type != "anchor")
  2002. return false;
  2003. parseAnchor(data, description);
  2004. return true;
  2005. }
  2006. void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element)
  2007. {
  2008. std::vector<std::string> parts = split(element, ';');
  2009. if (parts.size() == 1 ||
  2010. (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) {
  2011. std::vector<std::string> v_geom = split(parts[0], ',');
  2012. MY_CHECKGEOM("anchor", 0);
  2013. data->anchor.X = stof(v_geom[0]);
  2014. data->anchor.Y = stof(v_geom[1]);
  2015. return;
  2016. }
  2017. errorstream << "Invalid anchor element (" << parts.size() << "): '" << element
  2018. << "'" << std::endl;
  2019. }
  2020. bool GUIFormSpecMenu::parsePaddingDirect(parserData *data, const std::string &element)
  2021. {
  2022. if (element.empty())
  2023. return false;
  2024. std::vector<std::string> parts = split(element, '[');
  2025. if (parts.size() != 2)
  2026. return false;
  2027. auto type = trim(parts[0]);
  2028. std::string description(trim(parts[1]));
  2029. if (type != "padding")
  2030. return false;
  2031. parsePadding(data, description);
  2032. return true;
  2033. }
  2034. void GUIFormSpecMenu::parsePadding(parserData *data, const std::string &element)
  2035. {
  2036. std::vector<std::string> parts = split(element, ';');
  2037. if (parts.size() == 1 ||
  2038. (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) {
  2039. std::vector<std::string> v_geom = split(parts[0], ',');
  2040. MY_CHECKGEOM("padding", 0);
  2041. data->padding.X = stof(v_geom[0]);
  2042. data->padding.Y = stof(v_geom[1]);
  2043. return;
  2044. }
  2045. errorstream << "Invalid padding element (" << parts.size() << "): '" << element
  2046. << "'" << std::endl;
  2047. }
  2048. bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, bool style_type)
  2049. {
  2050. std::vector<std::string> parts = split(element, ';');
  2051. if (parts.size() < 2) {
  2052. errorstream << "Invalid style element (" << parts.size() << "): '" << element
  2053. << "'" << std::endl;
  2054. return false;
  2055. }
  2056. StyleSpec spec;
  2057. // Parse properties
  2058. for (size_t i = 1; i < parts.size(); i++) {
  2059. size_t equal_pos = parts[i].find('=');
  2060. if (equal_pos == std::string::npos) {
  2061. errorstream << "Invalid style element (Property missing value): '" << element
  2062. << "'" << std::endl;
  2063. return false;
  2064. }
  2065. std::string propname = trim(parts[i].substr(0, equal_pos));
  2066. std::string value = trim(unescape_string(parts[i].substr(equal_pos + 1)));
  2067. std::transform(propname.begin(), propname.end(), propname.begin(), ::tolower);
  2068. StyleSpec::Property prop = StyleSpec::GetPropertyByName(propname);
  2069. if (prop == StyleSpec::NONE) {
  2070. if (property_warned.find(propname) != property_warned.end()) {
  2071. warningstream << "Invalid style element (Unknown property " << propname << "): '"
  2072. << element
  2073. << "'" << std::endl;
  2074. property_warned.insert(propname);
  2075. }
  2076. continue;
  2077. }
  2078. spec.set(prop, value);
  2079. }
  2080. std::vector<std::string> selectors = split(parts[0], ',');
  2081. for (size_t sel = 0; sel < selectors.size(); sel++) {
  2082. std::string selector(trim(selectors[sel]));
  2083. // Copy the style properties to a new StyleSpec
  2084. // This allows a separate state mask per-selector
  2085. StyleSpec selector_spec = spec;
  2086. // Parse state information, if it exists
  2087. bool state_valid = true;
  2088. size_t state_pos = selector.find(':');
  2089. if (state_pos != std::string::npos) {
  2090. std::string state_str = selector.substr(state_pos + 1);
  2091. selector = selector.substr(0, state_pos);
  2092. if (state_str.empty()) {
  2093. errorstream << "Invalid style element (Invalid state): '" << element
  2094. << "'" << std::endl;
  2095. state_valid = false;
  2096. } else {
  2097. std::vector<std::string> states = split(state_str, '+');
  2098. for (std::string &state : states) {
  2099. StyleSpec::State converted = StyleSpec::getStateByName(state);
  2100. if (converted == StyleSpec::STATE_INVALID) {
  2101. infostream << "Unknown style state " << state <<
  2102. " in element '" << element << "'" << std::endl;
  2103. state_valid = false;
  2104. break;
  2105. }
  2106. selector_spec.addState(converted);
  2107. }
  2108. }
  2109. }
  2110. if (!state_valid) {
  2111. // Skip this selector
  2112. continue;
  2113. }
  2114. if (style_type) {
  2115. theme_by_type[selector].push_back(selector_spec);
  2116. } else {
  2117. theme_by_name[selector].push_back(selector_spec);
  2118. }
  2119. // Backwards-compatibility for existing _hovered/_pressed properties
  2120. if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED)
  2121. || selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED)
  2122. || selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) {
  2123. StyleSpec hover_spec;
  2124. hover_spec.addState(StyleSpec::STATE_HOVERED);
  2125. if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED)) {
  2126. hover_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_HOVERED, ""));
  2127. }
  2128. if (selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED)) {
  2129. hover_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_HOVERED, ""));
  2130. }
  2131. if (selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) {
  2132. hover_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_HOVERED, ""));
  2133. }
  2134. if (style_type) {
  2135. theme_by_type[selector].push_back(hover_spec);
  2136. } else {
  2137. theme_by_name[selector].push_back(hover_spec);
  2138. }
  2139. }
  2140. if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED)
  2141. || selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED)
  2142. || selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) {
  2143. StyleSpec press_spec;
  2144. press_spec.addState(StyleSpec::STATE_PRESSED);
  2145. if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED)) {
  2146. press_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_PRESSED, ""));
  2147. }
  2148. if (selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED)) {
  2149. press_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_PRESSED, ""));
  2150. }
  2151. if (selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) {
  2152. press_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_PRESSED, ""));
  2153. }
  2154. if (style_type) {
  2155. theme_by_type[selector].push_back(press_spec);
  2156. } else {
  2157. theme_by_name[selector].push_back(press_spec);
  2158. }
  2159. }
  2160. }
  2161. return true;
  2162. }
  2163. void GUIFormSpecMenu::parseSetFocus(const std::string &element)
  2164. {
  2165. std::vector<std::string> parts;
  2166. if (!precheckElement("set_focus", element, 1, 2, parts))
  2167. return;
  2168. if (m_is_form_regenerated)
  2169. return; // Never focus on resizing
  2170. bool force_focus = parts.size() >= 2 && is_yes(parts[1]);
  2171. if (force_focus || m_text_dst->m_formname != m_last_formname)
  2172. setFocus(parts[0]);
  2173. }
  2174. void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element)
  2175. {
  2176. MY_CHECKCLIENT("model");
  2177. std::vector<std::string> parts;
  2178. if (!precheckElement("model", element, 5, 10, parts))
  2179. return;
  2180. // Avoid length checks by resizing
  2181. if (parts.size() < 10)
  2182. parts.resize(10);
  2183. std::vector<std::string> v_pos = split(parts[0], ',');
  2184. std::vector<std::string> v_geom = split(parts[1], ',');
  2185. std::string name = unescape_string(parts[2]);
  2186. std::string meshstr = unescape_string(parts[3]);
  2187. std::vector<std::string> textures = split(parts[4], ',');
  2188. std::vector<std::string> vec_rot = split(parts[5], ',');
  2189. bool inf_rotation = is_yes(parts[6]);
  2190. bool mousectrl = is_yes(parts[7]) || parts[7].empty(); // default true
  2191. std::vector<std::string> frame_loop = split(parts[8], ',');
  2192. std::string speed = unescape_string(parts[9]);
  2193. MY_CHECKPOS("model", 0);
  2194. MY_CHECKGEOM("model", 1);
  2195. v2s32 pos;
  2196. v2s32 geom;
  2197. if (data->real_coordinates) {
  2198. pos = getRealCoordinateBasePos(v_pos);
  2199. geom = getRealCoordinateGeometry(v_geom);
  2200. } else {
  2201. pos = getElementBasePos(&v_pos);
  2202. geom.X = stof(v_geom[0]) * (float)imgsize.X;
  2203. geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
  2204. }
  2205. if (!data->explicit_size)
  2206. warningstream << "invalid use of model without a size[] element" << std::endl;
  2207. scene::IAnimatedMesh *mesh = m_client->getMesh(meshstr);
  2208. if (!mesh) {
  2209. errorstream << "Invalid model element: Unable to load mesh:"
  2210. << std::endl << "\t" << meshstr << std::endl;
  2211. return;
  2212. }
  2213. FieldSpec spec(
  2214. name,
  2215. L"",
  2216. L"",
  2217. 258 + m_fields.size()
  2218. );
  2219. core::rect<s32> rect(pos, pos + geom);
  2220. GUIScene *e = new GUIScene(Environment, m_client->getSceneManager(),
  2221. data->current_parent, rect, spec.fid);
  2222. auto meshnode = e->setMesh(mesh);
  2223. for (u32 i = 0; i < textures.size() && i < meshnode->getMaterialCount(); ++i)
  2224. e->setTexture(i, m_tsrc->getTexture(unescape_string(textures[i])));
  2225. if (vec_rot.size() >= 2)
  2226. e->setRotation(v2f(stof(vec_rot[0]), stof(vec_rot[1])));
  2227. e->enableContinuousRotation(inf_rotation);
  2228. e->enableMouseControl(mousectrl);
  2229. s32 frame_loop_begin = 0;
  2230. s32 frame_loop_end = 0x7FFFFFFF;
  2231. if (frame_loop.size() == 2) {
  2232. frame_loop_begin = stoi(frame_loop[0]);
  2233. frame_loop_end = stoi(frame_loop[1]);
  2234. }
  2235. e->setFrameLoop(frame_loop_begin, frame_loop_end);
  2236. e->setAnimationSpeed(stof(speed));
  2237. auto style = getStyleForElement("model", spec.fname);
  2238. e->setStyles(style);
  2239. e->drop();
  2240. m_fields.push_back(spec);
  2241. }
  2242. void GUIFormSpecMenu::removeAll()
  2243. {
  2244. // Remove children
  2245. removeAllChildren();
  2246. removeTooltip();
  2247. for (auto &table_it : m_tables)
  2248. table_it.second->drop();
  2249. for (auto &inventorylist_it : m_inventorylists)
  2250. inventorylist_it->drop();
  2251. for (auto &checkbox_it : m_checkboxes)
  2252. checkbox_it.second->drop();
  2253. for (auto &scrollbar_it : m_scrollbars)
  2254. scrollbar_it.second->drop();
  2255. for (auto &tooltip_rect_it : m_tooltip_rects)
  2256. tooltip_rect_it.first->drop();
  2257. for (auto &clickthrough_it : m_clickthrough_elements)
  2258. clickthrough_it->drop();
  2259. for (auto &scroll_container_it : m_scroll_containers)
  2260. scroll_container_it.second->drop();
  2261. }
  2262. void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
  2263. {
  2264. //some prechecks
  2265. if (element.empty())
  2266. return;
  2267. if (parseVersionDirect(element))
  2268. return;
  2269. size_t pos = element.find('[');
  2270. if (pos == std::string::npos)
  2271. return;
  2272. std::string type = trim(element.substr(0, pos));
  2273. std::string description = element.substr(pos+1);
  2274. if (type == "container") {
  2275. parseContainer(data, description);
  2276. return;
  2277. }
  2278. if (type == "container_end") {
  2279. parseContainerEnd(data);
  2280. return;
  2281. }
  2282. if (type == "list") {
  2283. parseList(data, description);
  2284. return;
  2285. }
  2286. if (type == "listring") {
  2287. parseListRing(data, description);
  2288. return;
  2289. }
  2290. if (type == "checkbox") {
  2291. parseCheckbox(data, description);
  2292. return;
  2293. }
  2294. if (type == "image") {
  2295. parseImage(data, description);
  2296. return;
  2297. }
  2298. if (type == "animated_image") {
  2299. parseAnimatedImage(data, description);
  2300. return;
  2301. }
  2302. if (type == "item_image") {
  2303. parseItemImage(data, description);
  2304. return;
  2305. }
  2306. if (type == "button" || type == "button_exit") {
  2307. parseButton(data, description, type);
  2308. return;
  2309. }
  2310. if (type == "background" || type == "background9") {
  2311. parseBackground(data, description);
  2312. return;
  2313. }
  2314. if (type == "tableoptions"){
  2315. parseTableOptions(data,description);
  2316. return;
  2317. }
  2318. if (type == "tablecolumns"){
  2319. parseTableColumns(data,description);
  2320. return;
  2321. }
  2322. if (type == "table"){
  2323. parseTable(data,description);
  2324. return;
  2325. }
  2326. if (type == "textlist"){
  2327. parseTextList(data,description);
  2328. return;
  2329. }
  2330. if (type == "dropdown"){
  2331. parseDropDown(data,description);
  2332. return;
  2333. }
  2334. if (type == "field_enter_after_edit") {
  2335. parseFieldEnterAfterEdit(data, description);
  2336. return;
  2337. }
  2338. if (type == "field_close_on_enter") {
  2339. parseFieldCloseOnEnter(data, description);
  2340. return;
  2341. }
  2342. if (type == "pwdfield") {
  2343. parsePwdField(data,description);
  2344. return;
  2345. }
  2346. if ((type == "field") || (type == "textarea")){
  2347. parseField(data,description,type);
  2348. return;
  2349. }
  2350. if (type == "hypertext") {
  2351. parseHyperText(data,description);
  2352. return;
  2353. }
  2354. if (type == "label") {
  2355. parseLabel(data,description);
  2356. return;
  2357. }
  2358. if (type == "vertlabel") {
  2359. parseVertLabel(data,description);
  2360. return;
  2361. }
  2362. if (type == "item_image_button") {
  2363. parseItemImageButton(data,description);
  2364. return;
  2365. }
  2366. if ((type == "image_button") || (type == "image_button_exit")) {
  2367. parseImageButton(data,description,type);
  2368. return;
  2369. }
  2370. if (type == "tabheader") {
  2371. parseTabHeader(data,description);
  2372. return;
  2373. }
  2374. if (type == "box") {
  2375. parseBox(data,description);
  2376. return;
  2377. }
  2378. if (type == "bgcolor") {
  2379. parseBackgroundColor(data,description);
  2380. return;
  2381. }
  2382. if (type == "listcolors") {
  2383. parseListColors(data,description);
  2384. return;
  2385. }
  2386. if (type == "tooltip") {
  2387. parseTooltip(data,description);
  2388. return;
  2389. }
  2390. if (type == "scrollbar") {
  2391. parseScrollBar(data, description);
  2392. return;
  2393. }
  2394. if (type == "real_coordinates") {
  2395. data->real_coordinates = is_yes(description);
  2396. return;
  2397. }
  2398. if (type == "style") {
  2399. parseStyle(data, description, false);
  2400. return;
  2401. }
  2402. if (type == "style_type") {
  2403. parseStyle(data, description, true);
  2404. return;
  2405. }
  2406. if (type == "scrollbaroptions") {
  2407. parseScrollBarOptions(data, description);
  2408. return;
  2409. }
  2410. if (type == "scroll_container") {
  2411. parseScrollContainer(data, description);
  2412. return;
  2413. }
  2414. if (type == "scroll_container_end") {
  2415. parseScrollContainerEnd(data);
  2416. return;
  2417. }
  2418. if (type == "set_focus") {
  2419. parseSetFocus(description);
  2420. return;
  2421. }
  2422. if (type == "model") {
  2423. parseModel(data, description);
  2424. return;
  2425. }
  2426. // Ignore others
  2427. infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
  2428. << std::endl;
  2429. }
  2430. void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
  2431. {
  2432. // Useless to regenerate without a screensize
  2433. if ((screensize.X <= 0) || (screensize.Y <= 0)) {
  2434. return;
  2435. }
  2436. parserData mydata;
  2437. // Preserve stuff only on same form, not on a new form.
  2438. if (m_text_dst->m_formname == m_last_formname) {
  2439. // Preserve tables/textlists
  2440. for (auto &m_table : m_tables) {
  2441. std::string tablename = m_table.first.fname;
  2442. GUITable *table = m_table.second;
  2443. mydata.table_dyndata[tablename] = table->getDynamicData();
  2444. }
  2445. // Preserve focus
  2446. gui::IGUIElement *focused_element = Environment->getFocus();
  2447. if (focused_element && focused_element->getParent() == this) {
  2448. s32 focused_id = focused_element->getID();
  2449. if (focused_id > 257) {
  2450. for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
  2451. if (field.fid == focused_id) {
  2452. m_focused_element = field.fname;
  2453. break;
  2454. }
  2455. }
  2456. }
  2457. }
  2458. } else {
  2459. // Don't keep old focus value
  2460. m_focused_element = std::nullopt;
  2461. }
  2462. removeAll();
  2463. mydata.size = v2s32(100, 100);
  2464. mydata.screensize = screensize;
  2465. mydata.offset = v2f32(0.5f, 0.5f);
  2466. mydata.anchor = v2f32(0.5f, 0.5f);
  2467. mydata.padding = v2f32(0.05f, 0.05f);
  2468. mydata.simple_field_count = 0;
  2469. // Base position of contents of form
  2470. mydata.basepos = getBasePos();
  2471. // the parent for the parsed elements
  2472. mydata.current_parent = this;
  2473. m_inventorylists.clear();
  2474. m_tables.clear();
  2475. m_checkboxes.clear();
  2476. m_scrollbars.clear();
  2477. m_fields.clear();
  2478. m_tooltips.clear();
  2479. m_tooltip_rects.clear();
  2480. m_inventory_rings.clear();
  2481. m_dropdowns.clear();
  2482. m_scroll_containers.clear();
  2483. theme_by_name.clear();
  2484. theme_by_type.clear();
  2485. m_clickthrough_elements.clear();
  2486. field_enter_after_edit.clear();
  2487. field_close_on_enter.clear();
  2488. m_dropdown_index_event.clear();
  2489. m_bgnonfullscreen = true;
  2490. m_bgfullscreen = false;
  2491. m_formspec_version = 1;
  2492. m_bgcolor = video::SColor(140, 0, 0, 0);
  2493. m_tabheader_upper_edge = 0;
  2494. {
  2495. v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color");
  2496. m_fullscreen_bgcolor = video::SColor(
  2497. (u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")),
  2498. clamp_u8(myround(formspec_bgcolor.X)),
  2499. clamp_u8(myround(formspec_bgcolor.Y)),
  2500. clamp_u8(myround(formspec_bgcolor.Z))
  2501. );
  2502. }
  2503. m_default_tooltip_bgcolor = video::SColor(255,110,130,60);
  2504. m_default_tooltip_color = video::SColor(255,255,255,255);
  2505. // Add tooltip
  2506. {
  2507. assert(!m_tooltip_element);
  2508. // Note: parent != this so that the tooltip isn't clipped by the menu rectangle
  2509. m_tooltip_element = gui::StaticText::add(Environment, L"",
  2510. core::rect<s32>(0, 0, 110, 18));
  2511. m_tooltip_element->enableOverrideColor(true);
  2512. m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
  2513. m_tooltip_element->setDrawBackground(true);
  2514. m_tooltip_element->setDrawBorder(true);
  2515. m_tooltip_element->setOverrideColor(m_default_tooltip_color);
  2516. m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
  2517. m_tooltip_element->setWordWrap(false);
  2518. //we're not parent so no autograb for this one!
  2519. m_tooltip_element->grab();
  2520. }
  2521. std::vector<std::string> elements = split(m_formspec_string,']');
  2522. unsigned int i = 0;
  2523. /* try to read version from first element only */
  2524. if (!elements.empty()) {
  2525. if (parseVersionDirect(elements[0])) {
  2526. i++;
  2527. }
  2528. }
  2529. /* we need size first in order to calculate image scale */
  2530. mydata.explicit_size = false;
  2531. for (; i< elements.size(); i++) {
  2532. if (!parseSizeDirect(&mydata, elements[i])) {
  2533. break;
  2534. }
  2535. }
  2536. /* "position" element is always after "size" element if it used */
  2537. for (; i< elements.size(); i++) {
  2538. if (!parsePositionDirect(&mydata, elements[i])) {
  2539. break;
  2540. }
  2541. }
  2542. /* "anchor" element is always after "position" (or "size" element) if it used */
  2543. for (; i< elements.size(); i++) {
  2544. if (!parseAnchorDirect(&mydata, elements[i])) {
  2545. break;
  2546. }
  2547. }
  2548. /* "padding" element is always after "anchor" and previous if it is used */
  2549. for (; i < elements.size(); i++) {
  2550. if (!parsePaddingDirect(&mydata, elements[i])) {
  2551. break;
  2552. }
  2553. }
  2554. /* "no_prepend" element is always after "padding" and previous if it used */
  2555. bool enable_prepends = true;
  2556. for (; i < elements.size(); i++) {
  2557. if (elements[i].empty())
  2558. break;
  2559. std::vector<std::string> parts = split(elements[i], '[');
  2560. if (trim(parts[0]) == "no_prepend")
  2561. enable_prepends = false;
  2562. else
  2563. break;
  2564. }
  2565. /* Copy of the "real_coordinates" element for after the form size. */
  2566. mydata.real_coordinates = m_formspec_version >= 2;
  2567. for (; i < elements.size(); i++) {
  2568. std::vector<std::string> parts = split(elements[i], '[');
  2569. auto name = trim(parts[0]);
  2570. if (name != "real_coordinates" || parts.size() != 2)
  2571. break; // Invalid format
  2572. mydata.real_coordinates = is_yes(trim(parts[1]));
  2573. }
  2574. if (mydata.explicit_size) {
  2575. // compute scaling for specified form size
  2576. if (m_lock) {
  2577. v2u32 current_screensize = RenderingEngine::get_video_driver()->getScreenSize();
  2578. v2u32 delta = current_screensize - m_lockscreensize;
  2579. if (current_screensize.Y > m_lockscreensize.Y)
  2580. delta.Y /= 2;
  2581. else
  2582. delta.Y = 0;
  2583. if (current_screensize.X > m_lockscreensize.X)
  2584. delta.X /= 2;
  2585. else
  2586. delta.X = 0;
  2587. offset = v2s32(delta.X,delta.Y);
  2588. mydata.screensize = m_lockscreensize;
  2589. } else {
  2590. offset = v2s32(0,0);
  2591. }
  2592. const double gui_scaling = g_settings->getFloat("gui_scaling", 0.5f, 42.0f);
  2593. const double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
  2594. double use_imgsize;
  2595. if (m_lock) {
  2596. // In fixed-size mode, inventory image size
  2597. // is 0.53 inch multiplied by the gui_scaling
  2598. // config parameter. This magic size is chosen
  2599. // to make the main menu (15.5 inventory images
  2600. // wide, including border) just fit into the
  2601. // default window (800 pixels wide) at 96 DPI
  2602. // and default scaling (1.00).
  2603. use_imgsize = 0.5555 * screen_dpi * gui_scaling;
  2604. } else {
  2605. // Variables for the maximum imgsize that can fit in the screen.
  2606. double fitx_imgsize;
  2607. double fity_imgsize;
  2608. v2f padded_screensize(
  2609. mydata.screensize.X * (1.0f - mydata.padding.X * 2.0f),
  2610. mydata.screensize.Y * (1.0f - mydata.padding.Y * 2.0f)
  2611. );
  2612. if (mydata.real_coordinates) {
  2613. fitx_imgsize = padded_screensize.X / mydata.invsize.X;
  2614. fity_imgsize = padded_screensize.Y / mydata.invsize.Y;
  2615. } else {
  2616. // The maximum imgsize in the old coordinate system also needs to
  2617. // factor in padding and spacing along with 0.1 inventory slot spare
  2618. // and help text space, hence the magic numbers.
  2619. fitx_imgsize = padded_screensize.X /
  2620. ((5.0 / 4.0) * (0.5 + mydata.invsize.X));
  2621. fity_imgsize = padded_screensize.Y /
  2622. ((15.0 / 13.0) * (0.85 + mydata.invsize.Y));
  2623. }
  2624. s32 min_screen_dim = std::min(padded_screensize.X, padded_screensize.Y);
  2625. #ifdef HAVE_TOUCHSCREENGUI
  2626. // In Android, the preferred imgsize should be larger to accommodate the
  2627. // smaller screensize.
  2628. double prefer_imgsize = min_screen_dim / 10 * gui_scaling;
  2629. #else
  2630. // Desktop computers have more space, so try to fit 15 coordinates.
  2631. double prefer_imgsize = min_screen_dim / 15 * gui_scaling;
  2632. #endif
  2633. // Try to use the preferred imgsize, but if that's bigger than the maximum
  2634. // size, use the maximum size.
  2635. use_imgsize = std::min(prefer_imgsize,
  2636. std::min(fitx_imgsize, fity_imgsize));
  2637. }
  2638. // Everything else is scaled in proportion to the
  2639. // inventory image size. The inventory slot spacing
  2640. // is 5/4 image size horizontally and 15/13 image size
  2641. // vertically. The padding around the form (incorporating
  2642. // the border of the outer inventory slots) is 3/8
  2643. // image size. Font height (baseline to baseline)
  2644. // is 2/5 vertical inventory slot spacing, and button
  2645. // half-height is 7/8 of font height.
  2646. imgsize = v2s32(use_imgsize, use_imgsize);
  2647. spacing = v2f32(use_imgsize*5.0/4, use_imgsize*15.0/13);
  2648. padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8);
  2649. m_btn_height = use_imgsize*15.0/13 * 0.35;
  2650. m_font = g_fontengine->getFont();
  2651. if (mydata.real_coordinates) {
  2652. mydata.size = v2s32(
  2653. mydata.invsize.X*imgsize.X,
  2654. mydata.invsize.Y*imgsize.Y
  2655. );
  2656. } else {
  2657. mydata.size = v2s32(
  2658. padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
  2659. padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
  2660. );
  2661. }
  2662. DesiredRect = mydata.rect = core::rect<s32>(
  2663. (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X,
  2664. (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y,
  2665. (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * (f32)mydata.size.X) + offset.X,
  2666. (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * (f32)mydata.size.Y) + offset.Y
  2667. );
  2668. } else {
  2669. // Non-size[] form must consist only of text fields and
  2670. // implicit "Proceed" button. Use default font, and
  2671. // temporary form size which will be recalculated below.
  2672. m_font = g_fontengine->getFont();
  2673. m_btn_height = font_line_height(m_font) * 0.875;
  2674. DesiredRect = core::rect<s32>(
  2675. (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * 580.0),
  2676. (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * 300.0),
  2677. (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * 580.0),
  2678. (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * 300.0)
  2679. );
  2680. }
  2681. recalculateAbsolutePosition(false);
  2682. mydata.basepos = getBasePos();
  2683. m_tooltip_element->setOverrideFont(m_font);
  2684. gui::IGUISkin *skin = Environment->getSkin();
  2685. sanity_check(skin);
  2686. gui::IGUIFont *old_font = skin->getFont();
  2687. skin->setFont(m_font);
  2688. // Add a new element that will hold all the background elements as its children.
  2689. // Because it is the first added element, all backgrounds will be behind all
  2690. // the other elements.
  2691. // (We use an arbitrarily big rect. The actual size is determined later by
  2692. // clipping to `this`.)
  2693. core::rect<s32> background_parent_rect(0, 0, 100000, 100000);
  2694. mydata.background_parent.reset(new gui::IGUIElement(EGUIET_ELEMENT, Environment,
  2695. this, -1, background_parent_rect));
  2696. pos_offset = v2f32();
  2697. // used for formspec versions < 3
  2698. std::list<IGUIElement *>::iterator legacy_sort_start = std::prev(Children.end()); // last element
  2699. if (enable_prepends) {
  2700. // Backup the coordinates so that prepends can use the coordinates of choice.
  2701. bool rc_backup = mydata.real_coordinates;
  2702. u16 version_backup = m_formspec_version;
  2703. mydata.real_coordinates = false; // Old coordinates by default.
  2704. std::vector<std::string> prepend_elements = split(m_formspec_prepend, ']');
  2705. for (const auto &element : prepend_elements)
  2706. parseElement(&mydata, element);
  2707. // legacy sorting for formspec versions < 3
  2708. if (m_formspec_version >= 3)
  2709. // prepends do not need to be reordered
  2710. legacy_sort_start = std::prev(Children.end()); // last element
  2711. else if (version_backup >= 3)
  2712. // only prepends elements have to be reordered
  2713. legacySortElements(legacy_sort_start);
  2714. m_formspec_version = version_backup;
  2715. mydata.real_coordinates = rc_backup; // Restore coordinates
  2716. }
  2717. for (; i< elements.size(); i++) {
  2718. parseElement(&mydata, elements[i]);
  2719. }
  2720. if (mydata.current_parent != this) {
  2721. errorstream << "Invalid formspec string: scroll_container was never closed!"
  2722. << std::endl;
  2723. } else if (!container_stack.empty()) {
  2724. errorstream << "Invalid formspec string: container was never closed!"
  2725. << std::endl;
  2726. }
  2727. // get the scrollbar elements for scroll_containers
  2728. for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers) {
  2729. for (const std::pair<FieldSpec, GUIScrollBar *> &b : m_scrollbars) {
  2730. if (c.first == b.first.fname) {
  2731. c.second->setScrollBar(b.second);
  2732. break;
  2733. }
  2734. }
  2735. }
  2736. // If there are fields without explicit size[], add a "Proceed"
  2737. // button and adjust size to fit all the fields.
  2738. if (mydata.simple_field_count > 0 && !mydata.explicit_size) {
  2739. mydata.rect = core::rect<s32>(
  2740. mydata.screensize.X / 2 - 580 / 2,
  2741. mydata.screensize.Y / 2 - 300 / 2,
  2742. mydata.screensize.X / 2 + 580 / 2,
  2743. mydata.screensize.Y / 2 + 240 / 2 + mydata.simple_field_count * 60
  2744. );
  2745. DesiredRect = mydata.rect;
  2746. recalculateAbsolutePosition(false);
  2747. mydata.basepos = getBasePos();
  2748. {
  2749. v2s32 pos = mydata.basepos;
  2750. pos.Y = (mydata.simple_field_count + 2) * 60;
  2751. v2s32 size = DesiredRect.getSize();
  2752. mydata.rect = core::rect<s32>(
  2753. size.X / 2 - 70, pos.Y,
  2754. size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2
  2755. );
  2756. GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257,
  2757. wstrgettext("Proceed").c_str());
  2758. }
  2759. }
  2760. // Set initial focus if parser didn't set it
  2761. gui::IGUIElement *focused_element = Environment->getFocus();
  2762. if (!focused_element
  2763. || !isMyChild(focused_element)
  2764. || focused_element->getType() == gui::EGUIET_TAB_CONTROL)
  2765. setInitialFocus();
  2766. skin->setFont(old_font);
  2767. // legacy sorting
  2768. if (m_formspec_version < 3)
  2769. legacySortElements(legacy_sort_start);
  2770. // Formname and regeneration setting
  2771. if (!m_is_form_regenerated) {
  2772. // Only set previous form name if we purposefully showed a new formspec
  2773. m_last_formname = m_text_dst->m_formname;
  2774. m_is_form_regenerated = true;
  2775. }
  2776. }
  2777. void GUIFormSpecMenu::legacySortElements(std::list<IGUIElement *>::iterator from)
  2778. {
  2779. /*
  2780. Draw order for formspec_version <= 2:
  2781. -3 bgcolor
  2782. -2 background
  2783. -1 box
  2784. 0 All other elements
  2785. 1 image
  2786. 2 item_image, item_image_button
  2787. 3 list
  2788. 4 label
  2789. */
  2790. if (from == Children.end())
  2791. from = Children.begin();
  2792. else
  2793. ++from;
  2794. std::list<IGUIElement *>::iterator to = Children.end();
  2795. // 1: Copy into a sortable container
  2796. std::vector<IGUIElement *> elements(from, to);
  2797. // 2: Sort the container
  2798. std::stable_sort(elements.begin(), elements.end(),
  2799. [this] (const IGUIElement *a, const IGUIElement *b) -> bool {
  2800. // TODO: getSpecByID is a linear search. It should made O(1), or cached here.
  2801. const FieldSpec *spec_a = getSpecByID(a->getID());
  2802. const FieldSpec *spec_b = getSpecByID(b->getID());
  2803. return spec_a && spec_b &&
  2804. spec_a->priority < spec_b->priority;
  2805. });
  2806. // 3: Re-assign the pointers
  2807. reorderChildren(from, to, elements);
  2808. }
  2809. #ifdef __ANDROID__
  2810. void GUIFormSpecMenu::getAndroidUIInput()
  2811. {
  2812. porting::AndroidDialogState dialogState = getAndroidUIInputState();
  2813. if (dialogState == porting::DIALOG_SHOWN) {
  2814. return;
  2815. } else if (dialogState == porting::DIALOG_CANCELED) {
  2816. m_jni_field_name.clear();
  2817. return;
  2818. }
  2819. porting::AndroidDialogType dialog_type = porting::getLastInputDialogType();
  2820. std::string fieldname = m_jni_field_name;
  2821. m_jni_field_name.clear();
  2822. for (const FieldSpec &field : m_fields) {
  2823. if (field.fname != fieldname)
  2824. continue; // Iterate until found
  2825. IGUIElement *element = getElementFromId(field.fid, true);
  2826. if (!element)
  2827. return;
  2828. auto element_type = element->getType();
  2829. if (dialog_type == porting::TEXT_INPUT && element_type == irr::gui::EGUIET_EDIT_BOX) {
  2830. gui::IGUIEditBox *editbox = (gui::IGUIEditBox *)element;
  2831. std::string text = porting::getInputDialogMessage();
  2832. editbox->setText(utf8_to_wide(text).c_str());
  2833. bool enter_after_edit = false;
  2834. auto iter = field_enter_after_edit.find(fieldname);
  2835. if (iter != field_enter_after_edit.end()) {
  2836. enter_after_edit = iter->second;
  2837. }
  2838. if (enter_after_edit && editbox->getParent()) {
  2839. SEvent enter;
  2840. enter.EventType = EET_GUI_EVENT;
  2841. enter.GUIEvent.Caller = editbox;
  2842. enter.GUIEvent.Element = nullptr;
  2843. enter.GUIEvent.EventType = gui::EGET_EDITBOX_ENTER;
  2844. editbox->getParent()->OnEvent(enter);
  2845. }
  2846. } else if (dialog_type == porting::SELECTION_INPUT &&
  2847. element_type == irr::gui::EGUIET_COMBO_BOX) {
  2848. auto dropdown = (gui::IGUIComboBox *) element;
  2849. int selected = porting::getInputDialogSelection();
  2850. dropdown->setAndSendSelected(selected);
  2851. }
  2852. return; // Early-return after found
  2853. }
  2854. }
  2855. #endif
  2856. GUIInventoryList::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
  2857. {
  2858. for (const GUIInventoryList *e : m_inventorylists) {
  2859. s32 item_index = e->getItemIndexAtPos(p);
  2860. if (item_index != -1)
  2861. return GUIInventoryList::ItemSpec(e->getInventoryloc(), e->getListname(),
  2862. item_index, e->getSlotSize());
  2863. }
  2864. return GUIInventoryList::ItemSpec(InventoryLocation(), "", -1, {0,0});
  2865. }
  2866. void GUIFormSpecMenu::drawSelectedItem()
  2867. {
  2868. video::IVideoDriver* driver = Environment->getVideoDriver();
  2869. if (!m_selected_item) {
  2870. // reset rotation time
  2871. drawItemStack(driver, m_font, ItemStack(),
  2872. core::rect<s32>(v2s32(0, 0), v2s32(0, 0)), NULL,
  2873. m_client, IT_ROT_DRAGGED);
  2874. return;
  2875. }
  2876. Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
  2877. sanity_check(inv);
  2878. InventoryList *list = inv->getList(m_selected_item->listname);
  2879. sanity_check(list);
  2880. ItemStack stack = list->getItem(m_selected_item->i);
  2881. stack.count = m_selected_amount;
  2882. v2s32 slotsize = m_selected_item->slotsize;
  2883. core::rect<s32> imgrect(0, 0, slotsize.X, slotsize.Y);
  2884. core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
  2885. rect.constrainTo(driver->getViewPort());
  2886. drawItemStack(driver, m_font, stack, rect, NULL, m_client, IT_ROT_DRAGGED);
  2887. }
  2888. void GUIFormSpecMenu::drawMenu()
  2889. {
  2890. if (m_form_src) {
  2891. const std::string &newform = m_form_src->getForm();
  2892. if (newform != m_formspec_string) {
  2893. m_formspec_string = newform;
  2894. m_is_form_regenerated = false;
  2895. regenerateGui(m_screensize_old);
  2896. }
  2897. }
  2898. gui::IGUISkin* skin = Environment->getSkin();
  2899. sanity_check(skin != NULL);
  2900. gui::IGUIFont *old_font = skin->getFont();
  2901. skin->setFont(m_font);
  2902. m_hovered_item_tooltips.clear();
  2903. updateSelectedItem();
  2904. video::IVideoDriver* driver = Environment->getVideoDriver();
  2905. /*
  2906. Draw background color
  2907. */
  2908. v2u32 screenSize = driver->getScreenSize();
  2909. core::rect<s32> allbg(0, 0, screenSize.X, screenSize.Y);
  2910. if (m_bgfullscreen)
  2911. driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg);
  2912. if (m_bgnonfullscreen)
  2913. driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);
  2914. /*
  2915. Draw rect_mode tooltip
  2916. */
  2917. m_tooltip_element->setVisible(false);
  2918. for (const auto &pair : m_tooltip_rects) {
  2919. const core::rect<s32> &rect = pair.first->getAbsoluteClippingRect();
  2920. if (rect.getArea() > 0 && rect.isPointInside(m_pointer)) {
  2921. const std::wstring &text = pair.second.tooltip;
  2922. if (!text.empty()) {
  2923. showTooltip(text, pair.second.color, pair.second.bgcolor);
  2924. break;
  2925. }
  2926. }
  2927. }
  2928. // Some elements are only visible while being drawn
  2929. for (gui::IGUIElement *e : m_clickthrough_elements)
  2930. e->setVisible(true);
  2931. /*
  2932. This is where all the drawing happens.
  2933. */
  2934. for (auto child : Children)
  2935. if (child->isNotClipped() ||
  2936. AbsoluteClippingRect.isRectCollided(
  2937. child->getAbsolutePosition()))
  2938. child->draw();
  2939. for (gui::IGUIElement *e : m_clickthrough_elements)
  2940. e->setVisible(false);
  2941. // Draw hovered item tooltips
  2942. for (const std::string &tooltip : m_hovered_item_tooltips) {
  2943. showTooltip(utf8_to_wide(tooltip), m_default_tooltip_color,
  2944. m_default_tooltip_bgcolor);
  2945. }
  2946. if (m_hovered_item_tooltips.empty()) {
  2947. // reset rotation time
  2948. drawItemStack(driver, m_font, ItemStack(),
  2949. core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
  2950. NULL, m_client, IT_ROT_HOVERED);
  2951. }
  2952. /*
  2953. Draw fields/buttons tooltips and update the mouse cursor
  2954. */
  2955. gui::IGUIElement *hovered =
  2956. Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
  2957. gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()->
  2958. getCursorControl();
  2959. gui::ECURSOR_ICON current_cursor_icon = gui::ECI_NORMAL;
  2960. if (cursor_control)
  2961. current_cursor_icon = cursor_control->getActiveIcon();
  2962. bool hovered_element_found = false;
  2963. if (hovered) {
  2964. if (m_show_debug) {
  2965. core::rect<s32> rect = hovered->getAbsoluteClippingRect();
  2966. driver->draw2DRectangle(0x22FFFF00, rect, &rect);
  2967. }
  2968. // find the formspec-element of the hovered IGUIElement (a parent)
  2969. s32 id;
  2970. for (gui::IGUIElement *hovered_fselem = hovered; hovered_fselem;
  2971. hovered_fselem = hovered_fselem->getParent()) {
  2972. id = hovered_fselem->getID();
  2973. if (id != -1)
  2974. break;
  2975. }
  2976. u64 delta = 0;
  2977. if (id == -1) {
  2978. m_old_tooltip_id = id;
  2979. } else {
  2980. if (id == m_old_tooltip_id) {
  2981. delta = porting::getDeltaMs(m_hovered_time, porting::getTimeMs());
  2982. } else {
  2983. m_hovered_time = porting::getTimeMs();
  2984. m_old_tooltip_id = id;
  2985. }
  2986. }
  2987. // Find and update the current tooltip and cursor icon
  2988. if (id != -1) {
  2989. for (const FieldSpec &field : m_fields) {
  2990. if (field.fid != id)
  2991. continue;
  2992. if (delta >= m_tooltip_show_delay) {
  2993. const std::wstring &text = m_tooltips[field.fname].tooltip;
  2994. if (!text.empty())
  2995. showTooltip(text, m_tooltips[field.fname].color,
  2996. m_tooltips[field.fname].bgcolor);
  2997. }
  2998. if (cursor_control &&
  2999. field.ftype != f_HyperText && // Handled directly in guiHyperText
  3000. current_cursor_icon != field.fcursor_icon)
  3001. cursor_control->setActiveIcon(field.fcursor_icon);
  3002. hovered_element_found = true;
  3003. break;
  3004. }
  3005. }
  3006. }
  3007. if (!hovered_element_found) {
  3008. // no element is hovered
  3009. if (cursor_control && current_cursor_icon != ECI_NORMAL)
  3010. cursor_control->setActiveIcon(ECI_NORMAL);
  3011. }
  3012. m_tooltip_element->draw();
  3013. /*
  3014. Draw dragged item stack
  3015. */
  3016. drawSelectedItem();
  3017. skin->setFont(old_font);
  3018. }
  3019. void GUIFormSpecMenu::showTooltip(const std::wstring &text,
  3020. const irr::video::SColor &color, const irr::video::SColor &bgcolor)
  3021. {
  3022. EnrichedString ntext(text);
  3023. ntext.setDefaultColor(color);
  3024. if (!ntext.hasBackground())
  3025. ntext.setBackground(bgcolor);
  3026. setStaticText(m_tooltip_element, ntext);
  3027. // Tooltip size and offset
  3028. s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
  3029. s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
  3030. v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
  3031. int tooltip_offset_x = m_btn_height;
  3032. int tooltip_offset_y = m_btn_height;
  3033. if (m_pointer_type == PointerType::Touch) {
  3034. tooltip_offset_x *= 3;
  3035. tooltip_offset_y = 0;
  3036. if (m_pointer.X > (s32)screenSize.X / 2)
  3037. tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
  3038. }
  3039. // Calculate and set the tooltip position
  3040. s32 tooltip_x = m_pointer.X + tooltip_offset_x;
  3041. s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
  3042. if (tooltip_x + tooltip_width > (s32)screenSize.X)
  3043. tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height;
  3044. if (tooltip_y + tooltip_height > (s32)screenSize.Y)
  3045. tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
  3046. m_tooltip_element->setRelativePosition(
  3047. core::rect<s32>(
  3048. core::position2d<s32>(tooltip_x, tooltip_y),
  3049. core::dimension2d<s32>(tooltip_width, tooltip_height)
  3050. )
  3051. );
  3052. // Display the tooltip
  3053. m_tooltip_element->setVisible(true);
  3054. bringToFront(m_tooltip_element);
  3055. }
  3056. void GUIFormSpecMenu::updateSelectedItem()
  3057. {
  3058. // Don't update when dragging an item
  3059. if (m_selected_item && (m_selected_dragging || m_left_dragging))
  3060. return;
  3061. verifySelectedItem();
  3062. // If craftresult is not empty and nothing else is selected,
  3063. // try to move it somewhere or select it now
  3064. if (!m_selected_item || m_shift_move_after_craft) {
  3065. for (const GUIInventoryList *e : m_inventorylists) {
  3066. if (e->getListname() != "craftpreview")
  3067. continue;
  3068. Inventory *inv = m_invmgr->getInventory(e->getInventoryloc());
  3069. if (!inv)
  3070. continue;
  3071. InventoryList *list = inv->getList("craftresult");
  3072. if (!list || list->getSize() == 0)
  3073. continue;
  3074. const ItemStack &item = list->getItem(0);
  3075. if (item.empty())
  3076. continue;
  3077. GUIInventoryList::ItemSpec s = GUIInventoryList::ItemSpec();
  3078. s.inventoryloc = e->getInventoryloc();
  3079. s.listname = "craftresult";
  3080. s.i = 0;
  3081. s.slotsize = e->getSlotSize();
  3082. if (m_shift_move_after_craft) {
  3083. // Try to shift-move the crafted item to the next list in the ring after the "craft" list.
  3084. // We don't look for the "craftresult" list because it's a hidden list,
  3085. // and shouldn't be part of the formspec, thus it won't be in the list ring.
  3086. do {
  3087. s16 r = getNextInventoryRing(s.inventoryloc, "craft");
  3088. if (r < 0) // Not found
  3089. break;
  3090. const ListRingSpec &to_ring = m_inventory_rings[r];
  3091. Inventory *inv_to = m_invmgr->getInventory(to_ring.inventoryloc);
  3092. if (!inv_to)
  3093. break;
  3094. InventoryList *list_to = inv_to->getList(to_ring.listname);
  3095. if (!list_to)
  3096. break;
  3097. IMoveAction *a = new IMoveAction();
  3098. a->count = item.count;
  3099. a->from_inv = s.inventoryloc;
  3100. a->from_list = s.listname;
  3101. a->from_i = s.i;
  3102. a->to_inv = to_ring.inventoryloc;
  3103. a->to_list = to_ring.listname;
  3104. a->move_somewhere = true;
  3105. m_invmgr->inventoryAction(a);
  3106. } while (0);
  3107. m_shift_move_after_craft = false;
  3108. } else {
  3109. // Grab selected item from the crafting result list
  3110. m_selected_item = new GUIInventoryList::ItemSpec(s);
  3111. m_selected_amount = item.count;
  3112. m_selected_dragging = false;
  3113. }
  3114. break;
  3115. }
  3116. }
  3117. // If craftresult is selected, keep the whole stack selected
  3118. if (m_selected_item && m_selected_item->listname == "craftresult")
  3119. m_selected_amount = verifySelectedItem().count;
  3120. }
  3121. ItemStack GUIFormSpecMenu::verifySelectedItem()
  3122. {
  3123. // If the selected stack has become empty for some reason, deselect it.
  3124. // If the selected stack has become inaccessible, deselect it.
  3125. // If the selected stack has become smaller, adjust m_selected_amount.
  3126. // Return the selected stack.
  3127. if (m_selected_item) {
  3128. if (m_selected_item->isValid()) {
  3129. Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
  3130. if (inv) {
  3131. InventoryList *list = inv->getList(m_selected_item->listname);
  3132. if (list && (u32) m_selected_item->i < list->getSize()) {
  3133. ItemStack stack = list->getItem(m_selected_item->i);
  3134. if (!m_selected_swap.empty()) {
  3135. if (m_selected_swap.name == stack.name &&
  3136. m_selected_swap.count == stack.count)
  3137. m_selected_swap.clear();
  3138. } else {
  3139. m_selected_amount = std::min(m_selected_amount, stack.count);
  3140. }
  3141. if (!stack.empty())
  3142. return stack;
  3143. }
  3144. }
  3145. }
  3146. // selection was not valid
  3147. delete m_selected_item;
  3148. m_selected_item = nullptr;
  3149. m_selected_amount = 0;
  3150. m_selected_dragging = false;
  3151. }
  3152. return ItemStack();
  3153. }
  3154. s16 GUIFormSpecMenu::getNextInventoryRing(
  3155. const InventoryLocation &inventoryloc, const std::string &listname)
  3156. {
  3157. u16 rings = m_inventory_rings.size();
  3158. if (rings < 2)
  3159. return -1;
  3160. // Look for the source ring
  3161. s16 index = -1;
  3162. for (u16 i = 0; i < rings; i++) {
  3163. ListRingSpec &lr = m_inventory_rings[i];
  3164. if (lr.inventoryloc == inventoryloc && lr.listname == listname) {
  3165. // Set the index to the next ring
  3166. index = (i + 1) % rings;
  3167. break;
  3168. }
  3169. }
  3170. return index;
  3171. }
  3172. void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode)
  3173. {
  3174. if(m_text_dst)
  3175. {
  3176. StringMap fields;
  3177. if (quitmode == quit_mode_accept) {
  3178. fields["quit"] = "true";
  3179. }
  3180. if (quitmode == quit_mode_cancel) {
  3181. fields["quit"] = "true";
  3182. m_text_dst->gotText(fields);
  3183. return;
  3184. }
  3185. if (current_keys_pending.key_down) {
  3186. fields["key_down"] = "true";
  3187. current_keys_pending.key_down = false;
  3188. }
  3189. if (current_keys_pending.key_up) {
  3190. fields["key_up"] = "true";
  3191. current_keys_pending.key_up = false;
  3192. }
  3193. if (current_keys_pending.key_enter) {
  3194. fields["key_enter"] = "true";
  3195. current_keys_pending.key_enter = false;
  3196. }
  3197. if (!current_field_enter_pending.empty()) {
  3198. fields["key_enter_field"] = current_field_enter_pending;
  3199. current_field_enter_pending.clear();
  3200. }
  3201. if (current_keys_pending.key_escape) {
  3202. fields["key_escape"] = "true";
  3203. current_keys_pending.key_escape = false;
  3204. }
  3205. for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
  3206. if (s.send) {
  3207. std::string name = s.fname;
  3208. if (s.ftype == f_Button) {
  3209. fields[name] = wide_to_utf8(s.flabel);
  3210. } else if (s.ftype == f_Table) {
  3211. GUITable *table = getTable(s.fname);
  3212. if (table) {
  3213. fields[name] = table->checkEvent();
  3214. }
  3215. } else if (s.ftype == f_DropDown) {
  3216. // No dynamic cast possible due to some distributions shipped
  3217. // without rtti support in Irrlicht
  3218. IGUIElement *element = getElementFromId(s.fid, true);
  3219. gui::IGUIComboBox *e = NULL;
  3220. if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
  3221. e = static_cast<gui::IGUIComboBox *>(element);
  3222. } else {
  3223. warningstream << "GUIFormSpecMenu::acceptInput: dropdown "
  3224. << "field without dropdown element" << std::endl;
  3225. continue;
  3226. }
  3227. s32 selected = e->getSelected();
  3228. if (selected >= 0) {
  3229. if (m_dropdown_index_event.find(s.fname) !=
  3230. m_dropdown_index_event.end()) {
  3231. fields[name] = std::to_string(selected + 1);
  3232. } else {
  3233. std::vector<std::string> *dropdown_values =
  3234. getDropDownValues(s.fname);
  3235. if (dropdown_values && selected < (s32)dropdown_values->size())
  3236. fields[name] = (*dropdown_values)[selected];
  3237. }
  3238. }
  3239. } else if (s.ftype == f_TabHeader) {
  3240. // No dynamic cast possible due to some distributions shipped
  3241. // without rtti support in Irrlicht
  3242. IGUIElement *element = getElementFromId(s.fid, true);
  3243. gui::IGUITabControl *e = nullptr;
  3244. if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
  3245. e = static_cast<gui::IGUITabControl *>(element);
  3246. }
  3247. if (e != 0) {
  3248. fields[name] = itos(e->getActiveTab() + 1);
  3249. }
  3250. } else if (s.ftype == f_CheckBox) {
  3251. // No dynamic cast possible due to some distributions shipped
  3252. // without rtti support in Irrlicht
  3253. IGUIElement *element = getElementFromId(s.fid, true);
  3254. gui::IGUICheckBox *e = nullptr;
  3255. if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
  3256. e = static_cast<gui::IGUICheckBox*>(element);
  3257. }
  3258. if (e != 0) {
  3259. if (e->isChecked())
  3260. fields[name] = "true";
  3261. else
  3262. fields[name] = "false";
  3263. }
  3264. } else if (s.ftype == f_ScrollBar) {
  3265. // No dynamic cast possible due to some distributions shipped
  3266. // without rtti support in Irrlicht
  3267. IGUIElement *element = getElementFromId(s.fid, true);
  3268. GUIScrollBar *e = nullptr;
  3269. if (element && element->getType() == gui::EGUIET_ELEMENT)
  3270. e = static_cast<GUIScrollBar *>(element);
  3271. if (e) {
  3272. if (s.fdefault == L"Changed")
  3273. fields[name] = "CHG:" + itos(e->getPos());
  3274. else
  3275. fields[name] = "VAL:" + itos(e->getPos());
  3276. }
  3277. } else if (s.ftype == f_AnimatedImage) {
  3278. // No dynamic cast possible due to some distributions shipped
  3279. // without rtti support in Irrlicht
  3280. IGUIElement *element = getElementFromId(s.fid, true);
  3281. GUIAnimatedImage *e = nullptr;
  3282. if (element && element->getType() == gui::EGUIET_ELEMENT)
  3283. e = static_cast<GUIAnimatedImage *>(element);
  3284. if (e)
  3285. fields[name] = std::to_string(e->getFrameIndex() + 1);
  3286. } else {
  3287. IGUIElement *e = getElementFromId(s.fid, true);
  3288. if (e)
  3289. fields[name] = wide_to_utf8(e->getText());
  3290. }
  3291. }
  3292. }
  3293. m_text_dst->gotText(fields);
  3294. }
  3295. }
  3296. bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
  3297. {
  3298. // This must be done first so that GUIModalMenu can set m_pointer_type
  3299. // correctly.
  3300. if (GUIModalMenu::preprocessEvent(event))
  3301. return true;
  3302. // The IGUITabControl renders visually using the skin's selected
  3303. // font, which we override for the duration of form drawing,
  3304. // but computes tab hotspots based on how it would have rendered
  3305. // using the font that is selected at the time of button release.
  3306. // To make these two consistent, temporarily override the skin's
  3307. // font while the IGUITabControl is processing the event.
  3308. if (event.EventType == EET_MOUSE_INPUT_EVENT &&
  3309. event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
  3310. s32 x = event.MouseInput.X;
  3311. s32 y = event.MouseInput.Y;
  3312. gui::IGUIElement *hovered =
  3313. Environment->getRootGUIElement()->getElementFromPoint(
  3314. core::position2d<s32>(x, y));
  3315. if (hovered && isMyChild(hovered) &&
  3316. hovered->getType() == gui::EGUIET_TAB_CONTROL) {
  3317. gui::IGUISkin* skin = Environment->getSkin();
  3318. sanity_check(skin != NULL);
  3319. gui::IGUIFont *old_font = skin->getFont();
  3320. skin->setFont(m_font);
  3321. bool retval = hovered->OnEvent(event);
  3322. skin->setFont(old_font);
  3323. return retval;
  3324. }
  3325. }
  3326. // Fix Esc/Return key being eaten by checkboxen and tables
  3327. if (event.EventType == EET_KEY_INPUT_EVENT) {
  3328. KeyPress kp(event.KeyInput);
  3329. if (kp == EscapeKey || kp == CancelKey
  3330. || kp == getKeySetting("keymap_inventory")
  3331. || event.KeyInput.Key==KEY_RETURN) {
  3332. gui::IGUIElement *focused = Environment->getFocus();
  3333. if (focused && isMyChild(focused) &&
  3334. (focused->getType() == gui::EGUIET_LIST_BOX ||
  3335. focused->getType() == gui::EGUIET_CHECK_BOX) &&
  3336. (focused->getParent()->getType() != gui::EGUIET_COMBO_BOX ||
  3337. event.KeyInput.Key != KEY_RETURN)) {
  3338. OnEvent(event);
  3339. return true;
  3340. }
  3341. }
  3342. }
  3343. // Mouse wheel and move events: send to hovered element instead of focused
  3344. if (event.EventType == EET_MOUSE_INPUT_EVENT &&
  3345. (event.MouseInput.Event == EMIE_MOUSE_WHEEL ||
  3346. (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
  3347. event.MouseInput.ButtonStates == 0))) {
  3348. s32 x = event.MouseInput.X;
  3349. s32 y = event.MouseInput.Y;
  3350. gui::IGUIElement *hovered =
  3351. Environment->getRootGUIElement()->getElementFromPoint(
  3352. core::position2d<s32>(x, y));
  3353. if (hovered && isMyChild(hovered)) {
  3354. hovered->OnEvent(event);
  3355. return event.MouseInput.Event == EMIE_MOUSE_WHEEL;
  3356. }
  3357. }
  3358. if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
  3359. /* TODO add a check like:
  3360. if (event.JoystickEvent != joystick_we_listen_for)
  3361. return false;
  3362. */
  3363. bool handled = m_joystick->handleEvent(event.JoystickEvent);
  3364. if (handled) {
  3365. if (m_joystick->wasKeyDown(KeyType::ESC)) {
  3366. tryClose();
  3367. } else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
  3368. if (m_allowclose) {
  3369. acceptInput(quit_mode_accept);
  3370. quitMenu();
  3371. }
  3372. }
  3373. }
  3374. return handled;
  3375. }
  3376. return false;
  3377. }
  3378. void GUIFormSpecMenu::tryClose()
  3379. {
  3380. if (m_allowclose) {
  3381. doPause = false;
  3382. acceptInput(quit_mode_cancel);
  3383. quitMenu();
  3384. } else {
  3385. m_text_dst->gotText(L"MenuQuit");
  3386. }
  3387. }
  3388. bool GUIFormSpecMenu::OnEvent(const SEvent& event)
  3389. {
  3390. if (event.EventType==EET_KEY_INPUT_EVENT) {
  3391. KeyPress kp(event.KeyInput);
  3392. if (event.KeyInput.PressedDown && (
  3393. (kp == EscapeKey) || (kp == CancelKey) ||
  3394. ((m_client != NULL) && (kp == getKeySetting("keymap_inventory"))))) {
  3395. tryClose();
  3396. return true;
  3397. }
  3398. if (m_client != NULL && event.KeyInput.PressedDown &&
  3399. (kp == getKeySetting("keymap_screenshot"))) {
  3400. m_client->makeScreenshot();
  3401. }
  3402. if (event.KeyInput.PressedDown && kp == getKeySetting("keymap_toggle_debug"))
  3403. m_show_debug = !m_show_debug;
  3404. if (event.KeyInput.PressedDown &&
  3405. (event.KeyInput.Key==KEY_RETURN ||
  3406. event.KeyInput.Key==KEY_UP ||
  3407. event.KeyInput.Key==KEY_DOWN)
  3408. ) {
  3409. switch (event.KeyInput.Key) {
  3410. case KEY_RETURN:
  3411. current_keys_pending.key_enter = true;
  3412. break;
  3413. case KEY_UP:
  3414. current_keys_pending.key_up = true;
  3415. break;
  3416. case KEY_DOWN:
  3417. current_keys_pending.key_down = true;
  3418. break;
  3419. break;
  3420. default:
  3421. //can't happen at all!
  3422. FATAL_ERROR("Reached a source line that can't ever been reached");
  3423. break;
  3424. }
  3425. if (current_keys_pending.key_enter && m_allowclose) {
  3426. acceptInput(quit_mode_accept);
  3427. quitMenu();
  3428. } else {
  3429. acceptInput();
  3430. }
  3431. return true;
  3432. }
  3433. }
  3434. /* Mouse event other than movement, or crossing the border of inventory
  3435. field while holding left, right, or middle mouse button
  3436. or touch event (for touch screen devices)
  3437. */
  3438. if ((event.EventType == EET_MOUSE_INPUT_EVENT &&
  3439. (event.MouseInput.Event != EMIE_MOUSE_MOVED ||
  3440. ((event.MouseInput.isLeftPressed() ||
  3441. event.MouseInput.isRightPressed() ||
  3442. event.MouseInput.isMiddlePressed()) &&
  3443. getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) ||
  3444. event.EventType == EET_TOUCH_INPUT_EVENT) {
  3445. // Get selected item and hovered/clicked item (s)
  3446. m_old_tooltip_id = -1;
  3447. updateSelectedItem();
  3448. GUIInventoryList::ItemSpec s = getItemAtPos(m_pointer);
  3449. Inventory *inv_selected = NULL;
  3450. InventoryList *list_selected = NULL;
  3451. Inventory *inv_s = NULL;
  3452. InventoryList *list_s = NULL;
  3453. if (m_selected_item) {
  3454. inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
  3455. sanity_check(inv_selected);
  3456. list_selected = inv_selected->getList(m_selected_item->listname);
  3457. sanity_check(list_selected);
  3458. }
  3459. u32 s_count = 0;
  3460. if (s.isValid())
  3461. do { // breakable
  3462. inv_s = m_invmgr->getInventory(s.inventoryloc);
  3463. if (!inv_s) {
  3464. errorstream << "InventoryMenu: The selected inventory location "
  3465. << "\"" << s.inventoryloc.dump() << "\" doesn't exist"
  3466. << std::endl;
  3467. s.i = -1; // make it invalid again
  3468. break;
  3469. }
  3470. list_s = inv_s->getList(s.listname);
  3471. if (list_s == NULL) {
  3472. verbosestream << "InventoryMenu: The selected inventory list \""
  3473. << s.listname << "\" does not exist" << std::endl;
  3474. s.i = -1; // make it invalid again
  3475. break;
  3476. }
  3477. if ((u32)s.i >= list_s->getSize()) {
  3478. infostream << "InventoryMenu: The selected inventory list \""
  3479. << s.listname << "\" is too small (i=" << s.i << ", size="
  3480. << list_s->getSize() << ")" << std::endl;
  3481. s.i = -1; // make it invalid again
  3482. break;
  3483. }
  3484. s_count = list_s->getItem(s.i).count;
  3485. } while(0);
  3486. // True if the hovered slot is the selected slot
  3487. bool identical = m_selected_item && s.isValid() && (*m_selected_item == s);
  3488. // True if the hovered slot is empty
  3489. bool empty = s.isValid() && list_s->getItem(s.i).empty();
  3490. // True if the hovered item would stack with the selected item
  3491. bool matching = false;
  3492. if (m_selected_item && s.isValid()) {
  3493. ItemStack a = list_selected->getItem(m_selected_item->i);
  3494. ItemStack b = list_s->getItem(s.i);
  3495. matching = a.stacksWith(b);
  3496. }
  3497. ButtonEventType button = BET_OTHER;
  3498. ButtonEventType updown = BET_OTHER;
  3499. bool mouse_shift = false;
  3500. if (event.EventType == EET_MOUSE_INPUT_EVENT) {
  3501. mouse_shift = event.MouseInput.Shift;
  3502. switch (event.MouseInput.Event) {
  3503. case EMIE_LMOUSE_PRESSED_DOWN:
  3504. button = BET_LEFT; updown = BET_DOWN;
  3505. break;
  3506. case EMIE_RMOUSE_PRESSED_DOWN:
  3507. button = BET_RIGHT; updown = BET_DOWN;
  3508. break;
  3509. case EMIE_MMOUSE_PRESSED_DOWN:
  3510. button = BET_MIDDLE; updown = BET_DOWN;
  3511. break;
  3512. case EMIE_MOUSE_WHEEL:
  3513. button = (event.MouseInput.Wheel > 0) ?
  3514. BET_WHEEL_UP : BET_WHEEL_DOWN;
  3515. updown = BET_DOWN;
  3516. break;
  3517. case EMIE_LMOUSE_LEFT_UP:
  3518. button = BET_LEFT; updown = BET_UP;
  3519. break;
  3520. case EMIE_RMOUSE_LEFT_UP:
  3521. button = BET_RIGHT; updown = BET_UP;
  3522. break;
  3523. case EMIE_MMOUSE_LEFT_UP:
  3524. button = BET_MIDDLE; updown = BET_UP;
  3525. break;
  3526. case EMIE_MOUSE_MOVED:
  3527. updown = BET_MOVE;
  3528. break;
  3529. default:
  3530. break;
  3531. }
  3532. }
  3533. // The second touch (see GUIModalMenu::preprocessEvent() function)
  3534. ButtonEventType touch = BET_OTHER;
  3535. if (event.EventType == EET_TOUCH_INPUT_EVENT) {
  3536. if (event.TouchInput.Event == ETIE_LEFT_UP)
  3537. touch = BET_RIGHT;
  3538. }
  3539. // Set this number to a positive value to generate a move action
  3540. // from m_selected_item to s.
  3541. u32 move_amount = 0;
  3542. // Set this number to a positive value to generate a move action
  3543. // from s to the next inventory ring.
  3544. u32 shift_move_amount = 0;
  3545. // Set this number to a positive value to generate a move action
  3546. // from s to m_selected_item.
  3547. u32 pickup_amount = 0;
  3548. // Set this number to a positive value to generate a drop action
  3549. // from m_selected_item.
  3550. u32 drop_amount = 0;
  3551. // Set this number to a positive value to generate a craft action at s.
  3552. u32 craft_amount = 0;
  3553. switch (updown) {
  3554. case BET_DOWN: {
  3555. // Some mouse button has been pressed
  3556. if (m_held_mouse_button != BET_OTHER)
  3557. break;
  3558. if (button == BET_LEFT || button == BET_RIGHT || button == BET_MIDDLE)
  3559. m_held_mouse_button = button;
  3560. if (!s.isValid()) {
  3561. if (m_selected_item && !getAbsoluteClippingRect().isPointInside(m_pointer)) {
  3562. // Clicked outside of the window: drop
  3563. if (button == BET_RIGHT || button == BET_WHEEL_UP)
  3564. drop_amount = 1;
  3565. else if (button == BET_MIDDLE)
  3566. drop_amount = MYMIN(m_selected_amount, 10);
  3567. else if (button == BET_LEFT)
  3568. drop_amount = m_selected_amount;
  3569. }
  3570. break;
  3571. }
  3572. if (s.listname == "craftpreview") {
  3573. // Craft preview has been clicked: craft
  3574. if (button == BET_MIDDLE)
  3575. craft_amount = 10;
  3576. else if (mouse_shift && button == BET_LEFT)
  3577. craft_amount = list_s->getItem(s.i).getStackMax(m_client->idef());
  3578. else
  3579. craft_amount = 1;
  3580. // Holding shift moves the crafted item to the inventory
  3581. m_shift_move_after_craft = mouse_shift;
  3582. } else if (!m_selected_item && button != BET_WHEEL_UP && !empty) {
  3583. // Non-empty stack has been clicked: select or shift-move it
  3584. u32 count = 0;
  3585. if (button == BET_RIGHT)
  3586. count = (s_count + 1) / 2;
  3587. else if (button == BET_MIDDLE)
  3588. count = MYMIN(s_count, 10);
  3589. else if (button == BET_WHEEL_DOWN)
  3590. count = 1;
  3591. else if (button == BET_LEFT)
  3592. count = s_count;
  3593. if (mouse_shift) {
  3594. // Shift pressed: move item, right click moves 1
  3595. shift_move_amount = button == BET_RIGHT ? 1 : count;
  3596. } else {
  3597. // No shift: select item
  3598. m_selected_item = new GUIInventoryList::ItemSpec(s);
  3599. m_selected_amount = count;
  3600. m_selected_dragging = button != BET_WHEEL_DOWN;
  3601. }
  3602. } else if (m_selected_item) {
  3603. // Clicked a slot: move
  3604. if (button == BET_RIGHT || button == BET_WHEEL_UP)
  3605. move_amount = 1;
  3606. else if (button == BET_WHEEL_DOWN)
  3607. pickup_amount = MYMIN(s_count, 1);
  3608. else if (button == BET_MIDDLE)
  3609. move_amount = MYMIN(m_selected_amount, 10);
  3610. else if (button == BET_LEFT)
  3611. move_amount = m_selected_amount;
  3612. if (mouse_shift && !identical && matching) {
  3613. // Shift-move all items the same as the selected item to the next list
  3614. move_amount = 0;
  3615. // Try to find somewhere to move the items to
  3616. s16 r = getNextInventoryRing(s.inventoryloc, s.listname);
  3617. if (r < 0) // Not found
  3618. break;
  3619. const ListRingSpec &to_ring = m_inventory_rings[r];
  3620. Inventory *inv_to = m_invmgr->getInventory(to_ring.inventoryloc);
  3621. if (!inv_to)
  3622. break;
  3623. InventoryList *list_to = inv_to->getList(to_ring.listname);
  3624. if (!list_to)
  3625. break;
  3626. ItemStack slct = list_selected->getItem(m_selected_item->i);
  3627. for (s32 i = 0; i < (s32)list_s->getSize(); i++) {
  3628. // Skip the selected slot
  3629. if (i == m_selected_item->i)
  3630. continue;
  3631. ItemStack item = list_s->getItem(i);
  3632. if (slct.stacksWith(item)) {
  3633. IMoveAction *a = new IMoveAction();
  3634. a->count = item.count;
  3635. a->from_inv = s.inventoryloc;
  3636. a->from_list = s.listname;
  3637. a->from_i = i;
  3638. a->to_inv = to_ring.inventoryloc;
  3639. a->to_list = to_ring.listname;
  3640. a->move_somewhere = true;
  3641. m_invmgr->inventoryAction(a);
  3642. }
  3643. }
  3644. } else if (button == BET_LEFT && (empty || matching)) {
  3645. // We don't know if the user is left-dragging, just moving
  3646. // the item, or doing a pickup-all via doubleclick, so assume
  3647. // that they are left-dragging, and wait for the next event
  3648. // before moving the item, or doing a pickup-all
  3649. m_left_dragging = true;
  3650. m_client->inhibit_inventory_revert = true;
  3651. m_left_drag_stack = list_selected->getItem(m_selected_item->i);
  3652. m_left_drag_amount = m_selected_amount;
  3653. m_left_drag_stacks.emplace_back(s, list_s->getItem(s.i));
  3654. move_amount = 0;
  3655. } else if (identical) {
  3656. // Change the selected amount instead of moving
  3657. if (button == BET_WHEEL_DOWN) {
  3658. if (m_selected_amount < s_count)
  3659. ++m_selected_amount;
  3660. } else if (button == BET_WHEEL_UP) {
  3661. if (m_selected_amount > 0)
  3662. --m_selected_amount;
  3663. } else {
  3664. if (move_amount >= m_selected_amount)
  3665. m_selected_amount = 0;
  3666. else
  3667. m_selected_amount -= move_amount;
  3668. }
  3669. move_amount = 0;
  3670. pickup_amount = 0;
  3671. }
  3672. }
  3673. break;
  3674. }
  3675. case BET_UP: {
  3676. // Some mouse button has been released
  3677. if (m_held_mouse_button != BET_OTHER && m_held_mouse_button != button)
  3678. break;
  3679. m_held_mouse_button = BET_OTHER;
  3680. if (m_selected_dragging && m_selected_item) {
  3681. if (s.isValid() && !identical && (empty || matching)) {
  3682. // Dragged to different slot: move all selected
  3683. move_amount = m_selected_amount;
  3684. } else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) {
  3685. // Dragged outside of window: drop all selected
  3686. drop_amount = m_selected_amount;
  3687. }
  3688. }
  3689. m_selected_dragging = false;
  3690. if (m_left_dragging && button == BET_LEFT) {
  3691. m_left_dragging = false;
  3692. m_client->inhibit_inventory_revert = false;
  3693. if (m_left_drag_stacks.size() > 1) {
  3694. // Finalize the left-dragging
  3695. for (auto &ds : m_left_drag_stacks) {
  3696. if (ds.first == *m_selected_item) {
  3697. // This entry is needed to properly calculate the stack sizes.
  3698. // The stack already exists, hence no further action needed here.
  3699. continue;
  3700. }
  3701. // Check how many items we should move to this slot,
  3702. // it may be less than the full split
  3703. Inventory *inv_to = m_invmgr->getInventory(ds.first.inventoryloc);
  3704. InventoryList *list_to = inv_to->getList(ds.first.listname);
  3705. ItemStack stack_to = list_to->getItem(ds.first.i);
  3706. u16 amount = stack_to.count - ds.second.count;
  3707. IMoveAction *a = new IMoveAction();
  3708. a->count = amount;
  3709. a->from_inv = m_selected_item->inventoryloc;
  3710. a->from_list = m_selected_item->listname;
  3711. a->from_i = m_selected_item->i;
  3712. a->to_inv = ds.first.inventoryloc;
  3713. a->to_list = ds.first.listname;
  3714. a->to_i = ds.first.i;
  3715. m_invmgr->inventoryAction(a);
  3716. }
  3717. } else if (identical) {
  3718. // Put the selected item back where it came from
  3719. m_selected_amount = 0;
  3720. } else if (s.isValid()) {
  3721. // Move the selected item
  3722. move_amount = m_selected_amount;
  3723. }
  3724. m_left_drag_stacks.clear();
  3725. }
  3726. break;
  3727. }
  3728. case BET_MOVE: {
  3729. // Mouse button is down and mouse pointer entered a new inventory field
  3730. if (!s.isValid() || s.listname == "craftpreview")
  3731. break;
  3732. if (!m_selected_item && mouse_shift) {
  3733. // Shift-move items while dragging
  3734. if (m_held_mouse_button == BET_RIGHT)
  3735. shift_move_amount = 1;
  3736. else if (m_held_mouse_button == BET_MIDDLE)
  3737. shift_move_amount = MYMIN(s_count, 10);
  3738. else if (m_held_mouse_button == BET_LEFT)
  3739. shift_move_amount = s_count;
  3740. } else if (m_selected_item) {
  3741. if (m_held_mouse_button != BET_LEFT) {
  3742. // Move items if the destination slot is empty
  3743. // or contains the same item type as what is going to be moved
  3744. if (!m_selected_dragging && (empty || matching)) {
  3745. if (m_held_mouse_button == BET_RIGHT)
  3746. move_amount = 1;
  3747. else if (m_held_mouse_button == BET_MIDDLE)
  3748. move_amount = MYMIN(m_selected_amount, 10);
  3749. }
  3750. } else if (m_left_dragging && (empty || matching) &&
  3751. m_left_drag_amount > m_left_drag_stacks.size()) {
  3752. // Add the slot to the left-drag list if it doesn't exist
  3753. bool found = false;
  3754. for (auto &ds : m_left_drag_stacks) {
  3755. if (s == ds.first) {
  3756. found = true;
  3757. break;
  3758. }
  3759. }
  3760. if (!found) {
  3761. m_left_drag_stacks.emplace_back(s, list_s->getItem(s.i));
  3762. }
  3763. } else if (m_selected_dragging && matching && !identical) {
  3764. // Pickup items of the same type while dragging
  3765. pickup_amount = s_count;
  3766. }
  3767. } else if (m_held_mouse_button == BET_LEFT) {
  3768. // Start picking up items
  3769. m_selected_item = new GUIInventoryList::ItemSpec(s);
  3770. m_selected_amount = s_count;
  3771. m_selected_dragging = true;
  3772. }
  3773. break;
  3774. }
  3775. case BET_OTHER: {
  3776. // Some other mouse event has occured
  3777. // Currently only left-double-click should trigger this
  3778. if (!s.isValid() || event.EventType != EET_MOUSE_INPUT_EVENT ||
  3779. event.MouseInput.Event != EMIE_LMOUSE_DOUBLE_CLICK)
  3780. break;
  3781. // Only do the pickup all thing when putting down an item.
  3782. // Doubleclick events are triggered after press-down events, so if
  3783. // m_left_dragging is true here, the user just put down an itemstack,
  3784. // but didn't yet release the button to make it happen.
  3785. if (!m_left_dragging)
  3786. break;
  3787. // Abort left-dragging
  3788. m_left_dragging = false;
  3789. m_client->inhibit_inventory_revert = false;
  3790. m_left_drag_stacks.clear();
  3791. // Both the selected item and the hovered item need to be checked
  3792. // because we don't know exactly when the double-click happened
  3793. ItemStack slct;
  3794. if (!m_selected_item && !empty)
  3795. slct = list_s->getItem(s.i);
  3796. else if (m_selected_item && (identical || empty))
  3797. slct = list_selected->getItem(m_selected_item->i);
  3798. // Pickup all of the item from the list
  3799. if (slct.count > 0) {
  3800. for (s32 i = 0; i < (s32)list_s->getSize(); i++) {
  3801. // Skip the selected slot
  3802. if (i == s.i)
  3803. continue;
  3804. ItemStack item = list_s->getItem(i);
  3805. if (slct.stacksWith(item)) {
  3806. // Found a match, check if we can pick it up
  3807. bool full = false;
  3808. u16 amount = item.count;
  3809. ItemStack leftover = slct.addItem(item, m_client->idef());
  3810. if (!leftover.empty()) {
  3811. amount -= leftover.count;
  3812. full = true;
  3813. }
  3814. if (amount > 0) {
  3815. IMoveAction *a = new IMoveAction();
  3816. a->count = amount;
  3817. a->from_inv = s.inventoryloc;
  3818. a->from_list = s.listname;
  3819. a->from_i = i;
  3820. a->to_inv = s.inventoryloc;
  3821. a->to_list = s.listname;
  3822. a->to_i = s.i;
  3823. m_invmgr->inventoryAction(a);
  3824. if (m_selected_item)
  3825. m_selected_amount += amount;
  3826. }
  3827. if (full) // Stack is full, stop
  3828. break;
  3829. }
  3830. }
  3831. }
  3832. break;
  3833. }
  3834. default:
  3835. break;
  3836. }
  3837. if (touch == BET_RIGHT && m_selected_item && !m_left_dragging) {
  3838. if (!s.isValid()) {
  3839. // Not a valid slot
  3840. if (!getAbsoluteClippingRect().isPointInside(m_pointer))
  3841. // Is outside the menu
  3842. drop_amount = 1;
  3843. } else {
  3844. // Over a valid slot
  3845. move_amount = 1;
  3846. if (identical) {
  3847. // Change the selected amount instead of moving
  3848. if (move_amount >= m_selected_amount)
  3849. m_selected_amount = 0;
  3850. else
  3851. m_selected_amount -= move_amount;
  3852. move_amount = 0;
  3853. }
  3854. }
  3855. }
  3856. // Update left-dragged slots
  3857. if (m_left_dragging && m_left_drag_stacks.size() > 1) {
  3858. // The split amount will always at least one, because the number
  3859. // of slots will never be greater than the selected amount
  3860. u16 split_amount = m_left_drag_amount / m_left_drag_stacks.size();
  3861. u16 split_remaining = m_left_drag_amount % m_left_drag_stacks.size();
  3862. ItemStack stack_from = m_left_drag_stack;
  3863. m_selected_amount = m_left_drag_amount;
  3864. for (auto &ds : m_left_drag_stacks) {
  3865. Inventory *inv_to = m_invmgr->getInventory(ds.first.inventoryloc);
  3866. InventoryList *list_to = inv_to->getList(ds.first.listname);
  3867. if (ds.first == *m_selected_item) {
  3868. // Adding to the source stack, just change the selected amount
  3869. m_selected_amount -= split_amount + split_remaining;
  3870. } else {
  3871. // Reset the stack to its original state
  3872. list_to->changeItem(ds.first.i, ds.second);
  3873. // Add the new split to the stack
  3874. ItemStack add_stack = stack_from;
  3875. add_stack.count = split_amount;
  3876. ItemStack leftover = list_to->addItem(ds.first.i, add_stack);
  3877. // Remove the split items from the source stack
  3878. u16 moved = split_amount - leftover.count;
  3879. m_selected_amount -= moved;
  3880. stack_from.count -= moved;
  3881. }
  3882. }
  3883. // Save the adjusted source stack
  3884. list_selected->changeItem(m_selected_item->i, stack_from);
  3885. }
  3886. // Possibly send inventory action to server
  3887. if (move_amount > 0) {
  3888. // Send IAction::Move
  3889. assert(m_selected_item && m_selected_item->isValid());
  3890. assert(s.isValid());
  3891. assert(list_selected && list_s);
  3892. ItemStack stack_from = list_selected->getItem(m_selected_item->i);
  3893. ItemStack stack_to = list_s->getItem(s.i);
  3894. // Check how many items can be moved
  3895. move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
  3896. ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
  3897. // If source stack cannot be added to destination stack at all,
  3898. // they are swapped
  3899. if (leftover.count == stack_from.count && leftover.name == stack_from.name) {
  3900. if (m_selected_swap.empty()) {
  3901. m_selected_amount = stack_to.count;
  3902. m_selected_dragging = false;
  3903. // WARNING: BLACK MAGIC, BUT IN A REDUCED SET
  3904. // Skip next validation checks due async inventory calls
  3905. m_selected_swap = stack_to;
  3906. } else {
  3907. move_amount = 0;
  3908. }
  3909. }
  3910. // Source stack goes fully into destination stack
  3911. else if (leftover.empty()) {
  3912. m_selected_amount -= move_amount;
  3913. }
  3914. // Source stack goes partly into destination stack
  3915. else {
  3916. move_amount -= leftover.count;
  3917. m_selected_amount -= move_amount;
  3918. }
  3919. if (move_amount > 0) {
  3920. infostream << "Handing IAction::Move to manager" << std::endl;
  3921. IMoveAction *a = new IMoveAction();
  3922. a->count = move_amount;
  3923. a->from_inv = m_selected_item->inventoryloc;
  3924. a->from_list = m_selected_item->listname;
  3925. a->from_i = m_selected_item->i;
  3926. a->to_inv = s.inventoryloc;
  3927. a->to_list = s.listname;
  3928. a->to_i = s.i;
  3929. m_invmgr->inventoryAction(a);
  3930. }
  3931. } else if (pickup_amount > 0) {
  3932. // Send IAction::Move
  3933. assert(m_selected_item && m_selected_item->isValid());
  3934. assert(s.isValid());
  3935. assert(list_selected && list_s);
  3936. ItemStack stack_from = list_s->getItem(s.i);
  3937. ItemStack stack_to = list_selected->getItem(m_selected_item->i);
  3938. // Only move if the items are exactly the same,
  3939. // we shouldn't attempt to pickup different items
  3940. if (matching) {
  3941. // Check how many items can be moved
  3942. pickup_amount = stack_from.count = MYMIN(pickup_amount, stack_from.count);
  3943. ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
  3944. pickup_amount -= leftover.count;
  3945. } else {
  3946. pickup_amount = 0;
  3947. }
  3948. if (pickup_amount > 0) {
  3949. m_selected_amount += pickup_amount;
  3950. infostream << "Handing IAction::Move to manager" << std::endl;
  3951. IMoveAction *a = new IMoveAction();
  3952. a->count = pickup_amount;
  3953. a->from_inv = s.inventoryloc;
  3954. a->from_list = s.listname;
  3955. a->from_i = s.i;
  3956. a->to_inv = m_selected_item->inventoryloc;
  3957. a->to_list = m_selected_item->listname;
  3958. a->to_i = m_selected_item->i;
  3959. m_invmgr->inventoryAction(a);
  3960. }
  3961. } else if (shift_move_amount > 0) {
  3962. // Try to shift-move the item
  3963. do {
  3964. s16 r = getNextInventoryRing(s.inventoryloc, s.listname);
  3965. if (r < 0) // Not found
  3966. break;
  3967. const ListRingSpec &to_ring = m_inventory_rings[r];
  3968. InventoryList *list_from = list_s;
  3969. if (!s.isValid())
  3970. break;
  3971. Inventory *inv_to = m_invmgr->getInventory(to_ring.inventoryloc);
  3972. if (!inv_to)
  3973. break;
  3974. InventoryList *list_to = inv_to->getList(to_ring.listname);
  3975. if (!list_to)
  3976. break;
  3977. // Check how many items can be moved
  3978. ItemStack stack_from = list_from->getItem(s.i);
  3979. shift_move_amount = MYMIN(shift_move_amount, stack_from.count);
  3980. if (shift_move_amount == 0)
  3981. break;
  3982. infostream << "Handing IAction::Move to manager" << std::endl;
  3983. IMoveAction *a = new IMoveAction();
  3984. a->count = shift_move_amount;
  3985. a->from_inv = s.inventoryloc;
  3986. a->from_list = s.listname;
  3987. a->from_i = s.i;
  3988. a->to_inv = to_ring.inventoryloc;
  3989. a->to_list = to_ring.listname;
  3990. a->move_somewhere = true;
  3991. m_invmgr->inventoryAction(a);
  3992. } while (0);
  3993. } else if (drop_amount > 0) {
  3994. // Send IAction::Drop
  3995. assert(m_selected_item && m_selected_item->isValid());
  3996. assert(list_selected);
  3997. ItemStack stack_from = list_selected->getItem(m_selected_item->i);
  3998. // Check how many items can be dropped
  3999. drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
  4000. assert(drop_amount > 0 && drop_amount <= m_selected_amount);
  4001. m_selected_amount -= drop_amount;
  4002. infostream << "Handing IAction::Drop to manager" << std::endl;
  4003. IDropAction *a = new IDropAction();
  4004. a->count = drop_amount;
  4005. a->from_inv = m_selected_item->inventoryloc;
  4006. a->from_list = m_selected_item->listname;
  4007. a->from_i = m_selected_item->i;
  4008. m_invmgr->inventoryAction(a);
  4009. } else if (craft_amount > 0) {
  4010. assert(s.isValid());
  4011. // If there are no items selected or the selected item
  4012. // belongs to craftresult list, proceed with crafting
  4013. if (!m_selected_item ||
  4014. !m_selected_item->isValid() || m_selected_item->listname == "craftresult") {
  4015. assert(inv_s);
  4016. // Send IACTION_CRAFT
  4017. infostream << "Handing IACTION_CRAFT to manager" << std::endl;
  4018. ICraftAction *a = new ICraftAction();
  4019. a->count = craft_amount;
  4020. a->craft_inv = s.inventoryloc;
  4021. m_invmgr->inventoryAction(a);
  4022. }
  4023. }
  4024. // If m_selected_amount has been decreased to zero,
  4025. // and we are not left-dragging, deselect
  4026. if (m_selected_amount == 0 && !m_left_dragging) {
  4027. m_selected_swap.clear();
  4028. delete m_selected_item;
  4029. m_selected_item = nullptr;
  4030. m_selected_amount = 0;
  4031. m_selected_dragging = false;
  4032. }
  4033. m_old_pointer = m_pointer;
  4034. }
  4035. if (event.EventType == EET_GUI_EVENT) {
  4036. if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
  4037. && isVisible()) {
  4038. // find the element that was clicked
  4039. for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
  4040. if ((s.ftype == f_TabHeader) &&
  4041. (s.fid == event.GUIEvent.Caller->getID())) {
  4042. if (!s.sound.empty() && m_sound_manager)
  4043. m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
  4044. s.send = true;
  4045. acceptInput();
  4046. s.send = false;
  4047. return true;
  4048. }
  4049. }
  4050. }
  4051. if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
  4052. && isVisible()) {
  4053. if (!canTakeFocus(event.GUIEvent.Element)) {
  4054. infostream<<"GUIFormSpecMenu: Not allowing focus change."
  4055. <<std::endl;
  4056. // Returning true disables focus change
  4057. return true;
  4058. }
  4059. }
  4060. if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
  4061. (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
  4062. (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
  4063. (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
  4064. s32 caller_id = event.GUIEvent.Caller->getID();
  4065. if (caller_id == 257) {
  4066. if (m_allowclose) {
  4067. acceptInput(quit_mode_accept);
  4068. quitMenu();
  4069. } else {
  4070. acceptInput();
  4071. m_text_dst->gotText(L"ExitButton");
  4072. }
  4073. // quitMenu deallocates menu
  4074. return true;
  4075. }
  4076. // find the element that was clicked
  4077. for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
  4078. // if its a button, set the send field so
  4079. // lua knows which button was pressed
  4080. if (caller_id != s.fid)
  4081. continue;
  4082. if (s.ftype == f_Button || s.ftype == f_CheckBox) {
  4083. if (!s.sound.empty() && m_sound_manager)
  4084. m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
  4085. s.send = true;
  4086. if (s.is_exit) {
  4087. if (m_allowclose) {
  4088. acceptInput(quit_mode_accept);
  4089. quitMenu();
  4090. } else {
  4091. m_text_dst->gotText(L"ExitButton");
  4092. }
  4093. return true;
  4094. }
  4095. acceptInput(quit_mode_no);
  4096. s.send = false;
  4097. return true;
  4098. } else if (s.ftype == f_DropDown) {
  4099. // only send the changed dropdown
  4100. for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
  4101. if (s2.ftype == f_DropDown) {
  4102. s2.send = false;
  4103. }
  4104. }
  4105. if (!s.sound.empty() && m_sound_manager)
  4106. m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
  4107. s.send = true;
  4108. acceptInput(quit_mode_no);
  4109. // revert configuration to make sure dropdowns are sent on
  4110. // regular button click
  4111. for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
  4112. if (s2.ftype == f_DropDown) {
  4113. s2.send = true;
  4114. }
  4115. }
  4116. return true;
  4117. } else if (s.ftype == f_ScrollBar) {
  4118. s.fdefault = L"Changed";
  4119. acceptInput(quit_mode_no);
  4120. s.fdefault.clear();
  4121. } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) {
  4122. if (!s.sound.empty() && m_sound_manager)
  4123. m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
  4124. s.send = true;
  4125. acceptInput();
  4126. s.send = false;
  4127. }
  4128. }
  4129. }
  4130. if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
  4131. // move scroll_containers
  4132. for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
  4133. c.second->onScrollEvent(event.GUIEvent.Caller);
  4134. }
  4135. if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
  4136. if (event.GUIEvent.Caller->getID() > 257) {
  4137. bool close_on_enter = true;
  4138. for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
  4139. if (s.ftype == f_Unknown &&
  4140. s.fid == event.GUIEvent.Caller->getID()) {
  4141. current_field_enter_pending = s.fname;
  4142. std::unordered_map<std::string, bool>::const_iterator it =
  4143. field_close_on_enter.find(s.fname);
  4144. if (it != field_close_on_enter.end())
  4145. close_on_enter = (*it).second;
  4146. break;
  4147. }
  4148. }
  4149. if (m_allowclose && close_on_enter) {
  4150. current_keys_pending.key_enter = true;
  4151. acceptInput(quit_mode_accept);
  4152. quitMenu();
  4153. } else {
  4154. current_keys_pending.key_enter = true;
  4155. acceptInput();
  4156. }
  4157. // quitMenu deallocates menu
  4158. return true;
  4159. }
  4160. }
  4161. if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
  4162. int current_id = event.GUIEvent.Caller->getID();
  4163. if (current_id > 257) {
  4164. // find the element that was clicked
  4165. for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
  4166. // if it's a table, set the send field
  4167. // so lua knows which table was changed
  4168. if ((s.ftype == f_Table) && (s.fid == current_id)) {
  4169. s.send = true;
  4170. acceptInput();
  4171. s.send=false;
  4172. }
  4173. }
  4174. return true;
  4175. }
  4176. }
  4177. }
  4178. if (m_second_touch)
  4179. return true; // Stop propagating the event
  4180. return Parent ? Parent->OnEvent(event) : false;
  4181. }
  4182. /**
  4183. * get name of element by element id
  4184. * @param id of element
  4185. * @return name string or empty string
  4186. */
  4187. std::string GUIFormSpecMenu::getNameByID(s32 id)
  4188. {
  4189. for (FieldSpec &spec : m_fields) {
  4190. if (spec.fid == id)
  4191. return spec.fname;
  4192. }
  4193. return "";
  4194. }
  4195. const GUIFormSpecMenu::FieldSpec *GUIFormSpecMenu::getSpecByID(s32 id)
  4196. {
  4197. for (FieldSpec &spec : m_fields) {
  4198. if (spec.fid == id)
  4199. return &spec;
  4200. }
  4201. return nullptr;
  4202. }
  4203. /**
  4204. * get label of element by id
  4205. * @param id of element
  4206. * @return label string or empty string
  4207. */
  4208. std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
  4209. {
  4210. for (FieldSpec &spec : m_fields) {
  4211. if (spec.fid == id)
  4212. return spec.flabel;
  4213. }
  4214. return L"";
  4215. }
  4216. StyleSpec GUIFormSpecMenu::getDefaultStyleForElement(const std::string &type,
  4217. const std::string &name, const std::string &parent_type) {
  4218. return getStyleForElement(type, name, parent_type)[StyleSpec::STATE_DEFAULT];
  4219. }
  4220. std::array<StyleSpec, StyleSpec::NUM_STATES> GUIFormSpecMenu::getStyleForElement(
  4221. const std::string &type, const std::string &name, const std::string &parent_type)
  4222. {
  4223. std::array<StyleSpec, StyleSpec::NUM_STATES> ret;
  4224. auto it = theme_by_type.find("*");
  4225. if (it != theme_by_type.end()) {
  4226. for (const StyleSpec &spec : it->second)
  4227. ret[(u32)spec.getState()] |= spec;
  4228. }
  4229. it = theme_by_name.find("*");
  4230. if (it != theme_by_name.end()) {
  4231. for (const StyleSpec &spec : it->second)
  4232. ret[(u32)spec.getState()] |= spec;
  4233. }
  4234. if (!parent_type.empty()) {
  4235. it = theme_by_type.find(parent_type);
  4236. if (it != theme_by_type.end()) {
  4237. for (const StyleSpec &spec : it->second)
  4238. ret[(u32)spec.getState()] |= spec;
  4239. }
  4240. }
  4241. it = theme_by_type.find(type);
  4242. if (it != theme_by_type.end()) {
  4243. for (const StyleSpec &spec : it->second)
  4244. ret[(u32)spec.getState()] |= spec;
  4245. }
  4246. it = theme_by_name.find(name);
  4247. if (it != theme_by_name.end()) {
  4248. for (const StyleSpec &spec : it->second)
  4249. ret[(u32)spec.getState()] |= spec;
  4250. }
  4251. return ret;
  4252. }