gcsx_popup.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. /* GCSx
  2. ** POPUP.CPP
  3. **
  4. ** Popup and drop-down menus
  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. const char* PopupItem::accelLabelsLower[ACCEL_LABEL_LOWER_LAST - ACCEL_LABEL_LOWER_FIRST + 1] = {
  25. // (starts at 8)
  26. "Backspace",
  27. "Tab",
  28. NULL, NULL, NULL, // 10-12
  29. "Enter",
  30. NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 14-21
  31. NULL, NULL, NULL, NULL, NULL, // 22-26
  32. "Escape",
  33. NULL, NULL, NULL, NULL, // 28-31
  34. "Space",
  35. "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", "<", "-", ">", "/",
  36. "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
  37. ":", ";", "<", "+", ">", "?", "@",
  38. "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
  39. "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
  40. "[", "\\", "]", "^", "_", "`",
  41. "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
  42. "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
  43. "{", "|", "}", "~",
  44. "Delete"
  45. };
  46. const char* PopupItem::accelLabelsUpper[ACCEL_LABEL_UPPER_LAST - ACCEL_LABEL_UPPER_FIRST + 1] = {
  47. // (starts at 273)
  48. "Up", "Down", "Right", "Left",
  49. "Insert", "Home", "End", "PageUp", "PageDown",
  50. "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
  51. "F13", "F14", "F15"
  52. };
  53. const string PopupMenu::wtSubmenu("e");
  54. const string PopupMenu::emptyItem("(empty)");
  55. int PopupMenu::checkboxSize = 0;
  56. void PopupItem::prepAccellabel(Sint32 pKey) { start_func
  57. const char* key = NULL;
  58. int pKeyMod = (pKey >> 16) & 0xFFFF;
  59. int pKeySym = pKey & 0xFFFF;
  60. if ((pKeySym >= ACCEL_LABEL_LOWER_FIRST) && (pKeySym <= ACCEL_LABEL_LOWER_LAST)) {
  61. key = accelLabelsLower[pKeySym - ACCEL_LABEL_LOWER_FIRST];
  62. }
  63. else if ((pKeySym >= ACCEL_LABEL_UPPER_FIRST) && (pKeySym <= ACCEL_LABEL_UPPER_LAST)) {
  64. key = accelLabelsUpper[pKeySym - ACCEL_LABEL_UPPER_FIRST];
  65. }
  66. if (key) {
  67. accelLabel = "(";
  68. if (pKeyMod & KMOD_SHIFT) accelLabel += "Shift+";
  69. if (pKeyMod & KMOD_CTRL) accelLabel += "Ctrl+";
  70. if (pKeyMod & KMOD_ALT) accelLabel += "Alt+";
  71. accelLabel += key;
  72. accelLabel += ")";
  73. }
  74. else {
  75. accelLabel = blankString;
  76. }
  77. // Certain keys become modified for actual accellerator
  78. // Letters always lowercase
  79. if ((pKeySym >= 'A') && (pKeySym <= 'Z')) pKeySym += 'a' - 'A';
  80. else {
  81. // Punctuation on 0-9 becomes shifted 0-9
  82. // Punctuation on other keys becomes unshifted version
  83. switch (pKeySym) {
  84. case '!': pKeySym = 1; pKeyMod |= KMOD_SHIFT; break;
  85. case '@': pKeySym = 2; pKeyMod |= KMOD_SHIFT; break;
  86. case '#': pKeySym = 3; pKeyMod |= KMOD_SHIFT; break;
  87. case '$': pKeySym = 4; pKeyMod |= KMOD_SHIFT; break;
  88. case '%': pKeySym = 5; pKeyMod |= KMOD_SHIFT; break;
  89. case '^': pKeySym = 6; pKeyMod |= KMOD_SHIFT; break;
  90. case '&': pKeySym = 7; pKeyMod |= KMOD_SHIFT; break;
  91. case '*': pKeySym = 8; pKeyMod |= KMOD_SHIFT; break;
  92. case '(': pKeySym = 9; pKeyMod |= KMOD_SHIFT; break;
  93. case ')': pKeySym = 0; pKeyMod |= KMOD_SHIFT; break;
  94. case '~': pKeySym = '`'; break;
  95. case '_': pKeySym = '-'; break;
  96. case '+': pKeySym = '='; break;
  97. case '{': pKeySym = '['; break;
  98. case '}': pKeySym = ']'; break;
  99. case '|': pKeySym = '\\'; break;
  100. case ':': pKeySym = ';'; break;
  101. case '"': pKeySym = '\''; break;
  102. case '<': pKeySym = ','; break;
  103. case '>': pKeySym = '.'; break;
  104. case '?': pKeySym = '/'; break;
  105. }
  106. }
  107. accel = pKey;
  108. }
  109. const string& PopupItem::getLabel() const { start_func
  110. return label;
  111. }
  112. void PopupItem::setLabel(const string& newLabel) { start_func
  113. label = newLabel;
  114. underline = convertGuiText(&label, &shortcut);
  115. if (label.length() > POPUP_MAX_WIDTH) label = label.substr(0, POPUP_MAX_WIDTH) + "...";
  116. }
  117. PopupItem::PopupItem() { start_func
  118. }
  119. PopupItem::PopupItem(int pCode, const char* pLabel, Sint32 pKey, int dynamic, class PopupMenu* pSubmenu) { start_func
  120. code = pCode;
  121. if (pLabel == NULL) {
  122. label = "";
  123. state = POPUP_SEPARATOR;
  124. }
  125. else {
  126. setLabel(pLabel);
  127. state = POPUP_NONE;
  128. }
  129. if (dynamic) state = (PopupState)(state | POPUP_DYNAMIC);
  130. submenu = pSubmenu;
  131. prepAccellabel(pKey);
  132. }
  133. PopupMenu::PopupMenu(int menubar) : Window(), items() { start_func
  134. isMenubar = menubar;
  135. fromMenubar = 0;
  136. numItems = 0;
  137. checkboxSize = fontHeight() * 4 / 5;
  138. }
  139. void PopupMenu::add(PopupItem& item) { start_func
  140. items.push_back(item);
  141. ++numItems;
  142. }
  143. PopupMenu::~PopupMenu() { start_func
  144. desktop->removeWindow(this);
  145. vector<PopupItem>::iterator end = items.end();
  146. for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos) {
  147. delete (*pos).submenu;
  148. }
  149. }
  150. #ifndef NDEBUG
  151. const char* PopupMenu::debugDump() const { start_func
  152. return (isMenubar ? "MENUBAR" : "POPUP");
  153. }
  154. #endif
  155. void PopupMenu::predraw(int xPos, int yPos) { start_func
  156. currSel = -1;
  157. int newWidth = 0;
  158. int newHeight = GUI_POPUP_EDGEPAD;
  159. int largestItem = 0;
  160. int largestAccel = 0;
  161. if (isMenubar) {
  162. xPos = 0;
  163. yPos = 0;
  164. newWidth = screenWidth;
  165. newHeight = GUI_MENUBAR_TOPPAD + GUI_MENUBAR_BOTTOMPAD + fontHeight(FONT_MENU);
  166. isEmpty = 0;
  167. int xPos = GUI_MENUBAR_SIDEPAD + GUI_MENUBAR_LEFTPAD;
  168. vector<PopupItem>::iterator end = items.end();
  169. for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos) {
  170. (*pos).pos = xPos;
  171. xPos += GUI_MENUBAR_SIDEPAD * 2 + fontWidth((*pos).getLabel(), FONT_MENU);
  172. }
  173. }
  174. else {
  175. // Calculate size, determine disabled and checkbox states and Y positions
  176. Window* win = desktop->findPreviousFocusWindow();
  177. // Hide any separators at top of menu
  178. int hideSeparator = -1;
  179. int numPos = 0;
  180. vector<PopupItem>::iterator end = items.end();
  181. for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
  182. PopupItem& currItem = *pos;
  183. if (currItem.state & PopupItem::POPUP_DYNAMIC) {
  184. // Clear all temporary states
  185. currItem.state = PopupItem::POPUP_DYNAMIC;
  186. Window::CommandSupport support = Window::COMMAND_HIDE;
  187. // Window, if any, gets to "vote" on this item
  188. if (win) support = win->supportsCommand(currItem.code);
  189. // Desktop calls global handler to "vote" on this item also
  190. support = desktop->supportsCommand(currItem.code, support);
  191. // Hidden?
  192. if (support == Window::COMMAND_HIDE) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_HIDDEN);
  193. // Grey?
  194. if (!(support & Window::COMMAND_ENABLE)) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_GRAYED);
  195. // Add check/radio?
  196. if (support & Window::COMMAND_CHECKBOX) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_CHECKBOX);
  197. if (support & Window::COMMAND_RADIO) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_RADIO);
  198. if (support & Window::COMMAND_SELECTED) currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_CHECKED);
  199. }
  200. // Y position
  201. currItem.pos = newHeight;
  202. if (currItem.state & PopupItem::POPUP_SEPARATOR) {
  203. if (hideSeparator) {
  204. // Hide
  205. currItem.state = (PopupItem::PopupState)(currItem.state | PopupItem::POPUP_HIDDEN);
  206. continue;
  207. }
  208. // Ensure unhidden
  209. currItem.state = (PopupItem::PopupState)(currItem.state & ~PopupItem::POPUP_HIDDEN);
  210. newHeight += GUI_POPUP_SEPHEIGHT;
  211. // Hide any separators after this one; note which separator was last
  212. // (this will never be 0, because a separator at pos 0 would be hidden)
  213. hideSeparator = numPos;
  214. }
  215. else if (currItem.state & PopupItem::POPUP_HIDDEN) continue;
  216. else {
  217. newHeight += fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD;
  218. // Real item- can show separators again
  219. hideSeparator = 0;
  220. }
  221. int size = fontWidth(currItem.getLabel(), FONT_MENU);
  222. if (size > largestItem) largestItem = size;
  223. size = fontWidth(currItem.accelLabel, FONT_MENU);
  224. if (size > largestAccel) largestAccel = size;
  225. }
  226. // Hide last separator?
  227. if (hideSeparator > 0) {
  228. items[hideSeparator].state = (PopupItem::PopupState)(items[hideSeparator].state | PopupItem::POPUP_HIDDEN);
  229. newHeight -= GUI_POPUP_SEPHEIGHT;
  230. }
  231. if (newHeight > GUI_POPUP_EDGEPAD) {
  232. isEmpty = 0;
  233. if (largestAccel) newWidth = largestItem + GUI_POPUP_GUTTER + largestAccel;
  234. else newWidth = largestItem;
  235. accelOffset = largestItem + GUI_POPUP_GUTTER;
  236. }
  237. else {
  238. // If no items, create room for one (empty) fake item
  239. isEmpty = 1;
  240. newWidth = fontWidth(emptyItem, FONT_MENU);
  241. newHeight += fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD;
  242. accelOffset = 0;
  243. }
  244. newWidth += GUI_POPUP_EDGEPAD * 2 + GUI_POPUP_MARGIN * 2;
  245. newHeight += GUI_POPUP_EDGEPAD + GUI_POPUP_LINEPAD;
  246. // Move as needed
  247. if (xPos + newWidth > screenWidth) xPos = screenWidth - newWidth;
  248. if (yPos + newHeight > screenHeight) yPos = screenHeight - newHeight;
  249. if (xPos < 0) xPos = 0;
  250. if (yPos < 0) yPos = 0;
  251. // Checkbox size/position
  252. checkboxX = (GUI_POPUP_MARGIN - fontHeight(FONT_WIDGET)) / 2;
  253. checkboxY = fontAscent(FONT_MENU) - fontAscent(FONT_WIDGET);
  254. }
  255. move(xPos, yPos);
  256. resize(newWidth, newHeight);
  257. }
  258. void PopupMenu::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  259. assert(destSurface);
  260. if (visible) {
  261. // If dirty, redraw all
  262. if (dirty) {
  263. getRect(toDisplay);
  264. toDisplay.x += xOffset;
  265. toDisplay.y += yOffset;
  266. dirty = 0;
  267. intersectRects(toDisplay, clipArea);
  268. }
  269. // Anything to draw?
  270. if (toDisplay.w) {
  271. SDL_SetClipRect(destSurface, &toDisplay);
  272. xOffset += x;
  273. yOffset += y;
  274. // This doesn't attempt to skip items outside dirty area, but they
  275. // will be clipped away when drawn.
  276. // Background
  277. if (isMenubar) {
  278. drawRect(xOffset, yOffset, width, height - 2, guiPacked[COLOR_FILL], destSurface);
  279. drawHLine(xOffset, yOffset + width - 1, height - 2, guiPacked[COLOR_DARK1], destSurface);
  280. drawHLine(xOffset, yOffset + width - 1, height - 1, guiPacked[COLOR_DARK2], destSurface);
  281. }
  282. else {
  283. drawGuiBox(xOffset, yOffset, width, height, 2, destSurface);
  284. }
  285. // Draw all popup entries we can see
  286. if (isEmpty) {
  287. drawText(emptyItem, guiRGB[COLOR_LIGHT1], xOffset + GUI_POPUP_EDGEPAD + GUI_POPUP_MARGIN + 1, yOffset + GUI_POPUP_EDGEPAD + 1, destSurface, FONT_MENU);
  288. drawText(emptyItem, guiRGB[COLOR_DARK1], xOffset + GUI_POPUP_EDGEPAD + GUI_POPUP_MARGIN, yOffset + GUI_POPUP_EDGEPAD, destSurface, FONT_MENU);
  289. }
  290. else {
  291. int numPos = 0;
  292. vector<PopupItem>::iterator end = items.end();
  293. for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
  294. PopupItem& currItem = *pos;
  295. if (currItem.state & PopupItem::POPUP_HIDDEN) continue;
  296. int xPos, yPos, xOffs;
  297. if (isMenubar) {
  298. xPos = xOffset + currItem.pos;
  299. xOffs = 0;
  300. yPos = yOffset + GUI_MENUBAR_TOPPAD;
  301. }
  302. else {
  303. xPos = xOffset + GUI_POPUP_EDGEPAD;
  304. xOffs = GUI_POPUP_MARGIN;
  305. yPos = yOffset + currItem.pos;
  306. }
  307. // Separators
  308. if (currItem.state & PopupItem::POPUP_SEPARATOR) {
  309. drawGuiBoxInvert(xPos, yPos + GUI_POPUP_SEPHEIGHT / 2, width - GUI_POPUP_EDGEPAD * 2, GUI_POPUP_SEPLINE, 1, destSurface);
  310. }
  311. else {
  312. // Border of selection
  313. if (currSel == numPos) {
  314. if (isMenubar) {
  315. // Non-gradient: nothing
  316. drawGradient(xPos - GUI_MENUBAR_SIDEPAD, yPos, fontWidth(currItem.getLabel(), FONT_MENU) + GUI_MENUBAR_SIDEPAD * 2, fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD + 1, guiRGB[COLOR_POPUPFILL1], guiRGB[COLOR_POPUPFILL2], destSurface);
  317. drawPixel(xPos - GUI_MENUBAR_SIDEPAD + fontWidth(currItem.getLabel(), FONT_MENU) + GUI_MENUBAR_SIDEPAD * 2 - 1, yPos, guiPacked[COLOR_FILL], destSurface);
  318. drawPixel(xPos - GUI_MENUBAR_SIDEPAD, yPos + fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD, guiPacked[COLOR_FILL], destSurface);
  319. drawGuiBoxInvert(xPos - GUI_MENUBAR_SIDEPAD, yPos, fontWidth(currItem.getLabel(), FONT_MENU) + GUI_MENUBAR_SIDEPAD * 2, fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD + 1, 1, destSurface);
  320. }
  321. else {
  322. // Non-gradient: nothing
  323. drawGradient(xPos, yPos, width - GUI_POPUP_EDGEPAD * 2, fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD + 1, guiRGB[COLOR_POPUPFILL1], guiRGB[COLOR_POPUPFILL2], destSurface);
  324. drawPixel(xPos + width - GUI_POPUP_EDGEPAD * 2 - 1, yPos, guiPacked[COLOR_FILL], destSurface);
  325. drawPixel(xPos, yPos + fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD, guiPacked[COLOR_FILL], destSurface);
  326. drawGuiBoxInvert(xPos, yPos, width - GUI_POPUP_EDGEPAD * 2, fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD + 1, 1, destSurface);
  327. }
  328. }
  329. // Text, including checkbox/arrow/underline
  330. if (currItem.state & PopupItem::POPUP_GRAYED) {
  331. if (currItem.state & PopupItem::POPUP_CHECKBOX) {
  332. drawCheckbox(0, currItem.state & PopupItem::POPUP_CHECKED, 1, xPos + checkboxX, yPos + checkboxY, checkboxSize, destSurface);
  333. }
  334. else if (currItem.state & PopupItem::POPUP_RADIO) {
  335. drawCheckbox(1, currItem.state & PopupItem::POPUP_CHECKED, 1, xPos + checkboxX, yPos + checkboxY, checkboxSize, destSurface);
  336. }
  337. if ((currItem.submenu != NULL) && (!isMenubar)) {
  338. drawText(wtSubmenu, guiRGB[COLOR_LIGHT1], xPos + width - checkboxX - GUI_POPUP_EDGEPAD * 2 - fontWidth(wtSubmenu, FONT_WIDGET), yPos + checkboxY + 1, destSurface, FONT_WIDGET);
  339. drawText(wtSubmenu, guiRGB[COLOR_DARK1], xPos + width - checkboxX - GUI_POPUP_EDGEPAD * 2 - fontWidth(wtSubmenu, FONT_WIDGET), yPos + checkboxY, destSurface, FONT_WIDGET);
  340. }
  341. if (currSel != numPos) {
  342. drawText(currItem.getLabel(), guiRGB[COLOR_LIGHT1], xPos + xOffs + 1, yPos + 1, destSurface, FONT_MENU);
  343. drawText(currItem.accelLabel, guiRGB[COLOR_LIGHT1], xPos + xOffs + 1 + accelOffset, yPos + 1, destSurface, FONT_MENU);
  344. }
  345. drawText(currItem.getLabel(), guiRGB[COLOR_DARK1], xPos + xOffs, yPos, destSurface, FONT_MENU);
  346. drawText(currItem.accelLabel, guiRGB[COLOR_DARK1], xPos + xOffs + accelOffset, yPos, destSurface, FONT_MENU);
  347. }
  348. else {
  349. if (currItem.state & PopupItem::POPUP_CHECKBOX) {
  350. drawCheckbox(0, currItem.state & PopupItem::POPUP_CHECKED, 0, xPos + checkboxX, yPos + checkboxY, checkboxSize, destSurface);
  351. }
  352. else if (currItem.state & PopupItem::POPUP_RADIO) {
  353. drawCheckbox(1, currItem.state & PopupItem::POPUP_CHECKED, 0, xPos + checkboxX, yPos + checkboxY, checkboxSize, destSurface);
  354. }
  355. if ((currItem.submenu != NULL) && (!isMenubar)) {
  356. drawText(wtSubmenu, guiRGB[currSel == numPos ? COLOR_POPUPTEXT : COLOR_TEXT], xPos + width - checkboxX - GUI_POPUP_EDGEPAD * 2 - fontWidth(wtSubmenu, FONT_WIDGET), yPos + checkboxY, destSurface, FONT_WIDGET);
  357. }
  358. drawText(currItem.getLabel(), guiRGB[currSel == numPos ? COLOR_POPUPTEXT : COLOR_TEXT], xPos + xOffs, yPos, destSurface, FONT_MENU);
  359. drawText(currItem.accelLabel, guiRGB[currSel == numPos ? COLOR_POPUPTEXT : COLOR_TEXT], xPos + xOffs + accelOffset, yPos, destSurface, FONT_MENU);
  360. if (currItem.underline) drawTextUnderline(currItem.underline, guiPacked[currSel == numPos ? COLOR_POPUPTEXT : COLOR_TEXT], xPos + xOffs, yPos, destSurface, FONT_MENU);
  361. }
  362. }
  363. }
  364. }
  365. }
  366. }
  367. }
  368. // (close all open (popup) submenus at a certain level or higher)
  369. int PopupMenu::closeAllSubmenus(int level) { start_func
  370. PopupMenu* next;
  371. int pos = 0;
  372. int count = 0;
  373. // (assignment intentional)
  374. while ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_POPUPMENU, pos))) ) {
  375. if (next->submenuLevel >= level) {
  376. desktop->removeWindow(next);
  377. next->resize(0, 0);
  378. ++count;
  379. }
  380. else ++pos;
  381. }
  382. return count;
  383. }
  384. void PopupMenu::popup(int xPos, int yPos, int newSubmenuLevel, int isFromMenubar) { start_func
  385. assert(!isMenubar);
  386. submenuLevel = newSubmenuLevel;
  387. fromMenubar = isFromMenubar;
  388. predraw(xPos, yPos);
  389. setDirty();
  390. desktop->addWindow(this, 1);
  391. }
  392. void PopupMenu::menubar(int show) { start_func
  393. assert(isMenubar);
  394. submenuLevel = -1;
  395. if (show) {
  396. predraw(0, 0);
  397. setDirty();
  398. desktop->addWindow(this);
  399. }
  400. else {
  401. desktop->removeWindow(this);
  402. resize(0, 0);
  403. }
  404. }
  405. int PopupMenu::wantsToBeDeleted() const { start_func
  406. return !isMenubar;
  407. }
  408. void PopupMenu::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
  409. if (isMenubar) {
  410. if (visible) predraw(0, 0);
  411. }
  412. else {
  413. Window::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
  414. }
  415. }
  416. int PopupMenu::event(int hasFocus, const SDL_Event* event) { start_func
  417. assert(event);
  418. int prevSel = currSel;
  419. int expand = 0;
  420. int select = 0;
  421. int item = -1;
  422. Sint32 key = 0;
  423. int used = 0;
  424. switch (event->type) {
  425. case SDL_CLOSE:
  426. if (isMenubar) {
  427. // Close all submenus and ourself
  428. closeAllSubmenus(0);
  429. menubar(0);
  430. }
  431. else {
  432. // Close all submenus at or below our submenu level
  433. closeAllSubmenus(submenuLevel);
  434. }
  435. return 1;
  436. case SDL_MOUSEBUTTONDBL:
  437. case SDL_MOUSEBUTTONDOWN:
  438. case SDL_MOUSEBUTTONUP:
  439. if (event->button.button != SDL_BUTTON_LEFT) return 0;
  440. case SDL_MOUSEMOTION:
  441. int targetX, targetY;
  442. if (event->type == SDL_MOUSEMOTION) {
  443. targetX = event->motion.x;
  444. targetY = event->motion.y;
  445. }
  446. else {
  447. targetX = event->button.x;
  448. targetY = event->button.y;
  449. }
  450. if (isMenubar) {
  451. // Don't cancel selection on menubar if a popup is open
  452. if ((desktop->findFocusWindow() == NULL) || (desktop->findFocusWindow()->windowType() != WINDOW_POPUPMENU)) currSel = -1;
  453. if ((targetY > GUI_MENUBAR_TOPPAD) && (targetY <= fontHeight(FONT_MENU) + GUI_MENUBAR_TOPPAD + GUI_POPUP_LINEPAD)) {
  454. int row = targetX -= GUI_MENUBAR_LEFTPAD;
  455. if (row >= 0) {
  456. int numPos = 0;
  457. vector<PopupItem>::iterator end = items.end();
  458. for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
  459. PopupItem& currItem = *pos;
  460. if (currItem.state & PopupItem::POPUP_HIDDEN) continue;
  461. int size = fontWidth(currItem.getLabel(), FONT_MENU) + GUI_MENUBAR_SIDEPAD * 2;
  462. if (row < size) {
  463. currSel = numPos;
  464. break;
  465. }
  466. else {
  467. row -= size;
  468. }
  469. }
  470. }
  471. }
  472. // If nothing selected, refuse focus
  473. if (currSel == -1) {
  474. desktop->sendToBottom(this);
  475. }
  476. }
  477. else {
  478. currSel = -1;
  479. if ((targetX > GUI_POPUP_EDGEPAD) && (targetX < width - GUI_POPUP_EDGEPAD)) {
  480. int row = targetY - GUI_POPUP_EDGEPAD;
  481. if (row >= 0) {
  482. int numPos = 0;
  483. vector<PopupItem>::iterator end = items.end();
  484. for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
  485. PopupItem& currItem = *pos;
  486. if (currItem.state & PopupItem::POPUP_HIDDEN) continue;
  487. if (currItem.state & PopupItem::POPUP_SEPARATOR) {
  488. if (row < GUI_POPUP_SEPHEIGHT) break;
  489. row -= GUI_POPUP_SEPHEIGHT;
  490. }
  491. else {
  492. if (row < (fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD)) {
  493. currSel = numPos;
  494. break;
  495. }
  496. else {
  497. row -= fontHeight(FONT_MENU) + GUI_POPUP_LINEPAD;
  498. }
  499. }
  500. }
  501. }
  502. }
  503. }
  504. if ((event->type == SDL_MOUSEBUTTONDOWN) && (event->button.button == SDL_BUTTON_LEFT)) {
  505. expand = 1;
  506. }
  507. if ((event->type == SDL_MOUSEBUTTONUP) && (event->button.button == SDL_BUTTON_LEFT)) {
  508. expand = 1;
  509. if (currSel != -1) select = 1;
  510. }
  511. used = 1;
  512. break;
  513. case SDL_MOUSEFOCUS:
  514. if (event->user.code & 1) return 0;
  515. else {
  516. // Cancel selection unless this is menubar and a popup is open
  517. if ((!isMenubar) || (desktop->findFocusWindow() == NULL) || (desktop->findFocusWindow()->windowType() != WINDOW_POPUPMENU)) currSel = -1;
  518. // Refuse input focus if menubar
  519. if (isMenubar) desktop->sendToBottom(this);
  520. }
  521. used = 1;
  522. break;
  523. case SDL_INPUTFOCUS:
  524. if (event->user.code & 1) return 0;
  525. else {
  526. if ((desktop->findFocusWindow() == NULL) ||
  527. ((desktop->findFocusWindow()->windowType() != WINDOW_POPUPMENU) &&
  528. (desktop->findFocusWindow()->windowType() != WINDOW_MENUBAR))
  529. ) {
  530. currSel = -1;
  531. select = 1;
  532. }
  533. }
  534. used = 1;
  535. break;
  536. case SDL_SYSKEY:
  537. // If ALT being held and we're the menubar, scan for shortcut
  538. if ((event->key.keysym.mod == KMOD_ALT) && (isMenubar)) {
  539. key = event->key.keysym.sym;
  540. }
  541. case SDL_KEYDOWN:
  542. if (event->type != SDL_SYSKEY) key = event->key.keysym.sym;
  543. key = combineKey(key, event->key.keysym.mod);
  544. // Translate keys for menubar
  545. if (isMenubar) {
  546. switch (key) {
  547. case SDLK_DOWN:
  548. key = SDLK_RIGHT;
  549. break;
  550. case SDLK_LEFT:
  551. key = SDLK_UP;
  552. break;
  553. case SDLK_RIGHT:
  554. key = SDLK_DOWN;
  555. break;
  556. }
  557. }
  558. switch (key) {
  559. case SDLK_ESCAPE:
  560. // If menubar, no current selection, and no submenus close,
  561. // ignore key (allows ESC to filter through in game mode)
  562. if ((isMenubar) && (currSel == -1) && (closeAllSubmenus(0) == 0))
  563. break;
  564. used = 1;
  565. item = 0;
  566. break;
  567. case SDLK_UP:
  568. used = 1;
  569. do {
  570. if (currSel < 1) currSel = numItems - 1;
  571. else --currSel;
  572. if (currSel == prevSel) break;
  573. } while (items[currSel].state & (PopupItem::POPUP_SEPARATOR | PopupItem::POPUP_HIDDEN));
  574. break;
  575. case SDLK_DOWN:
  576. if (isEmpty) return 0;
  577. used = 1;
  578. do {
  579. if (currSel < 0) currSel = 0;
  580. else if (++currSel >= numItems) currSel = 0;
  581. if (currSel == prevSel) break;
  582. } while (items[currSel].state & (PopupItem::POPUP_SEPARATOR | PopupItem::POPUP_HIDDEN));
  583. break;
  584. case SDLK_RIGHT:
  585. used = 1;
  586. expand = 1;
  587. if (currSel < 0) expand = 0;
  588. else if (items[currSel].submenu == NULL) expand = 0;
  589. if ((!expand) && (fromMenubar)) {
  590. // Go to one item right on menubar
  591. closeAllSubmenus(0);
  592. PopupMenu* next;
  593. // (assignment intentional)
  594. if ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_MENUBAR, 0))) ) {
  595. SDL_Event myEvent = *event;
  596. myEvent.key.keysym.sym = SDLK_DOWN;
  597. // Activate, send RIGHT, then DOWN
  598. desktop->bringToTop(next);
  599. next->event(0, event);
  600. return next->event(0, &myEvent);
  601. }
  602. return 1;
  603. }
  604. break;
  605. case SDLK_LEFT:
  606. used = 1;
  607. if (submenuLevel >= 0) {
  608. // Activate submenu above this, if there is one
  609. PopupMenu* next;
  610. int pos = 0;
  611. // (assignment intentional)
  612. while ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_POPUPMENU, pos))) ) {
  613. if (next->submenuLevel == submenuLevel - 1) {
  614. // Close all submenus at or below our level
  615. closeAllSubmenus(submenuLevel);
  616. desktop->bringToTop(next);
  617. return 1;
  618. }
  619. else pos++;
  620. }
  621. // Go to one item left on menubar?
  622. if (fromMenubar) {
  623. closeAllSubmenus(0);
  624. // (assignment intentional)
  625. if ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_MENUBAR, 0))) ) {
  626. SDL_Event myEvent = *event;
  627. myEvent.key.keysym.sym = SDLK_DOWN;
  628. // Activate, send LEFT, then DOWN
  629. desktop->bringToTop(next);
  630. next->event(0, event);
  631. return next->event(0, &myEvent);
  632. }
  633. }
  634. return 1;
  635. }
  636. break;
  637. case SDLK_RETURN:
  638. case SDLK_KP_ENTER:
  639. used = 1;
  640. expand = 1;
  641. select = 1;
  642. break;
  643. default:
  644. // Look for appropriate shortcut key; remove ALT
  645. key &= ~(KMOD_ALT << 16);
  646. // (make sure there's still a key)
  647. if (key) {
  648. int numPos = 0;
  649. vector<PopupItem>::iterator end = items.end();
  650. for (vector<PopupItem>::iterator pos = items.begin(); pos != end; ++pos, ++numPos) {
  651. PopupItem& currItem = *pos;
  652. // Grayed includes Temphidden
  653. if (currItem.state & (PopupItem::POPUP_HIDDEN | PopupItem::POPUP_SEPARATOR | PopupItem::POPUP_GRAYED)) continue;
  654. if (currItem.shortcut == key) {
  655. used = 1;
  656. currSel = numPos;
  657. expand = 1;
  658. select = 1;
  659. break;
  660. }
  661. }
  662. }
  663. break;
  664. }
  665. break;
  666. default:
  667. return 0;
  668. }
  669. if (currSel != prevSel) {
  670. // Selection changed and not empty- close submenus, open this one if any closed
  671. if (currSel != -1) {
  672. if (closeAllSubmenus(submenuLevel + 1)) expand = 1;
  673. }
  674. setDirty();
  675. }
  676. if ((expand) && (!isEmpty) && (currSel >= 0) && (items[currSel].submenu != NULL) && (!(items[currSel].state & PopupItem::POPUP_GRAYED))) {
  677. // Close all submenus at or below the submenu level
  678. closeAllSubmenus(submenuLevel + 1);
  679. if (isMenubar) {
  680. items[currSel].submenu->popup(x + items[currSel].pos - GUI_MENUBAR_SIDEPAD, y + fontHeight(FONT_MENU) + GUI_MENUBAR_TOPPAD + GUI_POPUP_LINEPAD + 1, submenuLevel + 1, 1);
  681. }
  682. else {
  683. items[currSel].submenu->popup(x + width - GUI_POPUP_EDGEPAD, y + items[currSel].pos - GUI_POPUP_EDGEPAD, submenuLevel + 1, fromMenubar);
  684. }
  685. return 1;
  686. }
  687. else if (select) {
  688. if ((currSel < 0) || (isEmpty)) item = 0;
  689. else if (items[currSel].state & PopupItem::POPUP_GRAYED) item = 0;
  690. else item = items[currSel].code;
  691. }
  692. if (item >= 0) {
  693. // Close all popup menus at any level
  694. closeAllSubmenus(0);
  695. // If we're menubar, refuse focus
  696. if (isMenubar) {
  697. desktop->sendToBottom(this);
  698. }
  699. // Tell menubar to cancel showing current selection
  700. PopupMenu* next;
  701. // (assignment intentional)
  702. if ( (next = dynamic_cast<PopupMenu*>(desktop->findWindow(WINDOW_MENUBAR, 0))) ) {
  703. SDL_Event myEvent = *event;
  704. myEvent.type = SDL_MOUSEFOCUS;
  705. myEvent.user.code = 0;
  706. next->event(0, &myEvent);
  707. }
  708. // Send event for command selected
  709. if (item > 0) {
  710. desktop->broadcastEvent(SDL_COMMAND, item);
  711. desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
  712. }
  713. return 1;
  714. }
  715. return used;
  716. }
  717. int PopupMenu::isDocked() const { start_func
  718. return isMenubar;
  719. }
  720. int PopupMenu::tempFocus() const { start_func
  721. return 1;
  722. }
  723. Window::WindowType PopupMenu::windowType() const { start_func
  724. return isMenubar ? WINDOW_MENUBAR : WINDOW_POPUPMENU;
  725. }
  726. Window::WindowSort PopupMenu::windowSort() const { start_func
  727. return isMenubar ? WINDOWSORT_DOCKED : WINDOWSORT_POPUP;
  728. }
  729. PopupMenu* PopupMenu::compileMenu(const PopupGen* menuData, int isMenuBar, int* pos) { start_func
  730. PopupMenu* myMenu = NULL;
  731. PopupMenu* subMenu = NULL;
  732. PopupItem* subItem = NULL;
  733. int dummyPos = 0;
  734. if (!pos) pos = &dummyPos;
  735. myMenu = new PopupMenu(isMenuBar);
  736. while (menuData[*pos].code) {
  737. // Submenu
  738. if (menuData[*pos].code == MENU_SUB) {
  739. int oldPos = *pos;
  740. ++*pos;
  741. subMenu = compileMenu(menuData, 0, pos);
  742. subItem = new PopupItem(menuData[oldPos].dynamic, menuData[oldPos].name,
  743. 0, menuData[oldPos].dynamic ? 1 : 0, subMenu);
  744. // subMenu not "owned" by anything yet
  745. }
  746. // Separator
  747. else if (menuData[*pos].code == MENU_SEP) {
  748. subItem = new PopupItem(0, NULL, 0, 0);
  749. }
  750. // Standard menu item
  751. else {
  752. subItem = new PopupItem(menuData[*pos].code, menuData[*pos].name,
  753. config->readShortcut(menuData[*pos].code),
  754. menuData[*pos].dynamic);
  755. }
  756. myMenu->add(*subItem);
  757. // subMenu now owned by myMenu
  758. subMenu = NULL;
  759. delete subItem;
  760. subItem = NULL;
  761. ++*pos;
  762. }
  763. return myMenu;
  764. }