PageRenderTime 62ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/gui/widgets/popup.cpp

https://github.com/bluegr/scummvm
C++ | 548 lines | 376 code | 106 blank | 66 comment | 102 complexity | 37b046ef32f2d055b3b6932b458de7d5 MD5 | raw file
  1. /* ScummVM - Graphic Adventure Engine
  2. *
  3. * ScummVM is the legal property of its developers, whose names
  4. * are too numerous to list here. Please refer to the COPYRIGHT
  5. * file distributed with this source distribution.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. *
  21. */
  22. #include "common/system.h"
  23. #include "gui/gui-manager.h"
  24. #include "gui/widgets/popup.h"
  25. #include "gui/ThemeEval.h"
  26. namespace GUI {
  27. //
  28. // PopUpDialog
  29. //
  30. PopUpDialog::PopUpDialog(Widget *boss, const Common::String &name, int clickX, int clickY):
  31. Dialog(name),
  32. _boss(boss),
  33. // Remember original mouse position
  34. _clickX(clickX),
  35. _clickY(clickY),
  36. _selection(-1),
  37. _initialSelection(-1),
  38. _openTime(0),
  39. _twoColumns(false),
  40. _entriesPerColumn(1),
  41. _leftPadding(0),
  42. _rightPadding(0),
  43. _lineHeight(kLineHeight),
  44. _lastRead(-1) {
  45. _backgroundType = ThemeEngine::kDialogBackgroundNone;
  46. _w = _boss->getWidth();
  47. }
  48. void PopUpDialog::open() {
  49. // Time the popup was opened
  50. _openTime = g_system->getMillis();
  51. _initialSelection = _selection;
  52. // Calculate real popup dimensions
  53. _h = _entries.size() * _lineHeight + 2;
  54. _entriesPerColumn = 1;
  55. // Perform clipping / switch to scrolling mode if we don't fit on the screen
  56. // FIXME - OSystem should send out notification messages when the screen
  57. // resolution changes... we could generalize CommandReceiver and CommandSender.
  58. const int screenH = g_system->getOverlayHeight();
  59. // HACK: For now, we do not do scrolling. Instead, we draw the dialog
  60. // in two columns if it's too tall.
  61. if (_h >= screenH) {
  62. const int screenW = g_system->getOverlayWidth();
  63. _twoColumns = true;
  64. _entriesPerColumn = _entries.size() / 2;
  65. if (_entries.size() & 1)
  66. _entriesPerColumn++;
  67. _h = _entriesPerColumn * _lineHeight + 2;
  68. _w = 0;
  69. for (uint i = 0; i < _entries.size(); i++) {
  70. int width = g_gui.getStringWidth(_entries[i]);
  71. if (width > _w)
  72. _w = width;
  73. }
  74. _w = 2 * _w + 10;
  75. if (!(_w & 1))
  76. _w++;
  77. if (_selection >= _entriesPerColumn) {
  78. _x -= _w / 2;
  79. _y = _boss->getAbsY() - (_selection - _entriesPerColumn) * _lineHeight;
  80. }
  81. if (_w >= screenW)
  82. _w = screenW - 1;
  83. if (_x < 0)
  84. _x = 0;
  85. if (_x + _w >= screenW)
  86. _x = screenW - 1 - _w;
  87. } else
  88. _twoColumns = false;
  89. if (_h >= screenH)
  90. _h = screenH - 1;
  91. if (_y < 0)
  92. _y = 0;
  93. else if (_y + _h >= screenH)
  94. _y = screenH - 1 - _h;
  95. // TODO - implement scrolling if we had to move the menu, or if there are too many entries
  96. _lastRead = -1;
  97. Dialog::open();
  98. }
  99. void PopUpDialog::reflowLayout() {
  100. }
  101. void PopUpDialog::drawDialog(DrawLayer layerToDraw) {
  102. Dialog::drawDialog(layerToDraw);
  103. if (g_gui.useRTL()) {
  104. _x = g_system->getOverlayWidth() - _x - _w + g_gui.getOverlayOffset();
  105. }
  106. // Draw the menu border
  107. g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), ThemeEngine::kWidgetBackgroundPlain);
  108. /*if (_twoColumns)
  109. g_gui.vLine(_x + _w / 2, _y, _y + _h - 2, g_gui._color);*/
  110. // Draw the entries
  111. int count = _entries.size();
  112. for (int i = 0; i < count; i++) {
  113. drawMenuEntry(i, i == _selection);
  114. }
  115. // The last entry may be empty. Fill it with black.
  116. /*if (_twoColumns && (count & 1)) {
  117. g_gui.fillRect(_x + 1 + _w / 2, _y + 1 + kLineHeight * (_entriesPerColumn - 1), _w / 2 - 1, kLineHeight, g_gui._bgcolor);
  118. }*/
  119. }
  120. void PopUpDialog::handleMouseUp(int x, int y, int button, int clickCount) {
  121. int absX = x + getAbsX();
  122. int absY = y + getAbsY();
  123. // Mouse was released. If it wasn't moved much since the original mouse down,
  124. // let the popup stay open. If it did move, assume the user made his selection.
  125. int dist = (_clickX - absX) * (_clickX - absX) + (_clickY - absY) * (_clickY - absY);
  126. if (dist > 3 * 3 || g_system->getMillis() - _openTime > 300) {
  127. int item = findItem(x, y);
  128. setResult(item);
  129. close();
  130. }
  131. _clickX = -1;
  132. _clickY = -1;
  133. _openTime = (uint32)-1;
  134. }
  135. void PopUpDialog::handleMouseWheel(int x, int y, int direction) {
  136. if (direction < 0)
  137. moveUp();
  138. else if (direction > 0)
  139. moveDown();
  140. }
  141. void PopUpDialog::handleMouseMoved(int x, int y, int button) {
  142. // Compute over which item the mouse is...
  143. int item = findItem(x, y);
  144. if (item >= 0 && _entries[item].size() == 0)
  145. item = -1;
  146. if (item == -1 && !isMouseDown()) {
  147. setSelection(_initialSelection);
  148. return;
  149. }
  150. // ...and update the selection accordingly
  151. setSelection(item);
  152. if (_lastRead != item && _entries.size() > 0 && item != -1) {
  153. read(_entries[item]);
  154. _lastRead = item;
  155. }
  156. }
  157. void PopUpDialog::handleMouseLeft(int button) {
  158. _lastRead = -1;
  159. }
  160. void PopUpDialog::read(const Common::U32String &str) {
  161. if (ConfMan.hasKey("tts_enabled", "scummvm") &&
  162. ConfMan.getBool("tts_enabled", "scummvm")) {
  163. Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
  164. if (ttsMan != nullptr)
  165. ttsMan->say(str);
  166. }
  167. }
  168. void PopUpDialog::handleKeyDown(Common::KeyState state) {
  169. if (state.keycode == Common::KEYCODE_ESCAPE) {
  170. // Don't change the previous selection
  171. setResult(-1);
  172. close();
  173. return;
  174. }
  175. if (isMouseDown())
  176. return;
  177. switch (state.keycode) {
  178. case Common::KEYCODE_RETURN:
  179. case Common::KEYCODE_KP_ENTER:
  180. setResult(_selection);
  181. close();
  182. break;
  183. // Keypad & special keys
  184. // - if num lock is set, we ignore the keypress
  185. // - if num lock is not set, we fall down to the special key case
  186. case Common::KEYCODE_KP1:
  187. if (state.flags & Common::KBD_NUM)
  188. break;
  189. // fall through
  190. case Common::KEYCODE_END:
  191. setSelection(_entries.size()-1);
  192. break;
  193. case Common::KEYCODE_KP2:
  194. if (state.flags & Common::KBD_NUM)
  195. break;
  196. // fall through
  197. case Common::KEYCODE_DOWN:
  198. moveDown();
  199. break;
  200. case Common::KEYCODE_KP7:
  201. if (state.flags & Common::KBD_NUM)
  202. break;
  203. // fall through
  204. case Common::KEYCODE_HOME:
  205. setSelection(0);
  206. break;
  207. case Common::KEYCODE_KP8:
  208. if (state.flags & Common::KBD_NUM)
  209. break;
  210. // fall through
  211. case Common::KEYCODE_UP:
  212. moveUp();
  213. break;
  214. default:
  215. break;
  216. }
  217. }
  218. void PopUpDialog::setPosition(int x, int y) {
  219. _x = x;
  220. _y = y;
  221. }
  222. void PopUpDialog::setPadding(int left, int right) {
  223. _leftPadding = left;
  224. _rightPadding = right;
  225. }
  226. void PopUpDialog::setLineHeight(int lineHeight) {
  227. _lineHeight = lineHeight;
  228. }
  229. void PopUpDialog::setWidth(uint16 width) {
  230. _w = width;
  231. }
  232. void PopUpDialog::appendEntry(const Common::U32String &entry) {
  233. _entries.push_back(entry);
  234. }
  235. void PopUpDialog::clearEntries() {
  236. _entries.clear();
  237. }
  238. int PopUpDialog::findItem(int x, int y) const {
  239. if (x >= 0 && x < _w && y >= 0 && y < _h) {
  240. if (_twoColumns) {
  241. uint entry = (y - 2) / _lineHeight;
  242. if (x > _w / 2) {
  243. entry += _entriesPerColumn;
  244. if (entry >= _entries.size())
  245. return -1;
  246. }
  247. return entry;
  248. }
  249. return (y - 2) / _lineHeight;
  250. }
  251. return -1;
  252. }
  253. void PopUpDialog::setSelection(int item) {
  254. if (item != _selection) {
  255. // Undraw old selection
  256. if (_selection >= 0)
  257. drawMenuEntry(_selection, false);
  258. // Change selection
  259. _selection = item;
  260. // Draw new selection
  261. if (item >= 0)
  262. drawMenuEntry(item, true);
  263. }
  264. }
  265. bool PopUpDialog::isMouseDown() {
  266. // TODO/FIXME - need a way to determine whether any mouse buttons are pressed or not.
  267. // Sure, we could just count mouse button up/down events, but that is cumbersome and
  268. // error prone. Would be much nicer to add an API to OSystem for this...
  269. return false;
  270. }
  271. void PopUpDialog::moveUp() {
  272. if (_selection < 0) {
  273. setSelection(_entries.size() - 1);
  274. } else if (_selection > 0) {
  275. int item = _selection;
  276. do {
  277. item--;
  278. } while (item >= 0 && _entries[item].size() == 0);
  279. if (item >= 0)
  280. setSelection(item);
  281. }
  282. }
  283. void PopUpDialog::moveDown() {
  284. int lastItem = _entries.size() - 1;
  285. if (_selection < 0) {
  286. setSelection(0);
  287. } else if (_selection < lastItem) {
  288. int item = _selection;
  289. do {
  290. item++;
  291. } while (item <= lastItem && _entries[item].size() == 0);
  292. if (item <= lastItem)
  293. setSelection(item);
  294. }
  295. }
  296. void PopUpDialog::drawMenuEntry(int entry, bool hilite) {
  297. // Draw one entry of the popup menu, including selection
  298. assert(entry >= 0);
  299. int x, y, w;
  300. if (_twoColumns) {
  301. int n = _entries.size() / 2;
  302. if (_entries.size() & 1)
  303. n++;
  304. if (entry >= n) {
  305. x = _x + 1 + _w / 2;
  306. y = _y + 1 + _lineHeight * (entry - n);
  307. } else {
  308. x = _x + 1;
  309. y = _y + 1 + _lineHeight * entry;
  310. }
  311. w = _w / 2 - 1;
  312. } else {
  313. x = _x + 1;
  314. y = _y + 1 + _lineHeight * entry;
  315. w = _w - 2;
  316. }
  317. Common::U32String &name(_entries[entry]);
  318. Common::Rect r1(x, y, x + w, y + _lineHeight);
  319. Common::Rect r2(x + 1, y + 2, x + w, y + 2 + _lineHeight);
  320. Graphics::TextAlign alignment = Graphics::kTextAlignLeft;
  321. int pad = _leftPadding;
  322. if (g_gui.useRTL()) {
  323. if (_twoColumns) {
  324. r1.translate(this->getWidth() - w, 0); // Shift the line-separator to the "first" col of RTL popup
  325. }
  326. r2.left = g_system->getOverlayWidth() - r2.left - w + g_gui.getOverlayOffset();
  327. r2.right = r2.left + w;
  328. alignment = Graphics::kTextAlignRight;
  329. pad = _rightPadding;
  330. }
  331. if (name.size() == 0) {
  332. // Draw a separator
  333. g_gui.theme()->drawLineSeparator(r1);
  334. } else {
  335. g_gui.theme()->drawText(
  336. r2,
  337. name, hilite ? ThemeEngine::kStateHighlight : ThemeEngine::kStateEnabled,
  338. alignment, ThemeEngine::kTextInversionNone, pad
  339. );
  340. }
  341. }
  342. #pragma mark -
  343. //
  344. // PopUpWidget
  345. //
  346. PopUpWidget::PopUpWidget(GuiObject *boss, const String &name, const U32String &tooltip, uint32 cmd)
  347. : Widget(boss, name, tooltip), CommandSender(boss) {
  348. setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
  349. _type = kPopUpWidget;
  350. _cmd = cmd;
  351. _selectedItem = -1;
  352. _leftPadding = _rightPadding = 0;
  353. }
  354. PopUpWidget::PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const U32String &tooltip, uint32 cmd)
  355. : Widget(boss, x, y, w, h, tooltip), CommandSender(boss) {
  356. setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
  357. _type = kPopUpWidget;
  358. _cmd = cmd;
  359. _selectedItem = -1;
  360. _leftPadding = _rightPadding = 0;
  361. }
  362. void PopUpWidget::handleMouseDown(int x, int y, int button, int clickCount) {
  363. if (isEnabled()) {
  364. PopUpDialog popupDialog(this, "", x + getAbsX(), y + getAbsY());
  365. popupDialog.setPosition(getAbsX(), getAbsY() - _selectedItem * kLineHeight);
  366. popupDialog.setPadding(_leftPadding, _rightPadding);
  367. popupDialog.setWidth(getWidth() - kLineHeight + 2);
  368. for (uint i = 0; i < _entries.size(); i++) {
  369. popupDialog.appendEntry(_entries[i].name);
  370. }
  371. popupDialog.setSelection(_selectedItem);
  372. int newSel = popupDialog.runModal();
  373. if (newSel != -1 && _selectedItem != newSel) {
  374. _selectedItem = newSel;
  375. sendCommand(_cmd, _entries[_selectedItem].tag);
  376. markAsDirty();
  377. }
  378. }
  379. }
  380. void PopUpWidget::handleMouseWheel(int x, int y, int direction) {
  381. if (isEnabled()) {
  382. int newSelection = _selectedItem + direction;
  383. // Skip separator entries
  384. while ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
  385. _entries[newSelection].name.empty()) {
  386. newSelection += direction;
  387. }
  388. // Just update the selected item when we're in range
  389. if ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
  390. (newSelection != _selectedItem)) {
  391. _selectedItem = newSelection;
  392. sendCommand(_cmd, _entries[_selectedItem].tag);
  393. markAsDirty();
  394. }
  395. }
  396. }
  397. void PopUpWidget::reflowLayout() {
  398. _leftPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Left", 0);
  399. _rightPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Right", 0);
  400. Widget::reflowLayout();
  401. }
  402. void PopUpWidget::appendEntry(const U32String &entry, uint32 tag) {
  403. Entry e;
  404. e.name = entry;
  405. e.tag = tag;
  406. _entries.push_back(e);
  407. }
  408. void PopUpWidget::appendEntry(const String &entry, uint32 tag) {
  409. appendEntry(U32String(entry), tag);
  410. }
  411. void PopUpWidget::clearEntries() {
  412. _entries.clear();
  413. _selectedItem = -1;
  414. }
  415. void PopUpWidget::setSelected(int item) {
  416. if (item != _selectedItem) {
  417. if (item >= 0 && item < (int)_entries.size()) {
  418. _selectedItem = item;
  419. } else {
  420. _selectedItem = -1;
  421. }
  422. }
  423. }
  424. void PopUpWidget::setSelectedTag(uint32 tag) {
  425. uint item;
  426. for (item = 0; item < _entries.size(); ++item) {
  427. if (_entries[item].tag == tag) {
  428. setSelected(item);
  429. return;
  430. }
  431. }
  432. }
  433. void PopUpWidget::drawWidget() {
  434. Common::U32String sel;
  435. if (_selectedItem >= 0)
  436. sel = _entries[_selectedItem].name;
  437. int pad = _leftPadding;
  438. if (g_gui.useRTL() && _useRTL)
  439. pad = _rightPadding;
  440. g_gui.theme()->drawPopUpWidget(Common::Rect(_x, _y, _x + _w, _y + _h), sel, pad, _state, (g_gui.useRTL() && _useRTL));
  441. }
  442. } // End of namespace GUI