gcsx_list.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. /* GCSx
  2. ** LIST.CPP
  3. **
  4. ** Listbox, drop-down, etc. dialog widgets
  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. // Listbox entries
  25. ListEntry& ListEntry::operator=(const ListEntry& right) { start_func
  26. label = right.label;
  27. selected = right.selected;
  28. disabled = right.disabled;
  29. id = right.id;
  30. code1 = right.code1;
  31. code2 = right.code2;
  32. code3 = right.code3;
  33. return *this;
  34. }
  35. ListEntry::ListEntry() { start_func
  36. label = blankString;
  37. selected = disabled = 0;
  38. id = 0;
  39. code1 = code2 = code3 = 0;
  40. }
  41. ListEntry::ListEntry(const ListEntry& from) { start_func
  42. label = from.label;
  43. selected = from.selected;
  44. disabled = from.disabled;
  45. id = from.id;
  46. code1 = from.code1;
  47. code2 = from.code2;
  48. code3 = from.code3;
  49. }
  50. ListEntry::ListEntry(const string& eLabel, int eId, int eCode1, int eCode2, int eCode3, int eDisabled) { start_func
  51. label = eLabel;
  52. selected = 0;
  53. disabled = eDisabled;
  54. id = eId;
  55. code1 = eCode1;
  56. code2 = eCode2;
  57. code3 = eCode3;
  58. }
  59. // Listbox widget
  60. WListBox::WListBox(int lId, int* lSelection, int lAllowZero, int lWidth, int lLines) : Widget(lId, blankString, lSelection), contents() { start_func
  61. assert(lSelection);
  62. wparent = NULL;
  63. valueHeight = fontHeight();
  64. valueWidth = 0;
  65. numValues = 0;
  66. cursorPos = 0;
  67. selectedItem = SELECTED_NONE;
  68. allowZero = lAllowZero;
  69. allowMany = 0;
  70. // Set to standard size
  71. if (lWidth > 0) {
  72. showWidth = lWidth;
  73. }
  74. else {
  75. string standard(GUI_LISTBOX_DEFAULTWIDTH, 'X');
  76. showWidth = GUI_LISTBOX_HORIZPAD * 2 + fontWidth(standard);
  77. }
  78. showLines = lLines < 1 ? GUI_LISTBOX_DEFAULTLINES : lLines;
  79. resize(0, 0);
  80. }
  81. // Needed due to virtual ~Widget() and our list members
  82. WListBox::~WListBox() { start_func
  83. }
  84. void WListBox::moveCursor(int id) { start_func
  85. // Clip position
  86. if (id < 0) id = 0;
  87. if (id >= numValues) id = numValues - 1;
  88. if (cursorPos == id) return;
  89. cursorPos = id;
  90. setDirty();
  91. if (wparent) wparent->scrollToView(0, cursorPos * valueHeight, width, valueHeight);
  92. }
  93. void WListBox::selectItem(int id, int select, int alone) { start_func
  94. assert(id >= 0);
  95. assert(id < numValues);
  96. int dirty = 0;
  97. // (if disabled, no effect at all)
  98. if (contents[id].disabled) return;
  99. // Force 'alone' if only one allowed to be selected
  100. if ((select) && (!allowMany)) alone = 1;
  101. // (manually deselect all so we don't send dirty/modified yet)
  102. // 'alone' has no meaning if deselecting
  103. if ((alone) && (select)) {
  104. if (selectedItem == SELECTED_MANY) {
  105. for (int pos = 0; pos < numValues; ++pos) {
  106. contents[pos].selected = 0;
  107. }
  108. dirty = 1;
  109. }
  110. else if (selectedItem != SELECTED_NONE) {
  111. contents[selectedItem].selected = 0;
  112. dirty = 1;
  113. }
  114. selectedItem = SELECTED_NONE;
  115. }
  116. if (select) {
  117. if (selectedItem == SELECTED_NONE) selectedItem = id;
  118. else selectedItem = SELECTED_MANY;
  119. contents[id].selected = 1;
  120. dirty = 1;
  121. }
  122. else if (contents[id].selected) {
  123. // Disallow deselection if not allowed to select zero
  124. if ((!allowZero) && (selectedItem == id)) return;
  125. contents[id].selected = 0;
  126. if (selectedItem == id) selectedItem = SELECTED_NONE;
  127. else {
  128. // Count how many are selected now
  129. selectedItem = SELECTED_NONE;
  130. for (int pos = 0; pos < numValues; ++pos) {
  131. if (contents[pos].selected) {
  132. if (selectedItem == SELECTED_NONE) selectedItem = pos;
  133. else {
  134. selectedItem = SELECTED_MANY;
  135. break;
  136. }
  137. }
  138. }
  139. }
  140. dirty = 1;
  141. }
  142. if (dirty) {
  143. // @TODO: 3 exceptions
  144. if (wparent) wparent->myParent()->childModified(this);
  145. setDirty();
  146. }
  147. }
  148. int WListBox::event(int hasFocus, const SDL_Event* event) { start_func
  149. assert(event);
  150. assert(parent);
  151. if ((numValues == 0) && (event->type != SDL_INPUTFOCUS)) return 0;
  152. switch (event->type) {
  153. case SDL_INPUTFOCUS:
  154. if (event->user.code & 1) {
  155. if (!haveFocus) {
  156. haveFocus = 1;
  157. setDirty();
  158. }
  159. }
  160. else {
  161. if (haveFocus) {
  162. haveFocus = 0;
  163. setDirty();
  164. }
  165. }
  166. return 1;
  167. case SDL_KEYDOWN:
  168. // @TODO: Ctrl should move cursor without selecting or select multiple
  169. // Shift should select a range
  170. switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
  171. case SDLK_SPACE:
  172. selectItem(cursorPos, 0);
  173. return 1;
  174. case SDLK_DOWN:
  175. case SDLK_RIGHT:
  176. // Next line
  177. moveCursor(cursorPos + 1);
  178. selectItem(cursorPos);
  179. return 1;
  180. case SDLK_UP:
  181. case SDLK_LEFT:
  182. // Previous line
  183. moveCursor(cursorPos - 1);
  184. selectItem(cursorPos);
  185. return 1;
  186. case SDLK_PAGEDOWN:
  187. // Next page
  188. moveCursor(cursorPos + showLines - 1);
  189. selectItem(cursorPos);
  190. return 1;
  191. case SDLK_PAGEUP:
  192. // Previous page
  193. moveCursor(cursorPos - showLines + 1);
  194. selectItem(cursorPos);
  195. return 1;
  196. case SDLK_HOME:
  197. // First item
  198. moveCursor(0);
  199. selectItem(cursorPos);
  200. return 1;
  201. case SDLK_END:
  202. // Last item
  203. moveCursor(numValues - 1);
  204. selectItem(cursorPos);
  205. return 1;
  206. default: {
  207. // Jump to first item beginning with a letter
  208. int key = tolower(event->key.keysym.sym);
  209. for (int pos = 0; pos < numValues; ++pos) {
  210. if (tolower(contents[pos].label[0]) == key) {
  211. moveCursor(pos);
  212. selectItem(cursorPos);
  213. return 1;
  214. }
  215. }
  216. break;
  217. }
  218. }
  219. break;
  220. case SDL_MOUSEBUTTONDBL:
  221. case SDL_MOUSEBUTTONDOWN:
  222. if (event->button.button == SDL_BUTTON_LEFT) {
  223. dragSet = 1;
  224. if (event->button.y < height) {
  225. int targetPos = event->button.y / valueHeight;
  226. // If CTRL and was already selected, just unselect
  227. if ((contents[targetPos].selected) && (SDL_GetModState() & KMOD_CTRL) && (allowZero)) {
  228. dragSet = 0;
  229. }
  230. moveCursor(targetPos);
  231. selectItem(cursorPos, dragSet);
  232. // Double-click?
  233. if (event->type == SDL_MOUSEBUTTONDBL) {
  234. // (find OK button)
  235. if (wparent->myParent()->buttonControl(Dialog::BUTTON_OK)) {
  236. wparent->myParent()->currentFocusDoAction();
  237. }
  238. }
  239. return 1;
  240. }
  241. }
  242. break;
  243. case SDL_MOUSEMOTION:
  244. if (event->motion.state & SDL_BUTTON_LMASK) {
  245. if (event->motion.y < height) {
  246. moveCursor(event->motion.y / valueHeight);
  247. // @TODO: Should select/deselect all in between
  248. selectItem(cursorPos, dragSet);
  249. return 1;
  250. }
  251. }
  252. break;
  253. }
  254. return 0;
  255. }
  256. void WListBox::load() { start_func
  257. int sel = *(int*)(setting);
  258. int count = 0;
  259. cursorPos = 0;
  260. selectedItem = SELECTED_NONE;
  261. for (int pos = 0; pos < numValues; ++pos) {
  262. if (contents[pos].id == sel) {
  263. if (!count) {
  264. contents[pos].selected = 1;
  265. selectedItem = cursorPos = pos;
  266. }
  267. ++count;
  268. }
  269. else contents[pos].selected = 0;
  270. }
  271. if ((!count) && (!allowZero) && (numValues)) {
  272. contents[0].selected = 1;
  273. selectedItem = cursorPos = 0;
  274. }
  275. }
  276. void WListBox::apply() { start_func
  277. *(int*)(setting) = 0;
  278. // @TODO: Support for multiple selections
  279. if (selectedItem >= 0) {
  280. *(int*)(setting) = contents[selectedItem].id;
  281. }
  282. }
  283. void WListBox::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  284. assert(destSurface);
  285. if (visible) {
  286. // If dirty, redraw all
  287. if (dirty) {
  288. getRect(toDisplay);
  289. toDisplay.x += xOffset;
  290. toDisplay.y += yOffset;
  291. // Always fill entire width of view
  292. if (viewWidth > toDisplay.w) toDisplay.w = viewWidth;
  293. dirty = 0;
  294. intersectRects(toDisplay, clipArea);
  295. }
  296. // Anything to draw?
  297. if (toDisplay.w) {
  298. SDL_SetClipRect(destSurface, &toDisplay);
  299. xOffset += x;
  300. yOffset += y;
  301. // This widget attempts to only redraw listbox entries within the
  302. // dirtied area.
  303. // Background
  304. SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_TEXTBOX]);
  305. // Draw all listbox entries we can see
  306. int pos = (toDisplay.y - yOffset) / valueHeight;
  307. // (add in value height - 1 so it rounds up)
  308. int last = (toDisplay.y + toDisplay.h - yOffset + valueHeight - 1) / valueHeight;
  309. if (last > numValues) last = numValues;
  310. int shownWidth = valueWidth + GUI_LISTBOX_HORIZPAD * 2;
  311. if (viewWidth > shownWidth) shownWidth = viewWidth;
  312. for (; pos < last; ++pos) {
  313. if ((contents[pos].selected) && (!disabled) && (!contents[pos].disabled)) {
  314. drawGradient(xOffset, valueHeight * pos + yOffset,
  315. shownWidth, valueHeight, guiRGB[COLOR_SELECTION1],
  316. guiRGB[COLOR_SELECTION2], destSurface);
  317. drawText(contents[pos].label, guiRGB[COLOR_TEXTBOX],
  318. xOffset + GUI_LISTBOX_HORIZPAD, valueHeight * pos + yOffset, destSurface);
  319. }
  320. else {
  321. drawText(contents[pos].label, guiRGB[(disabled || contents[pos].disabled) ? COLOR_LIGHT2 : COLOR_TEXT],
  322. xOffset + GUI_LISTBOX_HORIZPAD, valueHeight * pos + yOffset, destSurface);
  323. }
  324. if ((haveFocus) && (pos == cursorPos)) {
  325. drawFocusBox(xOffset, valueHeight * pos + yOffset,
  326. shownWidth, valueHeight, destSurface);
  327. }
  328. }
  329. }
  330. }
  331. }
  332. void WListBox::addTo(Dialog* dialog) { start_func
  333. wparent = new WidgetScroll(WidgetScroll::FRAMETYPE_BEVEL, this, showWidth, showLines * valueHeight);
  334. dialog->addWidget(wparent);
  335. }
  336. void WListBox::clear() { start_func
  337. contents.clear();
  338. numValues = 0;
  339. valueWidth = 0;
  340. cursorPos = 0;
  341. selectedItem = SELECTED_NONE;
  342. resize(0, 0);
  343. }
  344. void WListBox::sortItems() { start_func
  345. sort(contents.begin(), contents.end());
  346. setDirty();
  347. // Find which item is selected now
  348. selectedItem = SELECTED_NONE;
  349. for (int pos = 0; pos < numValues; ++pos) {
  350. if (contents[pos].selected) {
  351. if (selectedItem == SELECTED_NONE) selectedItem = pos;
  352. else {
  353. selectedItem = SELECTED_MANY;
  354. break;
  355. }
  356. }
  357. }
  358. }
  359. void WListBox::modifyItem(int position, const std::string* newLabel, const int* newDisabled, const int* newCode1, const int* newCode2, const int* newCode3) { start_func
  360. if (newCode1) contents[position].code1 = *newCode1;
  361. if (newCode2) contents[position].code2 = *newCode2;
  362. if (newCode3) contents[position].code3 = *newCode3;
  363. if (newDisabled) {
  364. // If newly disabled, deselecte
  365. if ((*newDisabled) && (!contents[position].disabled) && (contents[id].selected)) {
  366. if ((!allowZero) && (selectedItem == position)) {
  367. // @TODO: If only item selected and can't select zero items
  368. }
  369. else {
  370. // Just deselect
  371. selectItem(position, 0, 0);
  372. }
  373. }
  374. contents[position].disabled = *newDisabled;
  375. }
  376. // Name change- update width
  377. if (newLabel) {
  378. contents[position].label = *newLabel;
  379. // (limit length of display but don't affect actual data)
  380. int itemWidth = fontWidth(newLabel->substr(0, MAX_LINELENGTH));
  381. if (itemWidth > valueWidth) {
  382. valueWidth = itemWidth;
  383. resize(valueWidth + GUI_LISTBOX_HORIZPAD * 2, valueHeight * numValues);
  384. }
  385. }
  386. // Update display
  387. setDirty();
  388. }
  389. void WListBox::addItem(const ListEntry& item, int selectIt) { start_func
  390. contents.push_back(item);
  391. numValues = contents.size();
  392. // (limit length of display but don't affect actual data)
  393. int itemWidth = fontWidth(item.label.substr(0, MAX_LINELENGTH));
  394. if (itemWidth > valueWidth) valueWidth = itemWidth;
  395. if (selectIt) {
  396. moveCursor(numValues - 1);
  397. selectItem(cursorPos);
  398. }
  399. // Dirties (since we are definately larger now)
  400. resize(valueWidth + GUI_LISTBOX_HORIZPAD * 2, valueHeight * numValues);
  401. }
  402. int WListBox::refuseAll() const { start_func
  403. if ((numValues == 0) || (disabled)) return 1;
  404. return 0;
  405. }
  406. const ListEntry* WListBox::findEntry(int id) const { start_func
  407. for (int pos = 0; pos < numValues; ++pos) {
  408. if (contents[pos].id == id) {
  409. return &contents[pos];
  410. }
  411. }
  412. return NULL;
  413. }