gcsx_dialog.cpp 77 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385
  1. /* GCSx
  2. ** DIALOG.CPP
  3. **
  4. ** Dialog boxes
  5. */
  6. /*****************************************************************************
  7. ** Copyright (C) 2003-2006 Janson
  8. **
  9. ** This program is free software; you can redistribute it and/or modify
  10. ** it under the terms of the GNU General Public License as published by
  11. ** the Free Software Foundation; either version 2 of the License, or
  12. ** (at your option) any later version.
  13. **
  14. ** This program is distributed in the hope that it will be useful,
  15. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ** GNU General Public License for more details.
  18. **
  19. ** You should have received a copy of the GNU General Public License
  20. ** along with this program; if not, write to the Free Software
  21. ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  22. *****************************************************************************/
  23. #include "all.h"
  24. // Dialog class
  25. Dialog::Dialog(const string& dTitle, int dAutoApply, int dHasTitlebar, int toolPanel) : WindowCollection(), Window(), title(dTitle) { start_func
  26. isToolPanel = toolPanel;
  27. hasTitlebar = dHasTitlebar;
  28. lastButtonId = 0;
  29. open = 0;
  30. openModal = 0;
  31. nextTabId = 0;
  32. noAutoClose = 0;
  33. autoApply = dAutoApply;
  34. myFrame = NULL;
  35. lastWidgetArranged = -1;
  36. nextWidgetY = (toolPanel ? GUI_PANEL_TOPMARGIN : GUI_DIALOG_TOPMARGIN);
  37. inSiblingModify = 0;
  38. }
  39. Dialog::~Dialog() { start_func
  40. // (shall already have closed off of desktop)
  41. // Delete all widgets that wish to be
  42. list<Window*>::iterator end = windowDataDisplay.end();
  43. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  44. // If this were called during the wrong exception, windowDataDisplay COULD have NULLs in it
  45. if (*pos) {
  46. if ((*pos)->wantsToBeDeleted()) delete *pos;
  47. }
  48. }
  49. }
  50. void Dialog::setTitle(const string& newTitle) { start_func
  51. title = newTitle;
  52. if (myFrame) myFrame->setTitle(newTitle);
  53. }
  54. void Dialog::clearWidgets() { start_func
  55. assert(!open);
  56. // Delete all widgets that wish to be
  57. list<Window*>::iterator end = windowDataDisplay.end();
  58. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  59. // If this were called during the wrong exception, windowDataDisplay COULD have NULLs in it
  60. if (*pos) {
  61. if ((*pos)->wantsToBeDeleted()) delete *pos;
  62. }
  63. }
  64. windowDataDisplay.clear();
  65. windowDataFocus.clear();
  66. currentFocus = NULL;
  67. previousFocus = NULL;
  68. lastFocus = NULL;
  69. currentMouseFocus = NULL;
  70. modalPos = NULL;
  71. nextTabId = 0;
  72. lastWidgetArranged = -1;
  73. nextWidgetY = (isToolPanel ? GUI_PANEL_TOPMARGIN : GUI_DIALOG_TOPMARGIN);
  74. resize(1, 1);
  75. }
  76. #ifndef NDEBUG
  77. const char* Dialog::debugDump() const { start_func
  78. // return title.c_str();
  79. return "DIALOG";
  80. }
  81. #endif
  82. int Dialog::event(int hasFocus, const SDL_Event* event) { start_func
  83. assert(event);
  84. // Check for SDL_CLOSE events- this means we can close safely, frame window is closing
  85. if (event->type == SDL_CLOSE) {
  86. open = 0;
  87. openModal = 0;
  88. myFrame = NULL;
  89. // Frame window will delete us if we've requested being deleted, so we're done!
  90. return 1;
  91. }
  92. // We don't propogate these events (they'd infinite loop)
  93. // but we need to send them to the currently active element
  94. if (event->type == SDL_INPUTFOCUS) {
  95. if (event->user.code & 1) {
  96. changeInputFocus(lastFocus);
  97. lastFocus = NULL;
  98. }
  99. else {
  100. if (!lastFocus) {
  101. lastFocus = currentFocus;
  102. changeInputFocus(NULL);
  103. }
  104. }
  105. return 1;
  106. }
  107. else if (event->type == SDL_MOUSEFOCUS) {
  108. if (!(event->user.code & 1) && (currentMouseFocus)) {
  109. int result = windowRunEvent(currentMouseFocus, event);
  110. currentMouseFocus = NULL;
  111. return result;
  112. }
  113. return 0;
  114. }
  115. int result = handleEvent(event);
  116. // Handle special "global" keys like TAB
  117. if ((!result) && (event->type == SDL_KEYDOWN)) {
  118. int id = -1;
  119. switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
  120. case SDLK_TAB:
  121. // Next widget
  122. return nextControl();
  123. case combineKey(SDLK_TAB, KMOD_SHIFT):
  124. // Previous widget
  125. return prevControl();
  126. case SDLK_DOWN:
  127. case SDLK_RIGHT:
  128. // Next widget with same id
  129. if (currentFocus) id = dynamic_cast<Widget*>(currentFocus)->getId();
  130. return nextControl(id);
  131. case SDLK_UP:
  132. case SDLK_LEFT:
  133. // Previous widget with same id
  134. if (currentFocus) id = dynamic_cast<Widget*>(currentFocus)->getId();
  135. return prevControl(id);
  136. case SDLK_KP_ENTER:
  137. case SDLK_RETURN:
  138. if (noAutoClose) break;
  139. // Current button, or OK button if none
  140. if (currentFocus) {
  141. if (typeid(*currentFocus) == typeid(WButton)) {
  142. dynamic_cast<Widget*>(currentFocus)->doAction();
  143. return 1;
  144. }
  145. }
  146. // (find OK button)
  147. if (buttonControl(BUTTON_OK)) {
  148. dynamic_cast<Widget*>(currentFocus)->doAction();
  149. }
  150. return 1;
  151. case SDLK_ESCAPE:
  152. if (noAutoClose) break;
  153. // (find CANCEL button?)
  154. if (buttonControl(BUTTON_CANCEL)) {
  155. dynamic_cast<Widget*>(currentFocus)->doAction();
  156. }
  157. else {
  158. desktop->setModalReturn(0);
  159. closeWindow(); // Calls attemptClose
  160. }
  161. return 1;
  162. default:
  163. // Plain or ALT only
  164. if ((event->key.keysym.mod == KMOD_ALT) ||(event->key.keysym.mod == KMOD_NONE)) {
  165. // Hotkey?
  166. if (hotkeyControl(event->key.keysym.sym)) return 1;
  167. }
  168. break;
  169. }
  170. }
  171. return result;
  172. }
  173. void Dialog::currentFocusDoAction() { start_func
  174. if (currentFocus) {
  175. dynamic_cast<Widget*>(currentFocus)->doAction();
  176. }
  177. }
  178. void Dialog::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
  179. Window::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
  180. WindowCollection::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
  181. }
  182. void Dialog::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  183. assert(destSurface);
  184. if (visible) {
  185. // If dirty, redraw all
  186. if (dirty) {
  187. getRect(toDisplay);
  188. toDisplay.x += xOffset;
  189. toDisplay.y += yOffset;
  190. dirty = 0;
  191. updateRect.w = 0;
  192. }
  193. // Redraw portion requested?
  194. else if (updateRect.w) {
  195. updateRect.x += x + xOffset;
  196. updateRect.y += y + yOffset;
  197. boundRects(toDisplay, updateRect);
  198. updateRect.w = 0;
  199. }
  200. intersectRects(toDisplay, clipArea);
  201. // Anything to draw?
  202. if ((toDisplay.w) || (childDirty)) {
  203. // Fill with background
  204. if (toDisplay.w) {
  205. SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_FILL]);
  206. }
  207. xOffset += x;
  208. yOffset += y;
  209. list<Window*>::iterator end = windowDataDisplay.end();
  210. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  211. assert(*pos);
  212. // Start with clip area of entire widget
  213. Rect clip;
  214. (*pos)->getRect(clip);
  215. clip.x += xOffset;
  216. clip.y += yOffset;
  217. // If this intersects with our current update area OR widget is dirty
  218. if ((intersectRects(clip, toDisplay)) || ((*pos)->isDirty())) {
  219. // ...then display widget (we only request area that matches our update)
  220. (*pos)->display(destSurface, clip, clipArea, xOffset, yOffset);
  221. // and add in anything it did update to our update area
  222. boundRects(toDisplay, clip);
  223. }
  224. }
  225. childDirty = 0;
  226. }
  227. }
  228. }
  229. const char* Dialog::tooltip(int xPos, int yPos) const { start_func
  230. const Window* which = findWindowByCoords(xPos, yPos);
  231. if (which) {
  232. return which->tooltip(xPos - which->getX(), yPos - which->getY());
  233. }
  234. return NULL;
  235. }
  236. int Dialog::attemptClose() { start_func
  237. // If we know we're already closing...
  238. if ((!open) && (!openModal)) {
  239. // We're ok!
  240. return 1;
  241. }
  242. ButtonAction result = verifyEntry(0, BUTTON_CANCEL);
  243. // Action to take- closing is a cancel by default
  244. if (result == BUTTON_DEFAULT) result = BUTTON_CANCEL;
  245. // We can't call doAction because that calls closeWindow...
  246. if ((result == BUTTON_OK) || (result == BUTTON_APPLY)) {
  247. applySettings();
  248. }
  249. if ((result == BUTTON_OK) || (result == BUTTON_CANCEL)) {
  250. // We want to close
  251. return 1;
  252. }
  253. // We don't want to close
  254. return 0;
  255. }
  256. int Dialog::doAction(ButtonAction buttonType) { start_func
  257. if ((buttonType == BUTTON_OK) || (buttonType == BUTTON_APPLY)) {
  258. applySettings();
  259. }
  260. if ((buttonType == BUTTON_OK) || (buttonType == BUTTON_CANCEL)) {
  261. desktop->setModalReturn(0); // Defaults to 0 return, if not overridden
  262. closeWindow(1);
  263. return 1;
  264. }
  265. return 0;
  266. }
  267. Dialog::ButtonAction Dialog::verifyEntry(int buttonId, ButtonAction buttonType) { start_func
  268. if ((buttonType == BUTTON_APPLY) || (buttonType == BUTTON_OK)) {
  269. list<Window*>::iterator end = windowDataDisplay.end();
  270. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  271. assert(*pos);
  272. Widget* curr = dynamic_cast<Widget*>(*pos);
  273. if (curr->refuseAll()) continue;
  274. if (!curr->entryValid()) {
  275. bringToTop(curr);
  276. curr->tabAction();
  277. return BUTTON_NOTHING;
  278. }
  279. }
  280. }
  281. // (you must derive a new Dialog class to do anything here)
  282. return BUTTON_DEFAULT;
  283. }
  284. void Dialog::addWidget(Widget* newWidget) { start_func
  285. addWindow(newWidget, 0);
  286. newWidget->stateTabId(++nextTabId);
  287. }
  288. void Dialog::arrangeRow(int vertCenter, int leftColumnSize, int staticRight) { start_func
  289. list<Window*>::iterator end = windowDataDisplay.end();
  290. list<Window*>::iterator pos;
  291. int count = 0;
  292. for (pos = windowDataDisplay.begin(); (pos != end) && (count <= lastWidgetArranged); ++pos, ++count) ;
  293. // pos contains next widget to arrange
  294. if (pos == end) return;
  295. int tallest = 0;
  296. if (vertCenter) {
  297. // Determine tallest now, for centering
  298. for (; pos != end; ++pos) {
  299. if ((*pos)->getHeight() > tallest) tallest = (*pos)->getHeight();
  300. }
  301. // Start at beginning again
  302. int count = 0;
  303. for (pos = windowDataDisplay.begin(); (pos != end) && (count <= lastWidgetArranged); ++pos, ++count) ;
  304. }
  305. // Arrange in a row
  306. int widgetX = (isToolPanel ? GUI_PANEL_LEFTMARGIN : GUI_DIALOG_LEFTMARGIN);
  307. int totalWidth = -GUI_DIALOG_SEPWIDTH; // (because first seperation doesn't count)
  308. int first = 1;
  309. for (; pos != end; ++count, ++pos) {
  310. int wide = leftColumnSize;
  311. int rightAlign = 0;
  312. if ((!first) || (wide < 0)) {
  313. wide = (*pos)->getWidth();
  314. }
  315. // Right align?
  316. if ((staticRight) && (typeid(**pos) == typeid(WStatic))) {
  317. rightAlign = wide - (*pos)->getWidth();
  318. }
  319. if (vertCenter) {
  320. (*pos)->move(widgetX + rightAlign, nextWidgetY + (tallest - (*pos)->getHeight()) / 2);
  321. }
  322. else {
  323. (*pos)->move(widgetX + rightAlign, nextWidgetY);
  324. if ((*pos)->getHeight() > tallest) tallest = (*pos)->getHeight();
  325. }
  326. widgetX += wide + GUI_DIALOG_SEPWIDTH;
  327. totalWidth += wide + GUI_DIALOG_SEPWIDTH;
  328. first = 0;
  329. }
  330. // Next row goes where?
  331. nextWidgetY += tallest + GUI_DIALOG_SEPHEIGHT;
  332. // Resize dialog width?
  333. totalWidth += (isToolPanel ? (GUI_PANEL_LEFTMARGIN + GUI_PANEL_RIGHTMARGIN) : (GUI_DIALOG_LEFTMARGIN + GUI_DIALOG_RIGHTMARGIN));
  334. if (totalWidth < width) totalWidth = width;
  335. // Resize dialog, ensure we're dirty
  336. resize(totalWidth, nextWidgetY - GUI_DIALOG_SEPHEIGHT + (isToolPanel ? GUI_PANEL_BOTTOMMARGIN : GUI_DIALOG_BOTTOMMARGIN));
  337. setDirty(1);
  338. // Mark our spot for next time
  339. lastWidgetArranged = count - 1;
  340. }
  341. void Dialog::makePretty(int maxColumns, int vertCenter, int pureFlow, int staticRight) { start_func
  342. assert(maxColumns >= 1);
  343. assert(maxColumns <= GUI_DIALOG_NUMCOL);
  344. // Find width of largest widget in each column; sum up heights
  345. // Find tallest for use with vertical cemtering
  346. int widest[GUI_DIALOG_NUMCOL];
  347. int tallest[GUI_DIALOG_NUMROW];
  348. int currRow = 0;
  349. int totalHeight = nextWidgetY;
  350. // Of bottom row
  351. int totalButtonWidth = 0;
  352. int maxButtonHeight = 0;
  353. Window* firstButton = NULL;
  354. list<Window*>::iterator firstButtonPos;
  355. int skipButtons = 0;
  356. int prevColumn = -1;
  357. int rowMaxHeight = 0;
  358. int rowMaxSep = 0;
  359. // (pre-define column widests/tallests)
  360. for (int col = 0; col < GUI_DIALOG_NUMCOL; ++col) {
  361. widest[col] = 0;
  362. }
  363. for (int row = 0; row < GUI_DIALOG_NUMROW; ++row) {
  364. tallest[row] = 0;
  365. }
  366. list<Window*>::iterator end = windowDataDisplay.end();
  367. list<Window*>::iterator pos;
  368. int count = 0;
  369. for (pos = windowDataDisplay.begin(); (pos != end) && (count <= lastWidgetArranged); ++pos, ++count) ;
  370. // pos contains next widget to arrange
  371. if (pos == end) return;
  372. list<Window*>::iterator next;
  373. for (; pos != end; pos = next) {
  374. assert(*pos);
  375. next = pos;
  376. ++next;
  377. // Calculate a row of buttons
  378. if (typeid(**pos) == typeid(WButton)) {
  379. if (!skipButtons) {
  380. if (!firstButton) {
  381. firstButtonPos = pos;
  382. firstButton = *pos;
  383. }
  384. else {
  385. totalButtonWidth += GUI_DIALOG_SEPWIDTH;
  386. }
  387. if ((*pos)->getHeight() > maxButtonHeight) maxButtonHeight = (*pos)->getHeight();
  388. totalButtonWidth += (*pos)->getWidth();
  389. continue;
  390. }
  391. }
  392. // If not a button but we were doing a row of buttons, redo that row
  393. // as normal controls
  394. else if (firstButton) {
  395. firstButton = NULL;
  396. totalButtonWidth = 0;
  397. maxButtonHeight = 0;
  398. pos = firstButtonPos;
  399. --pos;
  400. skipButtons = 1;
  401. continue;
  402. }
  403. else {
  404. skipButtons = 0;
  405. }
  406. // Which column?
  407. int column = prevColumn + 1;
  408. // Pure flow- columns in order, period
  409. if (pureFlow) {
  410. if (column >= maxColumns) column = 0;
  411. }
  412. // Otherwise...
  413. else {
  414. // First time, we start in 2nd column
  415. if (prevColumn == -1) column = 1;
  416. // If we're past the column limit, reset to
  417. // 2nd column again, unless we've requested 1 column only
  418. if (column >= maxColumns) {
  419. if (maxColumns == 1) column = 0;
  420. else column = 1;
  421. }
  422. // Static labels always go in 1st column
  423. if (typeid(**pos) == typeid(WStatic)) {
  424. column = 0;
  425. }
  426. }
  427. // If same or less column, finish most recent row
  428. if (column <= prevColumn) {
  429. assert(currRow < GUI_DIALOG_NUMROW);
  430. tallest[currRow] = rowMaxHeight;
  431. totalHeight += rowMaxHeight + rowMaxSep;
  432. rowMaxHeight = 0;
  433. rowMaxSep = 0;
  434. ++currRow;
  435. }
  436. prevColumn = column;
  437. // Determine highest item in this row
  438. if ((*pos)->getHeight() > rowMaxHeight) rowMaxHeight = (*pos)->getHeight();
  439. // Determine separator below this row
  440. int sep;
  441. if (next == end) sep = 0;
  442. else if ((typeid(**pos) == typeid(*next)) &&
  443. ((typeid(*next) == typeid(WRadioButton)) ||
  444. (typeid(*next) == typeid(WCheckBox)))) sep = GUI_DIALOG_CHECKSEPHEIGHT;
  445. else sep = GUI_DIALOG_SEPHEIGHT;
  446. if (sep > rowMaxSep) rowMaxSep = sep;
  447. // Determine widest item in each column
  448. if ((*pos)->getWidth() > widest[column]) {
  449. widest[column] = (*pos)->getWidth();
  450. }
  451. }
  452. // Finish last row
  453. assert(currRow < GUI_DIALOG_NUMROW);
  454. tallest[currRow] = rowMaxHeight;
  455. totalHeight += rowMaxHeight;
  456. // Did we end up with a button row?
  457. if (firstButton) totalHeight += GUI_DIALOG_BUTTONSEPHEIGHT + maxButtonHeight;
  458. // Resize ourselves
  459. int colSum = widest[GUI_DIALOG_NUMCOL - 1];
  460. for (int col = 0; col < (GUI_DIALOG_NUMCOL - 1); ++col) {
  461. if ((widest[col]) && (widest[col + 1])) colSum += GUI_DIALOG_GUTTERWIDTH;
  462. colSum += widest[col];
  463. }
  464. if (totalButtonWidth > colSum) colSum = totalButtonWidth;
  465. // Resize dialog width?
  466. int totalWidth = colSum + (isToolPanel ? (GUI_PANEL_LEFTMARGIN + GUI_PANEL_RIGHTMARGIN) : (GUI_DIALOG_LEFTMARGIN + GUI_DIALOG_RIGHTMARGIN));
  467. if (totalWidth < width) totalWidth = width;
  468. // Resize dialog, ensure we're dirty
  469. resize(totalWidth, totalHeight + (isToolPanel ? GUI_PANEL_BOTTOMMARGIN : GUI_DIALOG_BOTTOMMARGIN));
  470. setDirty(1);
  471. // Restart
  472. count = 0;
  473. for (pos = windowDataDisplay.begin(); (pos != end) && (count <= lastWidgetArranged); ++pos, ++count) ;
  474. // pos contains next widget to arrange again
  475. // Place elements, starting at upper left margin
  476. int x;
  477. int y = nextWidgetY;
  478. int buttonMode = 0;
  479. int yCenter = 0;
  480. prevColumn = -1;
  481. rowMaxHeight = 0;
  482. rowMaxSep = 0;
  483. currRow = 0;
  484. for (; pos != end; pos = next, ++count) {
  485. assert(*pos);
  486. next = pos;
  487. ++next;
  488. if (*pos == firstButton) {
  489. buttonMode = 1;
  490. x = (width - totalButtonWidth) / 2;
  491. // Add separation and height from previous row (but not previous row sep)
  492. y += tallest[currRow] + GUI_DIALOG_BUTTONSEPHEIGHT;
  493. ++currRow;
  494. }
  495. if (buttonMode) {
  496. (*pos)->move(x, y);
  497. x += (*pos)->getWidth() + GUI_DIALOG_SEPWIDTH;
  498. }
  499. else {
  500. // Which column?
  501. int column = prevColumn + 1;
  502. int alignRight = 0;
  503. if (pureFlow) {
  504. if (column >= maxColumns) column = 0;
  505. if ((staticRight) && (typeid(**pos) == typeid(WStatic))) alignRight = 1;
  506. }
  507. else {
  508. if (prevColumn == -1) column = 1;
  509. if (column >= maxColumns) {
  510. if (maxColumns == 1) column = 0;
  511. else column = 1;
  512. }
  513. if (typeid(**pos) == typeid(WStatic)) {
  514. column = 0;
  515. alignRight = 1;
  516. }
  517. }
  518. // If same or less column, finish most recent row
  519. if (column <= prevColumn) {
  520. y += tallest[currRow] + rowMaxSep;
  521. ++currRow;
  522. rowMaxSep = 0;
  523. }
  524. prevColumn = column;
  525. // Determine separator below this row
  526. int sep;
  527. if (next == end) sep = 0;
  528. else if ((typeid(**pos) == typeid(*next)) &&
  529. ((typeid(*next) == typeid(WRadioButton)) ||
  530. (typeid(*next) == typeid(WCheckBox)))) sep = GUI_DIALOG_CHECKSEPHEIGHT;
  531. else sep = GUI_DIALOG_SEPHEIGHT;
  532. if (sep > rowMaxSep) rowMaxSep = sep;
  533. // Determine column x
  534. x = (isToolPanel ? GUI_PANEL_LEFTMARGIN : GUI_DIALOG_LEFTMARGIN);
  535. for (int col = 0; col < column; ++col) {
  536. if ((col < (GUI_DIALOG_NUMCOL - 1)) && (widest[col]) && (widest[col + 1])) x += GUI_DIALOG_GUTTERWIDTH;
  537. x += widest[col];
  538. }
  539. // Right align?
  540. if (alignRight) {
  541. x += widest[column] - (*pos)->getWidth();
  542. }
  543. // Vertically center?
  544. if (vertCenter) {
  545. yCenter = (tallest[currRow] - (*pos)->getHeight()) / 2;
  546. }
  547. (*pos)->move(x, y + yCenter);
  548. }
  549. }
  550. // Mark our spot for next time
  551. nextWidgetY = totalHeight + GUI_DIALOG_SEPHEIGHT;
  552. lastWidgetArranged = count - 1;
  553. }
  554. int Dialog::runModal(int xPos, int yPos) { start_func
  555. // Prevent duplication
  556. if (myFrame) return 0;
  557. // Keep a backup cause we clear myFrame on SDL_CLOSE
  558. FrameWindow* myFrameBackup;
  559. myFrameBackup = new FrameWindow(title, FrameWindow::RESIZING_OFF, FrameWindow::FRAMETYPE_DIALOG, this, hasTitlebar ? FrameWindow::TITLEBAR_NORMAL : FrameWindow::TITLEBAR_OFF);
  560. myFrame = myFrameBackup;
  561. // Frame window can NOT delete itself- we want to do that, to ensure it isn't
  562. // deleted twice on exception
  563. myFrameBackup->setWantsToBeDeleted(0);
  564. lastButtonId = 0;
  565. // We have to set this before showing the window, so the desktop knows it's modal
  566. openModal = 1;
  567. open = 1;
  568. loadSettings();
  569. // Modal dialogs default to center of SCREEN, not desktop
  570. if (xPos == -1) {
  571. xPos = (screenWidth - myFrameBackup->getWidth()) / 2;
  572. if (xPos < 0) xPos = 0;
  573. }
  574. if (yPos == -1) {
  575. yPos = (screenHeight - myFrameBackup->getHeight()) / 2;
  576. if (yPos < 0) yPos = 0;
  577. }
  578. myFrameBackup->show(xPos, yPos, FrameWindow::SHOW_CURRENT, FrameWindow::SHOW_CURRENT);
  579. firstControl();
  580. // So we don't deselect it immediately
  581. lastFocus = currentFocus;
  582. // Recurse into desktop event loop
  583. int returnValue = desktop->eventLoop();
  584. // Delete frame
  585. delete myFrameBackup;
  586. myFrame = NULL;
  587. return returnValue;
  588. }
  589. void Dialog::firstControl() { start_func
  590. changeInputFocus(NULL);
  591. nextControl();
  592. }
  593. void Dialog::runAsPanel() { start_func
  594. open = 1;
  595. noAutoClose = 1;
  596. loadSettings();
  597. // Select first control
  598. firstControl();
  599. // Deselect it immediately, now we know the proper "first" control
  600. lastFocus = currentFocus;
  601. changeInputFocus(NULL);
  602. }
  603. void Dialog::runWindowed(int xPos, int yPos) { start_func
  604. // Prevent duplication
  605. if (myFrame) {
  606. desktop->bringToTop(myFrame);
  607. return;
  608. }
  609. // We remember the frame pointer even though it'll delete itself
  610. myFrame = new FrameWindow(title, FrameWindow::RESIZING_OFF, FrameWindow::FRAMETYPE_DIALOG, this, hasTitlebar ? FrameWindow::TITLEBAR_NORMAL : FrameWindow::TITLEBAR_OFF);
  611. lastButtonId = 0;
  612. open = 1;
  613. loadSettings();
  614. // Non-modal dialogs go to center of DESKTOP, not entire screen
  615. if (xPos == -1) {
  616. xPos = desktop->desktopX() + (desktop->desktopWidth() - myFrame->getWidth()) / 2;
  617. if (xPos < 0) xPos = 0;
  618. }
  619. if (yPos == -1) {
  620. yPos = desktop->desktopY() + (desktop->desktopHeight() - myFrame->getHeight()) / 2;
  621. if (yPos < 0) yPos = 0;
  622. }
  623. myFrame->show(xPos, yPos, FrameWindow::SHOW_CURRENT, FrameWindow::SHOW_CURRENT);
  624. firstControl();
  625. // So we don't deselect it immediately
  626. lastFocus = currentFocus;
  627. }
  628. Widget* Dialog::findWidget(int id, int which) { start_func
  629. list<Window*>::iterator end = windowDataDisplay.end();
  630. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  631. assert(*pos);
  632. if (dynamic_cast<Widget*>(*pos)->getId() == id) {
  633. if (which) --which;
  634. else return dynamic_cast<Widget*>(*pos)->returnSelf();
  635. }
  636. }
  637. return NULL;
  638. }
  639. void Dialog::applySettings() { start_func
  640. list<Window*>::iterator end = windowDataDisplay.end();
  641. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  642. assert(*pos);
  643. dynamic_cast<Widget*>(*pos)->apply();
  644. }
  645. }
  646. void Dialog::loadSettings() { start_func
  647. list<Window*>::iterator end = windowDataDisplay.end();
  648. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  649. assert(*pos);
  650. dynamic_cast<Widget*>(*pos)->load();
  651. }
  652. }
  653. Window::WindowSort Dialog::windowSort() const { start_func
  654. if (openModal) return WINDOWSORT_MODAL;
  655. return WINDOWSORT_NORMAL;
  656. }
  657. Window::WindowType Dialog::windowType() const { start_func
  658. return WINDOW_CLIENT;
  659. }
  660. int Dialog::wantsToBeDeleted() const { start_func
  661. return 0;
  662. }
  663. Window::CommandSupport Dialog::supportsCommand(int code) const { start_func
  664. if (currentFocus) {
  665. return currentFocus->supportsCommand(code);
  666. }
  667. return Window::COMMAND_HIDE;
  668. }
  669. int Dialog::hotkeyControl(char keypress) { start_func
  670. list<Window*>::iterator end = windowDataDisplay.end();
  671. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  672. assert(*pos);
  673. Widget* curr = dynamic_cast<Widget*>(*pos);
  674. if (curr->getShortcut() == keypress) {
  675. int refuse = curr->refuseAll();
  676. if (refuse == WStatic::REFUSE_STATIC) {
  677. // Select next control, but no action
  678. nextControl(-1, curr);
  679. return 1;
  680. }
  681. if (refuse) {
  682. return 0;
  683. }
  684. bringToTop(curr);
  685. curr->doAction();
  686. return 1;
  687. }
  688. }
  689. return 0;
  690. }
  691. int Dialog::buttonControl(ButtonAction action) { start_func
  692. list<Window*>::iterator end = windowDataDisplay.end();
  693. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  694. assert(*pos);
  695. if (typeid(**pos) == typeid(WButton)) {
  696. if (dynamic_cast<Widget*>(*pos)->refuseAll()) continue;
  697. if (dynamic_cast<WButton*>(*pos)->getAction() == action) {
  698. bringToTop(dynamic_cast<Widget*>(*pos));
  699. return 1;
  700. }
  701. }
  702. }
  703. return 0;
  704. }
  705. int Dialog::nextControl(int id, const Widget* from) { start_func
  706. // Current widget tab id
  707. int currId = 0;
  708. if (from) currId = from->stateTabId();
  709. else if (currentFocus) currId = dynamic_cast<Widget*>(currentFocus)->stateTabId();
  710. // Search for the closest higher id; also record lowest id
  711. int closestId = 0x7FFF;
  712. Widget* closestWidget = NULL;
  713. int lowestId = 0x7FFF;
  714. Widget* lowestWidget = NULL;
  715. list<Window*>::iterator end = windowDataDisplay.end();
  716. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  717. assert(*pos);
  718. Widget* curr = dynamic_cast<Widget*>(*pos);
  719. int tabId = curr->stateTabId();
  720. if ((id >= 0) && (curr->getId() != id)) continue;
  721. if (curr->refuseAll()) continue;
  722. if ((tabId < closestId) && (tabId > currId)) {
  723. closestId = tabId;
  724. closestWidget = curr;
  725. }
  726. if (tabId < lowestId) {
  727. lowestId = tabId;
  728. lowestWidget = curr;
  729. }
  730. }
  731. if (!closestWidget) closestWidget = lowestWidget;
  732. if ((closestWidget) && (currentFocus != closestWidget)) {
  733. bringToTop(closestWidget);
  734. closestWidget->tabAction();
  735. return 1;
  736. }
  737. return 0;
  738. }
  739. int Dialog::prevControl(int id) { start_func
  740. // Current widget id
  741. int currId = 0;
  742. if (currentFocus) currId = dynamic_cast<Widget*>(currentFocus)->stateTabId();
  743. // Search for the closest lower id; also record highest id
  744. int closestId = 0;
  745. Widget* closestWidget = NULL;
  746. int highestId = 0;
  747. Widget* highestWidget = NULL;
  748. list<Window*>::iterator end = windowDataDisplay.end();
  749. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  750. assert(*pos);
  751. Widget* curr = dynamic_cast<Widget*>(*pos);
  752. int tabId = curr->stateTabId();
  753. if ((id >= 0) && (curr->getId() != id)) continue;
  754. if (curr->refuseAll()) continue;
  755. if ((tabId > closestId) && (tabId < currId)) {
  756. closestId = tabId;
  757. closestWidget = curr;
  758. }
  759. if (tabId > highestId) {
  760. highestId = tabId;
  761. highestWidget = curr;
  762. }
  763. }
  764. if (!closestWidget) closestWidget = highestWidget;
  765. if ((closestWidget) && (currentFocus != closestWidget)) {
  766. bringToTop(closestWidget);
  767. closestWidget->tabAction();
  768. return 1;
  769. }
  770. return 0;
  771. }
  772. void Dialog::childModified(Window* modified) { start_func
  773. assert(modified);
  774. Widget* widget = dynamic_cast<Widget*>(modified);
  775. if (widget) {
  776. if (autoApply) widget->apply();
  777. // Don't recurse modifies
  778. if (inSiblingModify) return;
  779. inSiblingModify = 1;
  780. // Tell all other widgets
  781. list<Window*>::iterator end = windowDataDisplay.end();
  782. for (list<Window*>::iterator pos = windowDataDisplay.begin(); pos != end; ++pos) {
  783. if (*pos != modified) {
  784. Widget* lWidget = dynamic_cast<Widget*>(*pos);
  785. if (lWidget) lWidget->siblingModified(widget);
  786. }
  787. }
  788. inSiblingModify = 0;
  789. }
  790. Window::childModified(modified);
  791. }
  792. // Widget class
  793. Widget::Widget(int wId, const string& wLabel, void* wSetting) : Window(), label() { start_func
  794. assert(wId >= 0);
  795. setLabel(wLabel);
  796. disabled = 0;
  797. haveFocus = 0;
  798. id = wId;
  799. setting = wSetting;
  800. tabId = -1;
  801. tip = NULL;
  802. }
  803. Widget::~Widget() { start_func
  804. }
  805. void Widget::setLabel(const string& newLabel) { start_func
  806. label = newLabel;
  807. if (label.length() > MAX_LINELENGTH) label = label.substr(0, MAX_LINELENGTH);
  808. underline = convertGuiText(&label, &shortcut);
  809. }
  810. void Widget::setToolTip(const char* wTip) { start_func
  811. assert(wTip);
  812. tip = wTip;
  813. }
  814. const char* Widget::tooltip(int xPos, int yPos) const { start_func
  815. return tip;
  816. }
  817. Widget* Widget::returnSelf() { start_func
  818. return this;
  819. }
  820. void Widget::changeStorage(void* newSetting) { start_func
  821. setting = newSetting;
  822. }
  823. void Widget::addTo(Dialog* dialog) { start_func
  824. dialog->addWidget(this);
  825. }
  826. #ifndef NDEBUG
  827. const char* Widget::debugDump() const { start_func
  828. if (label.size()) return label.c_str();
  829. return "(widget)";
  830. }
  831. #endif
  832. int Widget::stateTabId(int wTabId) { start_func
  833. assert(wTabId > 0);
  834. tabId = wTabId;
  835. return tabId;
  836. }
  837. int Widget::stateTabId() const { start_func
  838. assert(tabId > 0);
  839. return tabId;
  840. }
  841. int Widget::refuseAll() const { start_func
  842. if (disabled) return 1;
  843. return 0;
  844. }
  845. void Widget::siblingModified(Widget* modified) { start_func
  846. assert(modified);
  847. assert(modified != this);
  848. }
  849. int Widget::entryValid() const { start_func
  850. return 1;
  851. }
  852. void Widget::doAction() { start_func
  853. }
  854. void Widget::tabAction() { start_func
  855. }
  856. int Widget::wantsToBeDeleted() const { start_func
  857. return 1;
  858. }
  859. Window::WindowType Widget::windowType() const { start_func
  860. return WINDOW_WIDGET;
  861. }
  862. char Widget::getShortcut() const { start_func
  863. return shortcut;
  864. }
  865. int Widget::getId() const { start_func
  866. return id;
  867. }
  868. void Widget::disable() { start_func
  869. if (!disabled) {
  870. disabled = 1;
  871. haveFocus = 0;
  872. setDirty(1);
  873. }
  874. }
  875. void Widget::enable() { start_func
  876. if (disabled) {
  877. disabled = 0;
  878. setDirty(1);
  879. }
  880. }
  881. Dialog* Widget::myParent() { start_func
  882. return dynamic_cast<Dialog*>(parent);
  883. }
  884. // Static widget
  885. WStatic::WStatic(int sId, const string& sLabel) : Widget(sId, sLabel, NULL) { start_func
  886. // Calculate size (add one to width so we can properly draw disabled text)
  887. resize(fontWidth(label) + 1, fontHeight());
  888. }
  889. int WStatic::refuseAll() const { start_func
  890. if (disabled) return 1;
  891. return WStatic::REFUSE_STATIC;
  892. }
  893. int WStatic::event(int hasFocus, const SDL_Event* event) { start_func
  894. assert(event);
  895. assert(parent);
  896. switch (event->type) {
  897. case SDL_INPUTFOCUS:
  898. // Refuse focus
  899. if (event->user.code & 1) {
  900. myParent()->sendToBottom(this);
  901. }
  902. return 1;
  903. }
  904. return 0;
  905. }
  906. void WStatic::changeText(const string& newLabel) { start_func
  907. setLabel(newLabel);
  908. // Recalculate size, but don't make smaller
  909. resize(max(width, fontWidth(label) + 1), height);
  910. setDirty();
  911. }
  912. void WStatic::load() { start_func
  913. // (static text does not store a value)
  914. }
  915. void WStatic::apply() { start_func
  916. // (static text does not store a value)
  917. }
  918. void WStatic::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  919. assert(destSurface);
  920. if (visible) {
  921. // If dirty, redraw all
  922. if (dirty) {
  923. getRect(toDisplay);
  924. toDisplay.x += xOffset;
  925. toDisplay.y += yOffset;
  926. dirty = 0;
  927. intersectRects(toDisplay, clipArea);
  928. }
  929. // Anything to draw?
  930. if (toDisplay.w) {
  931. SDL_SetClipRect(destSurface, &toDisplay);
  932. xOffset += x;
  933. yOffset += y;
  934. // This widget makes no attempt at partial redraw other than the initial fill.
  935. SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_FILL]);
  936. if (disabled) {
  937. drawText(label, guiRGB[COLOR_LIGHT1], xOffset + 1, yOffset + 1, destSurface);
  938. drawText(label, guiRGB[COLOR_DARK1], xOffset, yOffset, destSurface);
  939. }
  940. else {
  941. drawText(label, guiRGB[COLOR_TEXT], xOffset, yOffset, destSurface);
  942. if (underline) drawTextUnderline(underline, guiPacked[COLOR_DARK2], xOffset, yOffset, destSurface);
  943. }
  944. }
  945. }
  946. }
  947. // Button widget
  948. WButton::WButton(int bId, const string& bLabel, void* bSetting) : Widget(bId, bLabel, bSetting) { start_func
  949. action = Dialog::BUTTON_NOTHING;
  950. pressed = spacePressed = hover = 0;
  951. icon = NULL;
  952. isIcon = 0;
  953. command = NO_COMMAND;
  954. }
  955. WButton::WButton(int bId, SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH, void* bSetting) : Widget(bId, blankString, bSetting) { start_func
  956. constructIcon(iIconSurface, iX, iY, iW, iH);
  957. action = Dialog::BUTTON_NOTHING;
  958. pressed = spacePressed = hover = 0;
  959. command = NO_COMMAND;
  960. }
  961. void WButton::constructIcon(SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH) { start_func
  962. assert(iX >= 0);
  963. assert(iY >= 0);
  964. assert(iW > 0);
  965. assert(iH > 0);
  966. isIcon = 1;
  967. icon = iIconSurface;
  968. iconX = iX;
  969. iconY = iY;
  970. iconW = iW;
  971. iconH = iH;
  972. // Calculate size
  973. resize(iW + GUI_ICON_MARGIN * 2, iH + GUI_ICON_MARGIN * 2);
  974. }
  975. WButton::WButton(int bId, const string& bLabel, Dialog::ButtonAction bAction, int bCommand) : Widget(bId, bLabel, NULL) { start_func
  976. assert(bAction > Dialog::BUTTON_DEFAULT);
  977. assert(bAction <= Dialog::BUTTON_LASTACT);
  978. action = bAction;
  979. pressed = spacePressed = hover = 0;
  980. icon = NULL;
  981. isIcon = 0;
  982. command = bCommand;
  983. // Calculate size
  984. resize(fontWidth(label) + GUI_BUTTON_LEFTMARGIN + GUI_BUTTON_RIGHTMARGIN,
  985. fontHeight() + GUI_BUTTON_TOPMARGIN + GUI_BUTTON_BOTTOMMARGIN);
  986. }
  987. WButton::WButton(int bId, SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH, Dialog::ButtonAction bAction, int bCommand) : Widget(bId, blankString, NULL) { start_func
  988. assert(bAction > Dialog::BUTTON_DEFAULT);
  989. assert(bAction <= Dialog::BUTTON_LASTACT);
  990. constructIcon(iIconSurface, iX, iY, iW, iH);
  991. action = bAction;
  992. pressed = spacePressed = hover = 0;
  993. command = bCommand;
  994. }
  995. void WButton::changeIcon(int iX, int iY) { start_func
  996. assert(iX >= 0);
  997. assert(iY >= 0);
  998. iconX = iX;
  999. iconY = iY;
  1000. setDirty();
  1001. }
  1002. void WButton::changeText(const string& newLabel) { start_func
  1003. setLabel(newLabel);
  1004. // Recalculate size, but don't make smaller
  1005. resize(max(width, fontWidth(label) + GUI_BUTTON_LEFTMARGIN + GUI_BUTTON_RIGHTMARGIN),
  1006. fontHeight() + GUI_BUTTON_TOPMARGIN + GUI_BUTTON_BOTTOMMARGIN);
  1007. setDirty();
  1008. }
  1009. Dialog::ButtonAction WButton::getAction() const { start_func
  1010. return action;
  1011. }
  1012. void WButton::doAction() { start_func
  1013. // Command?
  1014. if (command != NO_COMMAND) {
  1015. desktop->broadcastEvent(SDL_COMMAND, command);
  1016. desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
  1017. }
  1018. // Handle button press
  1019. Dialog::ButtonAction result = myParent()->verifyEntry(id, action);
  1020. if (result == Dialog::BUTTON_DEFAULT) result = action;
  1021. if (myParent()->doAction(result)) {
  1022. desktop->setModalReturn(id);
  1023. }
  1024. }
  1025. int WButton::event(int hasFocus, const SDL_Event* event) { start_func
  1026. assert(event);
  1027. assert(parent);
  1028. switch (event->type) {
  1029. case SDL_INPUTFOCUS:
  1030. if (event->user.code & 1) {
  1031. if (!haveFocus) {
  1032. haveFocus = 1;
  1033. setDirty();
  1034. }
  1035. }
  1036. else {
  1037. if ((haveFocus) || (spacePressed)) {
  1038. spacePressed = 0;
  1039. haveFocus = 0;
  1040. setDirty();
  1041. }
  1042. }
  1043. return 1;
  1044. case SDL_KEYDOWN:
  1045. switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
  1046. case SDLK_SPACE:
  1047. if (!spacePressed) {
  1048. spacePressed = 1;
  1049. setDirty();
  1050. }
  1051. return 1;
  1052. }
  1053. break;
  1054. case SDL_KEYUP:
  1055. // (no modifiers on key up)
  1056. switch (event->key.keysym.sym) {
  1057. case SDLK_SPACE:
  1058. if (spacePressed) {
  1059. spacePressed = 0;
  1060. doAction();
  1061. setDirty();
  1062. return 1;
  1063. }
  1064. default:
  1065. break;
  1066. }
  1067. break;
  1068. case SDL_MOUSEBUTTONUP:
  1069. if (event->button.button == SDL_BUTTON_LEFT) {
  1070. pressed = hover = 0;
  1071. if ((event->button.x < width) && (event->button.y < height)) {
  1072. doAction();
  1073. }
  1074. setDirty();
  1075. return 1;
  1076. }
  1077. break;
  1078. case SDL_MOUSEBUTTONDBL:
  1079. case SDL_MOUSEBUTTONDOWN:
  1080. if (event->button.button == SDL_BUTTON_LEFT) {
  1081. if ((event->button.x < width) && (event->button.y < height)) {
  1082. if ((!pressed) || (!hover)) {
  1083. pressed = hover = 1;
  1084. setDirty();
  1085. }
  1086. }
  1087. else {
  1088. if ((pressed) || (hover)) {
  1089. pressed = hover = 0;
  1090. setDirty();
  1091. }
  1092. }
  1093. return 1;
  1094. }
  1095. break;
  1096. case SDL_MOUSEMOTION:
  1097. if ((event->motion.x < width) && (event->motion.y < height)) {
  1098. if (!hover) {
  1099. hover = 1;
  1100. setDirty();
  1101. }
  1102. }
  1103. else {
  1104. if (hover) {
  1105. hover = 0;
  1106. setDirty();
  1107. }
  1108. }
  1109. if (event->motion.state & SDL_BUTTON_LMASK) {
  1110. if (pressed != hover) {
  1111. pressed = hover;
  1112. setDirty();
  1113. }
  1114. }
  1115. else {
  1116. if (pressed) {
  1117. pressed = 0;
  1118. setDirty();
  1119. }
  1120. }
  1121. return 1;
  1122. case SDL_MOUSEFOCUS:
  1123. if (event->user.code & 1) {
  1124. if (!hover) {
  1125. hover = 1;
  1126. setDirty();
  1127. }
  1128. }
  1129. else {
  1130. if (hover) {
  1131. hover = 0;
  1132. setDirty();
  1133. }
  1134. }
  1135. return 1;
  1136. }
  1137. return 0;
  1138. }
  1139. void WButton::load() { start_func
  1140. // (buttons do not store a value)
  1141. if ((pressed) || (spacePressed)) {
  1142. pressed = 0;
  1143. spacePressed = 0;
  1144. setDirty();
  1145. }
  1146. }
  1147. void WButton::apply() { start_func
  1148. // (buttons do not store a value)
  1149. }
  1150. void WButton::iconDisplay(SDL_Surface* destSurface, int atX, int atY, int pressed) { start_func
  1151. assert(destSurface);
  1152. int offset = 0;
  1153. int disableMargin;
  1154. // This widget makes no attempt at partial redraw
  1155. if (pressed) {
  1156. // Non-gradient: drawRect(atX, atY, width, height, guiPacked[COLOR_FILL], destSurface);
  1157. drawGradient(atX, atY, width, height, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
  1158. drawGuiBoxInvert(atX, atY, width, height, 2, destSurface);
  1159. disableMargin = 2;
  1160. offset = 1;
  1161. }
  1162. else {
  1163. drawGuiBoxInvert(atX, atY, width, height, 1, destSurface);
  1164. drawGuiBox(atX + 1, atY + 1, width - 2, height - 2, 2, destSurface);
  1165. // Non-gradient: nothing
  1166. drawGradient(atX + 3, atY + 3, width - 6, height - 6, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
  1167. disableMargin = 3;
  1168. }
  1169. if (icon) {
  1170. blit(iconX, iconY, icon, atX + GUI_ICON_MARGIN + offset, atY + GUI_ICON_MARGIN + offset, destSurface, iconW, iconH);
  1171. if (disabled) {
  1172. // 75% opacity to disable
  1173. drawSelectRect(atX + disableMargin, atY + disableMargin, width - disableMargin * 2, height - disableMargin * 2, guiPacked[COLOR_FILL], destSurface, 192);
  1174. }
  1175. }
  1176. if (haveFocus) {
  1177. drawFocusBox(atX + GUI_ICON_FOCUSMARGIN, atY + GUI_ICON_FOCUSMARGIN,
  1178. width - GUI_ICON_FOCUSMARGIN * 2,
  1179. height - GUI_ICON_FOCUSMARGIN * 2, destSurface);
  1180. }
  1181. }
  1182. void WButton::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  1183. assert(destSurface);
  1184. if (visible) {
  1185. // If dirty, redraw all
  1186. if (dirty) {
  1187. getRect(toDisplay);
  1188. toDisplay.x += xOffset;
  1189. toDisplay.y += yOffset;
  1190. dirty = 0;
  1191. intersectRects(toDisplay, clipArea);
  1192. }
  1193. // Anything to draw?
  1194. if (toDisplay.w) {
  1195. SDL_SetClipRect(destSurface, &toDisplay);
  1196. int offset = 0;
  1197. xOffset += x;
  1198. yOffset += y;
  1199. // Icon?
  1200. if (isIcon) {
  1201. iconDisplay(destSurface, xOffset, yOffset, pressed || spacePressed);
  1202. }
  1203. else {
  1204. // This widget makes no attempt at partial redraw other than the initial fill.
  1205. if ((pressed) || (spacePressed)) {
  1206. // Non-gradient: SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_FILL]);
  1207. drawGradient(xOffset, yOffset, width, height, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
  1208. drawGuiBoxInvert(xOffset, yOffset, width, height, 2, destSurface);
  1209. offset = 1;
  1210. }
  1211. else {
  1212. drawGuiBoxInvert(xOffset, yOffset, width, height, 1, destSurface);
  1213. drawGuiBox(xOffset + 1, yOffset + 1, width - 2, height - 2, 2, destSurface);
  1214. // Non-gradient: nothing
  1215. drawGradient(xOffset + 3, yOffset + 3, width - 6, height - 6, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
  1216. }
  1217. if (disabled) {
  1218. drawText(label, guiRGB[COLOR_LIGHT1], xOffset + GUI_BUTTON_LEFTMARGIN + 1 + offset, yOffset + GUI_BUTTON_TOPMARGIN + 1 + offset, destSurface);
  1219. drawText(label, guiRGB[COLOR_DARK1], xOffset + GUI_BUTTON_LEFTMARGIN + offset, yOffset + GUI_BUTTON_TOPMARGIN + offset, destSurface);
  1220. }
  1221. else {
  1222. drawText(label, guiRGB[COLOR_TEXT], xOffset + GUI_BUTTON_LEFTMARGIN + offset, yOffset + GUI_BUTTON_TOPMARGIN + offset, destSurface);
  1223. if (underline) drawTextUnderline(underline, guiPacked[COLOR_DARK2], xOffset + GUI_BUTTON_LEFTMARGIN + offset, yOffset + GUI_BUTTON_TOPMARGIN + offset, destSurface);
  1224. }
  1225. if (haveFocus) {
  1226. drawFocusBox(xOffset + GUI_BUTTON_HFOCUSMARGIN, yOffset + GUI_BUTTON_VFOCUSMARGIN,
  1227. width - GUI_BUTTON_HFOCUSMARGIN * 2,
  1228. height - GUI_BUTTON_VFOCUSMARGIN * 2, destSurface);
  1229. }
  1230. }
  1231. }
  1232. }
  1233. }
  1234. // Checkbox widget
  1235. int WCheckBox::checkboxSize = 0;
  1236. int WCheckBox::checkboxX = 0;
  1237. int WCheckBox::checkboxY = 0;
  1238. int WCheckBox::checkboxXText = 0;
  1239. WCheckBox::WCheckBox(int cId, const string& cLabel, int* cSetting, int cValue) : WButton(cId, cLabel, cSetting), affectControls() { start_func
  1240. assert(cSetting);
  1241. assert(cValue >= 0); // 0 is allowed for radio buttons
  1242. value = cValue;
  1243. currentStatus = 0;
  1244. isRadio = 0;
  1245. checkboxSize = fontHeight() * 4 / 5;
  1246. checkboxX = GUI_CHECKBOX_PRECHECK + GUI_CHECKBOX_LEFTMARGIN;
  1247. checkboxY = (fontHeight() - checkboxSize + 1) / 2 + GUI_CHECKBOX_TOPMARGIN;
  1248. checkboxXText = GUI_CHECKBOX_POSTCHECK + GUI_CHECKBOX_LEFTMARGIN + checkboxSize;
  1249. // Calculate size
  1250. resize(fontWidth(label) + checkboxXText + GUI_CHECKBOX_RIGHTMARGIN,
  1251. fontHeight() + GUI_CHECKBOX_TOPMARGIN + GUI_CHECKBOX_BOTTOMMARGIN);
  1252. }
  1253. WCheckBox::WCheckBox(int cId, SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH, int* cSetting, int cValue) : WButton(cId, iIconSurface, iX, iY, iW, iH, cSetting), affectControls() { start_func
  1254. assert(cSetting);
  1255. assert(cValue >= 0); // 0 is allowed for radio buttons
  1256. value = cValue;
  1257. currentStatus = 0;
  1258. isRadio = 0;
  1259. }
  1260. void WCheckBox::affectControl(int cId, int enable) { start_func
  1261. affectControls[cId] = enable;
  1262. }
  1263. void WCheckBox::changeText(const string& newLabel) { start_func
  1264. setLabel(newLabel);
  1265. // Calculate size, but don't make smaller
  1266. resize(max(width, fontWidth(label) + checkboxXText + GUI_CHECKBOX_RIGHTMARGIN),
  1267. fontHeight() + GUI_CHECKBOX_TOPMARGIN + GUI_CHECKBOX_BOTTOMMARGIN);
  1268. setDirty();
  1269. }
  1270. void WCheckBox::doAction() { start_func
  1271. state(!currentStatus);
  1272. setDirty();
  1273. }
  1274. void WCheckBox::load() { start_func
  1275. state(*(int*)(setting) & value, 1);
  1276. spacePressed = 0;
  1277. pressed = 0;
  1278. hover = 0;
  1279. setDirty();
  1280. }
  1281. void WCheckBox::apply() { start_func
  1282. *(int*)(setting) &= ~value;
  1283. if (state()) *(int*)(setting) |= value;
  1284. }
  1285. int WCheckBox::state(int checked, int fromLoad) { start_func
  1286. if (checked >= 0) {
  1287. if (parent) {
  1288. map<int, int>::iterator pos = affectControls.begin();
  1289. for (; pos != affectControls.end(); ++pos) {
  1290. Widget* affects = dynamic_cast<Dialog*>(parent)->findWidget((*pos).first);
  1291. if (affects) {
  1292. int doEnable = checked;
  1293. if (!((*pos).second)) doEnable = !doEnable;
  1294. if (doEnable) affects->enable();
  1295. else affects->disable();
  1296. }
  1297. }
  1298. }
  1299. currentStatus = checked;
  1300. setDirty();
  1301. if ((!fromLoad) && (parent)) parent->childModified(this);
  1302. }
  1303. return currentStatus;
  1304. }
  1305. void WCheckBox::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  1306. assert(destSurface);
  1307. if (visible) {
  1308. // If dirty, redraw all
  1309. if (dirty) {
  1310. getRect(toDisplay);
  1311. toDisplay.x += xOffset;
  1312. toDisplay.y += yOffset;
  1313. dirty = 0;
  1314. intersectRects(toDisplay, clipArea);
  1315. }
  1316. // Anything to draw?
  1317. if (toDisplay.w) {
  1318. SDL_SetClipRect(destSurface, &toDisplay);
  1319. int offset = 0;
  1320. xOffset += x;
  1321. yOffset += y;
  1322. // Icon?
  1323. if (isIcon) {
  1324. iconDisplay(destSurface, xOffset, yOffset, pressed || spacePressed || state());
  1325. }
  1326. else {
  1327. // This widget makes no attempt at partial redraw other than the initial fill.
  1328. if ((pressed) || (spacePressed)) {
  1329. // Non-gradient: SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_FILL]);
  1330. drawGradient(xOffset, yOffset, width, height, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
  1331. drawGuiBoxInvert(xOffset, yOffset, width, height, 1, destSurface);
  1332. ++offset;
  1333. }
  1334. else if (hover) {
  1335. drawGuiBox(xOffset, yOffset, width, height, 1, destSurface);
  1336. // Non-gradient: nothing
  1337. drawGradient(xOffset + 1, yOffset + 1, width - 2, height - 2, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
  1338. }
  1339. else {
  1340. SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_FILL]);
  1341. }
  1342. if (disabled) {
  1343. drawCheckbox(isRadio, state(), 1, xOffset + offset + checkboxX, yOffset + offset + checkboxY, checkboxSize, destSurface);
  1344. drawText(label, guiRGB[COLOR_LIGHT1], xOffset + checkboxXText + 1 + offset, yOffset + 1 + offset + GUI_CHECKBOX_TOPMARGIN, destSurface);
  1345. drawText(label, guiRGB[COLOR_DARK1], xOffset + checkboxXText + offset, yOffset + offset + GUI_CHECKBOX_TOPMARGIN, destSurface);
  1346. }
  1347. else {
  1348. drawCheckbox(isRadio, state(), 0, xOffset + offset + checkboxX, yOffset + offset + checkboxY, checkboxSize, destSurface);
  1349. drawText(label, guiRGB[COLOR_TEXT], xOffset + checkboxXText + offset, yOffset + offset + GUI_CHECKBOX_TOPMARGIN, destSurface);
  1350. if (underline) drawTextUnderline(underline, guiPacked[COLOR_DARK2], xOffset + checkboxXText + offset, yOffset + offset + GUI_CHECKBOX_TOPMARGIN, destSurface);
  1351. }
  1352. if (haveFocus) {
  1353. drawFocusBox(xOffset, yOffset, width, height, destSurface);
  1354. }
  1355. }
  1356. }
  1357. }
  1358. }
  1359. // Radio widget
  1360. WRadioButton::WRadioButton(int rId, const string& rLabel, int* rSetting, int rValue) : WCheckBox(rId, rLabel, rSetting, rValue) { start_func
  1361. isRadio = 1;
  1362. }
  1363. WRadioButton::WRadioButton(int rId, SDL_Surface* iIconSurface, int iX, int iY, int iW, int iH, int* rSetting, int rValue) : WCheckBox(rId, iIconSurface, iX, iY, iW, iH, rSetting, rValue) { start_func
  1364. isRadio = 1;
  1365. }
  1366. void WRadioButton::doAction() { start_func
  1367. state(1);
  1368. }
  1369. void WRadioButton::load() { start_func
  1370. state(*(int*)(setting) == value, 1);
  1371. spacePressed = 0;
  1372. pressed = 0;
  1373. hover = 0;
  1374. setDirty();
  1375. }
  1376. void WRadioButton::apply() { start_func
  1377. if (state()) *(int*)(setting) = value;
  1378. }
  1379. int WRadioButton::state(int checked, int fromLoad) { start_func
  1380. if ((checked >= 0) && (parent)) {
  1381. map<int, int>::iterator pos = affectControls.begin();
  1382. for (; pos != affectControls.end(); ++pos) {
  1383. Widget* affects = dynamic_cast<Dialog*>(parent)->findWidget((*pos).first);
  1384. if (affects) {
  1385. int doEnable = checked;
  1386. if (!((*pos).second)) doEnable = !doEnable;
  1387. if (doEnable) affects->enable();
  1388. else affects->disable();
  1389. }
  1390. }
  1391. }
  1392. if (checked > 0) {
  1393. if (!currentStatus) setDirty();
  1394. currentStatus = 1;
  1395. // Unset all others of same id
  1396. int pos = 0;
  1397. Widget* widget;
  1398. // (assignment intentional)
  1399. while ( (widget = dynamic_cast<Widget*>(myParent()->findWindow(Window::WINDOW_WIDGET, pos++))) ) {
  1400. if ((typeid(*widget) == typeid(*this)) && (widget->getId() == id)) {
  1401. if (widget != this) dynamic_cast<WRadioButton*>(widget)->state(0, fromLoad);
  1402. }
  1403. }
  1404. if ((!fromLoad) && (parent)) parent->childModified(this);
  1405. }
  1406. else if (checked == 0) {
  1407. if (currentStatus) setDirty();
  1408. currentStatus = 0;
  1409. // Doesn't alert to modified if cleared, as another radio button handles that
  1410. }
  1411. return currentStatus;
  1412. }
  1413. // Textbox widget
  1414. const string WTextBox::wordChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
  1415. WTextBox::WTextBox(int tId, string* tSetting, int tMaxSize, int tDisplayLength) : Widget(tId, blankString, tSetting) { start_func
  1416. assert(tMaxSize >= 0);
  1417. maxSize = tMaxSize;
  1418. if ((maxSize == 0) || (maxSize > MAX_LINELENGTH)) maxSize = MAX_LINELENGTH;
  1419. textX = GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN + GUI_TEXTBOX_LEFTPAD;
  1420. textY = GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN;
  1421. insertY = textY + 1;
  1422. insertHeight = fontHeight() - 2;
  1423. if (tDisplayLength == -1) {
  1424. tDisplayLength = min(maxSize, (int)GUI_TEXTBOX_MAXSIZE);
  1425. string standard(tDisplayLength, 'X');
  1426. tDisplayLength = fontWidth(standard);
  1427. }
  1428. resize(tDisplayLength + (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN) * 2 + GUI_TEXTBOX_LEFTPAD + GUI_TEXTBOX_RIGHTPAD,
  1429. fontHeight() + (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN) * 2);
  1430. }
  1431. // Needed due to virtual ~Widget() and our string member
  1432. WTextBox::~WTextBox() { start_func
  1433. }
  1434. void WTextBox::setDirtyAndBlink() { start_func
  1435. insertBlink = 1;
  1436. insertBlinkMs = SDL_GetTicks();
  1437. setDirty();
  1438. }
  1439. Window::CommandSupport WTextBox::supportsCommand(int code) const { start_func
  1440. switch (code) {
  1441. case EDIT_COPY:
  1442. case EDIT_CUT:
  1443. if (selectionEnd == selectionBegin) return Window::COMMAND_DISABLE;
  1444. return Window::COMMAND_ENABLE;
  1445. case EDIT_PASTE:
  1446. if (canConvertClipboard(CLIPBOARD_TEXT_LINE)) return Window::COMMAND_ENABLE;
  1447. return Window::COMMAND_DISABLE;
  1448. case EDIT_SELECTALL:
  1449. if (currentValue.size()) return Window::COMMAND_ENABLE;
  1450. return Window::COMMAND_DISABLE;
  1451. case EDIT_UNDO:
  1452. if (undoType == UNDO_NONE) return Window::COMMAND_DISABLE;
  1453. return Window::COMMAND_ENABLE;
  1454. }
  1455. return Window::COMMAND_HIDE;
  1456. }
  1457. int WTextBox::event(int hasFocus, const SDL_Event* event) { start_func
  1458. assert(event);
  1459. assert(parent);
  1460. string::size_type found;
  1461. int drag = 0;
  1462. Sint32 key;
  1463. Sint16 scroll;
  1464. string clipStorage;
  1465. switch (event->type) {
  1466. case SDL_MOUSEBUTTONDOWN:
  1467. if ((event->button.button == SDL_BUTTON_LEFT) &&
  1468. (event->button.y >= (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN)) &&
  1469. (event->button.y < (height - GUI_TEXTBOX_BORDERTHICKNESS - GUI_TEXTBOX_INNERMARGIN)) &&
  1470. (event->button.x >= (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN)) &&
  1471. (event->button.x < (width - GUI_TEXTBOX_BORDERTHICKNESS - GUI_TEXTBOX_INNERMARGIN))) {
  1472. insertionState(insertWhere(event->button.x), SDL_GetModState() & KMOD_SHIFT);
  1473. return 1;
  1474. }
  1475. break;
  1476. case SDL_MOUSEBUTTONDBL:
  1477. if ((event->button.y >= (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN)) &&
  1478. (event->button.y < (height - GUI_TEXTBOX_BORDERTHICKNESS - GUI_TEXTBOX_INNERMARGIN)) &&
  1479. (event->button.x >= (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN)) &&
  1480. (event->button.x < (width - GUI_TEXTBOX_BORDERTHICKNESS - GUI_TEXTBOX_INNERMARGIN))) {
  1481. found = insertWhere(event->button.x) - 1;
  1482. if (found < 0) found = 0;
  1483. found = currentValue.find_last_not_of(wordChars, found);
  1484. if (found == string::npos) found = 0;
  1485. else ++found;
  1486. insertionState(found, 0);
  1487. found = currentValue.find_first_not_of(wordChars, insertPoint);
  1488. if (found == string::npos) found = currentValue.size();
  1489. insertionState(found, 1);
  1490. return 1;
  1491. }
  1492. break;
  1493. case SDL_MOUSEMOTION:
  1494. if (event->motion.state & SDL_BUTTON_LMASK) {
  1495. scroll = (Sint16)event->motion.x;
  1496. insertionState(insertWhere(scroll), 1);
  1497. }
  1498. else if ((event->motion.y >= (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN)) &&
  1499. (event->motion.y < (height - GUI_TEXTBOX_BORDERTHICKNESS - GUI_TEXTBOX_INNERMARGIN)) &&
  1500. (event->motion.x >= (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN)) &&
  1501. (event->motion.x < (width - GUI_TEXTBOX_BORDERTHICKNESS - GUI_TEXTBOX_INNERMARGIN))) {
  1502. selectMouse(MOUSE_BEAM);
  1503. }
  1504. else {
  1505. selectMouse(MOUSE_NORMAL);
  1506. }
  1507. return 1;
  1508. case SDL_INPUTFOCUS:
  1509. if (event->user.code & 1) {
  1510. if (!haveFocus) {
  1511. haveFocus = 1;
  1512. setDirtyAndBlink();
  1513. }
  1514. }
  1515. else {
  1516. if (haveFocus) {
  1517. haveFocus = 0;
  1518. setDirty();
  1519. }
  1520. }
  1521. return 1;
  1522. case SDL_SPECIAL:
  1523. if ((event->user.code & SDL_IDLE) && (haveFocus)) {
  1524. Uint32 ticks = SDL_GetTicks();
  1525. // (catch wraparound)
  1526. if (ticks < insertBlinkMs) insertBlinkMs = SDL_GetTicks();
  1527. else if (ticks - insertBlinkMs > DELAY_CURSOR_BLINK) {
  1528. insertBlink = !insertBlink;
  1529. insertBlinkMs = SDL_GetTicks();
  1530. setDirty();
  1531. }
  1532. return 1;
  1533. }
  1534. break;
  1535. case SDL_COMMAND:
  1536. switch (event->user.code) {
  1537. case EDIT_COPY:
  1538. copyText(clipStorage);
  1539. if (clipStorage.size()) clipboardCopy(clipStorage);
  1540. return 1;
  1541. case EDIT_CUT:
  1542. copyText(clipStorage);
  1543. if (clipStorage.size()) {
  1544. saveUndo(UNDO_DELETE);
  1545. pasteText(blankString);
  1546. clipboardCopy(clipStorage);
  1547. }
  1548. return 1;
  1549. case EDIT_PASTE:
  1550. if (canConvertClipboard(CLIPBOARD_TEXT_LINE)) {
  1551. saveUndo(UNDO_OTHER);
  1552. clipboardPasteTextLine(clipStorage);
  1553. pasteText(clipStorage);
  1554. }
  1555. return 1;
  1556. case EDIT_SELECTALL:
  1557. selectAll();
  1558. return 1;
  1559. case EDIT_UNDO:
  1560. case EDIT_REDO:
  1561. undo();
  1562. return 1;
  1563. }
  1564. break;
  1565. case SDL_KEYDOWN:
  1566. if (event->key.keysym.mod & KMOD_SHIFT) {
  1567. drag = 1; // We'll check this if we care about SHIFT
  1568. key = combineKey(event->key.keysym.sym, event->key.keysym.mod & ~KMOD_SHIFT);
  1569. }
  1570. else {
  1571. key = combineKey(event->key.keysym.sym, event->key.keysym.mod);
  1572. }
  1573. switch (key) {
  1574. case SDLK_DELETE:
  1575. if (drag) break;
  1576. saveUndo(UNDO_DELETE);
  1577. keyDelete();
  1578. return 1;
  1579. case SDLK_BACKSPACE:
  1580. if (drag) break;
  1581. saveUndo(UNDO_DELETE);
  1582. keyBackspace();
  1583. return 1;
  1584. case combineKey(SDLK_DELETE, KMOD_CTRL):
  1585. if (drag) break;
  1586. saveUndo(UNDO_DELETE);
  1587. // If no selection, delete to end of line
  1588. if (selectionBegin == selectionEnd) {
  1589. insertionState(currentValue.size(), 1);
  1590. }
  1591. // Delete current selection
  1592. keyDelete();
  1593. return 1;
  1594. case SDLK_RIGHT:
  1595. case SDLK_DOWN:
  1596. insertionState(insertPoint + 1, drag);
  1597. return 1;
  1598. case SDLK_LEFT:
  1599. case SDLK_UP:
  1600. if (insertPoint > 0) insertionState(insertPoint - 1, drag);
  1601. return 1;
  1602. case SDLK_HOME:
  1603. insertionState(0, drag);
  1604. return 1;
  1605. case SDLK_END:
  1606. insertionState(currentValue.size(), drag);
  1607. return 1;
  1608. case combineKey(SDLK_RIGHT, KMOD_CTRL):
  1609. case combineKey(SDLK_DOWN, KMOD_CTRL):
  1610. found = currentValue.find_first_not_of(wordChars, insertPoint);
  1611. if (found == string::npos) found = currentValue.size();
  1612. found = currentValue.find_first_of(wordChars, found);
  1613. if (found == string::npos) found = currentValue.size();
  1614. insertionState(found, drag);
  1615. return 1;
  1616. case combineKey(SDLK_LEFT, KMOD_CTRL):
  1617. case combineKey(SDLK_UP, KMOD_CTRL):
  1618. if (insertPoint <= 1) found = 0;
  1619. else {
  1620. found = currentValue.find_last_of(wordChars, insertPoint - 1);
  1621. if (found == string::npos) found = 0;
  1622. found = currentValue.find_last_not_of(wordChars, found);
  1623. if (found == string::npos) found = 0;
  1624. else if (found > 0) ++found;
  1625. }
  1626. insertionState(found, drag);
  1627. return 1;
  1628. default:
  1629. if ((event->key.keysym.unicode) && !(event->key.keysym.mod & KMOD_ALT)) {
  1630. if (event->key.keysym.unicode & 0xFF80) {
  1631. key = '?';
  1632. }
  1633. else {
  1634. key = event->key.keysym.unicode;
  1635. if (key < 32) break;
  1636. }
  1637. saveUndo(UNDO_TYPING);
  1638. string toInsert(1, key);
  1639. pasteText(toInsert);
  1640. return 1;
  1641. }
  1642. break;
  1643. }
  1644. break;
  1645. }
  1646. return 0;
  1647. }
  1648. void WTextBox::load() { start_func
  1649. currentValue = *((string*)setting);
  1650. if ((int)currentValue.length() > maxSize) currentValue = currentValue.substr(0, maxSize);
  1651. undoValue = blankString;
  1652. undoType = UNDO_NONE;
  1653. insertPoint = 0;
  1654. selectionBegin = 0;
  1655. selectionEnd = 0;
  1656. insertPointX = 0;
  1657. selectionBeginX = 0;
  1658. selectionEndX = 0;
  1659. scrollPointX = 0;
  1660. setDirty();
  1661. }
  1662. void WTextBox::apply() { start_func
  1663. *((string*)setting) = currentValue;
  1664. }
  1665. void WTextBox::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  1666. assert(destSurface);
  1667. if (visible) {
  1668. // If dirty, redraw all
  1669. if (dirty) {
  1670. getRect(toDisplay);
  1671. toDisplay.x += xOffset;
  1672. toDisplay.y += yOffset;
  1673. dirty = 0;
  1674. intersectRects(toDisplay, clipArea);
  1675. }
  1676. // Anything to draw?
  1677. if (toDisplay.w) {
  1678. SDL_SetClipRect(destSurface, &toDisplay);
  1679. xOffset += x;
  1680. yOffset += y;
  1681. // This widget makes no attempt at partial redraw.
  1682. // (fill corners of inverted box first)
  1683. drawRect(xOffset + width - GUI_TEXTBOX_BORDERTHICKNESS, yOffset, GUI_TEXTBOX_BORDERTHICKNESS, GUI_TEXTBOX_BORDERTHICKNESS, guiPacked[COLOR_FILL], destSurface);
  1684. drawRect(xOffset, yOffset + height - GUI_TEXTBOX_BORDERTHICKNESS, GUI_TEXTBOX_BORDERTHICKNESS, GUI_TEXTBOX_BORDERTHICKNESS, guiPacked[COLOR_FILL], destSurface);
  1685. drawGuiBoxInvert(xOffset, yOffset, width, height, GUI_TEXTBOX_BORDERTHICKNESS, destSurface);
  1686. // Clear
  1687. drawRect(xOffset + GUI_TEXTBOX_BORDERTHICKNESS, yOffset + GUI_TEXTBOX_BORDERTHICKNESS, width - 2 * GUI_TEXTBOX_BORDERTHICKNESS, height - 2 * GUI_TEXTBOX_BORDERTHICKNESS, guiPacked[disabled ? COLOR_FILL : COLOR_TEXTBOX], destSurface);
  1688. // Text area
  1689. Rect textClip = { xOffset + GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN,
  1690. yOffset + GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN,
  1691. width - 2 * (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN),
  1692. height - 2 * (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN) };
  1693. if (intersectRects(textClip, toDisplay)) {
  1694. SDL_SetClipRect(destSurface, &textClip);
  1695. // Paint text
  1696. if (disabled) {
  1697. drawText(currentValue, guiRGB[COLOR_LIGHT1], xOffset + textX + scrollPointX + 1, yOffset + textY + 1, destSurface);
  1698. drawText(currentValue, guiRGB[COLOR_DARK1], xOffset + textX + scrollPointX, yOffset + textY, destSurface);
  1699. }
  1700. else {
  1701. drawText(currentValue, guiRGB[COLOR_TEXT], xOffset + textX + scrollPointX, yOffset + textY, destSurface);
  1702. }
  1703. if (haveFocus) {
  1704. if (selectionBegin != selectionEnd) {
  1705. drawGradient(xOffset + textX + scrollPointX + selectionBeginX, yOffset + insertY,
  1706. selectionEndX - selectionBeginX, insertHeight,
  1707. guiRGB[COLOR_SELECTION1], guiRGB[COLOR_SELECTION2], destSurface);
  1708. // Redraw highlighted text inverted
  1709. drawText(currentValue.substr(selectionBegin, selectionEnd - selectionBegin), guiRGB[COLOR_TEXTBOX],
  1710. xOffset + textX + scrollPointX + selectionBeginX, yOffset + textY, destSurface);
  1711. }
  1712. if (insertBlink) {
  1713. // Insertion point
  1714. drawRect(xOffset + textX + scrollPointX + insertPointX - 1, yOffset + insertY,
  1715. GUI_TEXTBOX_INSERTPOINTWIDTH, insertHeight,
  1716. guiPacked[COLOR_CURSOR], destSurface);
  1717. }
  1718. }
  1719. }
  1720. }
  1721. }
  1722. }
  1723. int WTextBox::allowChange(const string& oldValue, string* newValue, int oldCursorPos, int* newCursorPos) const { start_func
  1724. return 1;
  1725. }
  1726. const string& WTextBox::state() const { start_func
  1727. return currentValue;
  1728. }
  1729. const string& WTextBox::state(const string& newValue) { start_func
  1730. // (sets dirty)
  1731. selectAll();
  1732. pasteText(newValue);
  1733. return currentValue;
  1734. }
  1735. void WTextBox::saveUndo(UndoType type) { start_func
  1736. // Any sequence of delete or typing is treated as one action
  1737. if ((type == undoType) && ((type == UNDO_TYPING) || (type == UNDO_DELETE))) return;
  1738. // Any sequence of typing after deleting is treated as one action
  1739. if ((type == UNDO_TYPING) && (undoType == UNDO_DELETE)) {
  1740. undoType = type;
  1741. return;
  1742. }
  1743. undoPoint = insertPoint;
  1744. undoValue = currentValue;
  1745. undoType = type;
  1746. }
  1747. void WTextBox::undo() { start_func
  1748. if (undoType == UNDO_NONE) return;
  1749. undoType = UNDO_OTHER;
  1750. // Swap undo and buffer
  1751. swap(undoValue, currentValue);
  1752. // Swap undo point and current point
  1753. int newUndoPoint = insertPoint;
  1754. insertionState(undoPoint);
  1755. undoPoint = newUndoPoint;
  1756. parent->childModified(this);
  1757. }
  1758. void WTextBox::selectAll() { start_func
  1759. // (sets dirty)
  1760. insertionState(0, 0);
  1761. insertionState(currentValue.size(), 1);
  1762. }
  1763. void WTextBox::keyBackspace() { start_func
  1764. if (selectionBegin == selectionEnd) {
  1765. if (insertPoint == 0) return;
  1766. insertionState(insertPoint - 1, 1);
  1767. }
  1768. // (sets dirty)
  1769. pasteText(blankString);
  1770. }
  1771. void WTextBox::keyDelete() { start_func
  1772. if (selectionBegin == selectionEnd) {
  1773. if (insertPoint == (int)currentValue.size()) return;
  1774. insertionState(insertPoint + 1, 1);
  1775. }
  1776. // (sets dirty)
  1777. pasteText(blankString);
  1778. }
  1779. void WTextBox::copyText(string& buffer) const { start_func
  1780. if (selectionEnd == selectionBegin) buffer = blankString;
  1781. else buffer = currentValue.substr(selectionBegin, selectionEnd - selectionBegin);
  1782. }
  1783. int WTextBox::insertionState(int newPos, int drag) { start_func
  1784. if (newPos >= 0) {
  1785. // Clip on right
  1786. if (newPos > (int)currentValue.size()) newPos = currentValue.size();
  1787. // Drag?
  1788. if (drag) {
  1789. // No current selection?
  1790. if (selectionBegin == selectionEnd) {
  1791. selectionBegin = selectionEnd = insertPoint;
  1792. }
  1793. // If selection beginning matches insert point
  1794. if (selectionBegin == insertPoint) {
  1795. // Drag beginning point
  1796. selectionBegin = insertPoint = newPos;
  1797. }
  1798. else {
  1799. // Drag end point
  1800. selectionEnd = insertPoint = newPos;
  1801. }
  1802. // No selection?
  1803. if (selectionBegin == selectionEnd) {
  1804. selectionBegin = selectionEnd = 0;
  1805. }
  1806. else {
  1807. // Ensure in proper order
  1808. if (selectionEnd < selectionBegin) {
  1809. swap(selectionBegin, selectionEnd);
  1810. }
  1811. // Determine point positions
  1812. selectionBeginX = fontWidth(currentValue.substr(0, selectionBegin));
  1813. selectionEndX = fontWidth(currentValue.substr(0, selectionEnd));
  1814. }
  1815. }
  1816. else {
  1817. // Reset selection
  1818. selectionBegin = 0;
  1819. selectionEnd = 0;
  1820. selectionBeginX = 0;
  1821. selectionEndX = 0;
  1822. // Insertion point
  1823. insertPoint = newPos;
  1824. }
  1825. // Insertion point position
  1826. insertPointX = fontWidth(currentValue.substr(0, insertPoint));
  1827. // Scroll?
  1828. int rightMargin = GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN + GUI_TEXTBOX_RIGHTPAD;
  1829. int fullWidth = fontWidth(currentValue);
  1830. if ((textX + scrollPointX + insertPointX) > (width - rightMargin - GUI_TEXTBOX_SCROLLAHEAD)) {
  1831. scrollPointX = width - rightMargin - GUI_TEXTBOX_SCROLLAHEAD - insertPointX - textX;
  1832. }
  1833. if ((fullWidth + scrollPointX - width + rightMargin + textX) < 0) {
  1834. scrollPointX = width - rightMargin - textX - fullWidth;
  1835. }
  1836. if ((scrollPointX + insertPointX) < GUI_TEXTBOX_SCROLLAHEAD) {
  1837. scrollPointX = GUI_TEXTBOX_SCROLLAHEAD - insertPointX;
  1838. }
  1839. if (scrollPointX > 0) scrollPointX = 0;
  1840. // Dirty
  1841. setDirtyAndBlink();
  1842. }
  1843. return insertPoint;
  1844. }
  1845. void WTextBox::pasteText(const string& text) { start_func
  1846. string newText = currentValue;
  1847. int newPoint = insertPoint;
  1848. // First, clear selection, if any
  1849. if (selectionBegin != selectionEnd) {
  1850. newText.erase(selectionBegin, selectionEnd - selectionBegin);
  1851. newPoint = selectionBegin;
  1852. }
  1853. // Insert text
  1854. newText.insert(newPoint, text);
  1855. newPoint += text.size();
  1856. // Validate
  1857. if ((int)newText.size() <= maxSize) {
  1858. if (allowChange(currentValue, &newText, insertPoint, &newPoint)) {
  1859. currentValue = newText;
  1860. selectionBegin = selectionEnd = 0;
  1861. // Move insertion point (and sets dirty)
  1862. insertionState(newPoint);
  1863. }
  1864. }
  1865. parent->childModified(this);
  1866. }
  1867. int WTextBox::insertWhere(int x) const { start_func
  1868. // Adjust by insertion point width for a bit of a fudge
  1869. x -= textX + scrollPointX - GUI_TEXTBOX_INSERTPOINTWIDTH;
  1870. for (int pos = currentValue.size(); pos > 0; --pos) {
  1871. if (fontWidth(currentValue.substr(0, pos)) < x) return pos;
  1872. }
  1873. return 0;
  1874. }
  1875. void WTextBox::tabAction() { start_func
  1876. selectAll();
  1877. }
  1878. // Numberbox widget
  1879. WNumberBox::WNumberBox(int tId, int* tSetting, int tMin, int tMax) : WTextBox(tId, NULL, 0) { start_func
  1880. assert(tMin <= tMax);
  1881. setting = tSetting;
  1882. min = tMin;
  1883. max = tMax;
  1884. // Determine maximum size needed
  1885. currentValue = intToStr(min);
  1886. maxSize = currentValue.size();
  1887. currentValue = intToStr(max);
  1888. if ((int)currentValue.size() > maxSize) maxSize = currentValue.size();
  1889. string standard(maxSize, 'X');
  1890. resize(fontWidth(standard) + (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN) * 2 + GUI_TEXTBOX_LEFTPAD + GUI_TEXTBOX_RIGHTPAD,
  1891. fontHeight() + (GUI_TEXTBOX_BORDERTHICKNESS + GUI_TEXTBOX_INNERMARGIN) * 2);
  1892. }
  1893. WNumberBox::~WNumberBox() { start_func
  1894. }
  1895. int WNumberBox::entryValid() const { start_func
  1896. // Make sure numeric
  1897. int valid = 1;
  1898. int current;
  1899. try {
  1900. current = strToIntErr(currentValue);
  1901. }
  1902. catch (int) {
  1903. valid = 0;
  1904. }
  1905. if ((current < min) || (current > max)) valid = 0;
  1906. if (!valid) {
  1907. string prompt = formatString("Please enter a value between %d and %d", min, max);
  1908. guiErrorBox(prompt, errorTitleInvalidEntry);
  1909. return 0;
  1910. }
  1911. return 1;
  1912. }
  1913. int WNumberBox::event(int hasFocus, const SDL_Event* event) { start_func
  1914. assert(event);
  1915. assert(parent);
  1916. Sint32 key;
  1917. switch (event->type) {
  1918. case SDL_KEYDOWN:
  1919. key = combineKey(event->key.keysym.sym, event->key.keysym.mod & ~KMOD_SHIFT);
  1920. long current;
  1921. long prevCurrent;
  1922. int did = 0;
  1923. // Make sure numeric
  1924. try {
  1925. current = strToIntErr(currentValue);
  1926. }
  1927. catch (int) {
  1928. current = min;
  1929. }
  1930. prevCurrent = current;
  1931. switch (key) {
  1932. case SDLK_DOWN:
  1933. did = 1;
  1934. --current;
  1935. break;
  1936. case SDLK_UP:
  1937. did = 1;
  1938. ++current;
  1939. break;
  1940. case combineKey(SDLK_DOWN, KMOD_CTRL):
  1941. case SDLK_PAGEDOWN:
  1942. did = 1;
  1943. current -= 10;
  1944. break;
  1945. case combineKey(SDLK_UP, KMOD_CTRL):
  1946. case SDLK_PAGEUP:
  1947. did = 1;
  1948. current += 10;
  1949. break;
  1950. }
  1951. if (did) {
  1952. if (current > max) current = max;
  1953. if (current < min) current = min;
  1954. if (prevCurrent != current) {
  1955. saveUndo(UNDO_TYPING);
  1956. currentValue = intToStr(current);
  1957. parent->childModified(this);
  1958. insertionState(currentValue.size(), 0);
  1959. }
  1960. return 1;
  1961. }
  1962. break;
  1963. }
  1964. return WTextBox::event(hasFocus, event);
  1965. }
  1966. void WNumberBox::load() { start_func
  1967. currentValue = intToStr(*((int*)setting));
  1968. undoValue = blankString;
  1969. undoType = UNDO_NONE;
  1970. insertPoint = 0;
  1971. selectionBegin = 0;
  1972. selectionEnd = 0;
  1973. insertPointX = 0;
  1974. selectionBeginX = 0;
  1975. selectionEndX = 0;
  1976. scrollPointX = 0;
  1977. setDirty();
  1978. }
  1979. void WNumberBox::apply() { start_func
  1980. *((int*)setting) = strToInt(currentValue);
  1981. }
  1982. int WNumberBox::state() const { start_func
  1983. return strToInt(currentValue);
  1984. }
  1985. int WNumberBox::state(int newValue) { start_func
  1986. string newStr = intToStr(newValue);
  1987. // (sets dirty)
  1988. selectAll();
  1989. pasteText(newStr);
  1990. return strToInt(currentValue);
  1991. }
  1992. int WNumberBox::allowChange(const string& oldValue, string* newValue, int oldCursorPos, int* newCursorPos) const { start_func
  1993. // Blank is specifically OK
  1994. if (newValue->empty()) return 1;
  1995. // + or - is OK if that range is enabled
  1996. if ((newValue->size() == 1) && ((*newValue)[0] == '+') && (max > 0)) return 1;
  1997. if ((newValue->size() == 1) && ((*newValue)[0] == '-') && (min < 0)) return 1;
  1998. long current;
  1999. // Make sure numeric
  2000. try {
  2001. current = strToIntErr(*newValue);
  2002. }
  2003. catch (int) {
  2004. return 0;
  2005. }
  2006. // Any number from 1 to max is ok if max is positive
  2007. if ((max > 0) && (current > 0) && (current <= max)) return 1;
  2008. // Any number from -1 to min is ok if min is negative
  2009. if ((min < 0) && (current < 0) && (current >= min)) return 1;
  2010. // Zero is ok if zero is allowed
  2011. if ((current == 0) && ((min <= 0) || (max >= 0))) return 1;
  2012. return 0;
  2013. }