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
23 #include "common/util.h"
24 #include "common/stack.h"
25 #include "graphics/primitives.h"
26
27 #include "sci/sci.h"
28 #include "sci/event.h"
29 #include "sci/engine/kernel.h"
30 #include "sci/engine/state.h"
31 #include "sci/engine/selector.h"
32 #include "sci/graphics/ports.h"
33 #include "sci/graphics/paint16.h"
34 #include "sci/graphics/animate.h"
35 #include "sci/graphics/cursor.h"
36 #include "sci/graphics/scifont.h"
37 #include "sci/graphics/text16.h"
38 #include "sci/graphics/screen.h"
39 #include "sci/graphics/menu.h"
40
41 namespace Sci {
42
GfxMenu(EventManager * event,SegManager * segMan,GfxPorts * ports,GfxPaint16 * paint16,GfxText16 * text16,GfxScreen * screen,GfxCursor * cursor)43 GfxMenu::GfxMenu(EventManager *event, SegManager *segMan, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen, GfxCursor *cursor)
44 : _event(event), _segMan(segMan), _ports(ports), _paint16(paint16), _text16(text16), _screen(screen), _cursor(cursor) {
45
46 _menuSaveHandle = NULL_REG;
47 _barSaveHandle = NULL_REG;
48 _oldPort = NULL;
49 _mouseOldState = false;
50
51 reset();
52 }
53
~GfxMenu()54 GfxMenu::~GfxMenu() {
55 for (GuiMenuItemList::iterator itemIter = _itemList.begin(); itemIter != _itemList.end(); ++itemIter)
56 delete *itemIter;
57
58 _itemList.clear();
59
60 for (GuiMenuList::iterator menuIter = _list.begin(); menuIter != _list.end(); ++menuIter)
61 delete *menuIter;
62
63 _list.clear();
64 }
65
reset()66 void GfxMenu::reset() {
67 _list.clear();
68 _itemList.clear();
69
70 // We actually set active item in here and remember last selection of the
71 // user. Sierra SCI always defaulted to first item every time menu was
72 // called via ESC, we don't follow that logic.
73 _curMenuId = 1;
74 _curItemId = 1;
75 }
76
kernelAddEntry(Common::String title,Common::String content,reg_t contentVmPtr)77 void GfxMenu::kernelAddEntry(Common::String title, Common::String content, reg_t contentVmPtr) {
78 GuiMenuEntry *menuEntry;
79 uint16 itemCount = 0;
80 GuiMenuItemEntry *itemEntry;
81 int contentSize = content.size();
82 int separatorCount;
83 int curPos, beginPos, endPos, tempPos;
84 int tagPos, rightAlignedPos, functionPos, altPos, controlPos;
85 const char *tempPtr;
86
87 // Sierra SCI starts with id 1, so we do so as well
88 menuEntry = new GuiMenuEntry(_list.size() + 1);
89 menuEntry->text = title;
90 _list.push_back(menuEntry);
91
92 curPos = 0;
93 uint16 listSize = _list.size();
94
95 do {
96 itemCount++;
97 itemEntry = new GuiMenuItemEntry(listSize, itemCount);
98
99 beginPos = curPos;
100
101 // Now go through the content till we find end-marker and collect data about it.
102 // ':' is an end-marker for each item.
103 tagPos = 0; rightAlignedPos = 0;
104 controlPos = 0; altPos = 0; functionPos = 0;
105 while ((curPos < contentSize) && (content[curPos] != ':')) {
106 switch (content[curPos]) {
107 case '=': // Set tag
108 // Special case for normal animation speed - they use right
109 // aligned "=" for that one, so we ignore it as being recognized
110 // as tag marker.
111 if (rightAlignedPos == curPos - 1)
112 break;
113 if (tagPos)
114 error("multiple tag markers within one menu-item");
115 tagPos = curPos;
116 break;
117 case '`': // Right-aligned
118 if (rightAlignedPos)
119 error("multiple right-aligned markers within one menu-item");
120 rightAlignedPos = curPos;
121 break;
122 case '^': // Ctrl-prefix
123 if (controlPos)
124 error("multiple control markers within one menu-item");
125 controlPos = curPos;
126 break;
127 case '@': // Alt-prefix
128 if (altPos)
129 error("multiple alt markers within one menu-item");
130 altPos = curPos;
131 break;
132 case '#': // Function-prefix
133 // #G is used as language separator (SQ3 German Amiga) so only
134 // treat # as a function prefix once ` has been reached
135 if (rightAlignedPos) {
136 if (functionPos)
137 error("multiple function markers within one menu-item");
138 functionPos = curPos;
139 }
140 break;
141 default:
142 break;
143 }
144 curPos++;
145 }
146 endPos = curPos;
147
148 // Control/Alt/Function key mapping...
149 if (controlPos) {
150 content.setChar(SCI_MENU_REPLACE_ONCONTROL, controlPos);
151 itemEntry->keyModifier = kSciKeyModCtrl;
152 tempPos = controlPos + 1;
153 if (tempPos >= contentSize)
154 error("control marker at end of item");
155 itemEntry->keyPress = tolower(content[tempPos]);
156 content.setChar(toupper(content[tempPos]), tempPos);
157 }
158 if (altPos) {
159 content.setChar(SCI_MENU_REPLACE_ONALT, altPos);
160 itemEntry->keyModifier = kSciKeyModAlt;
161 tempPos = altPos + 1;
162 if (tempPos >= contentSize)
163 error("alt marker at end of item");
164 itemEntry->keyPress = tolower(content[tempPos]);
165 content.setChar(toupper(content[tempPos]), tempPos);
166 }
167 if (functionPos) {
168 content.setChar(SCI_MENU_REPLACE_ONFUNCTION, functionPos);
169 tempPos = functionPos + 1;
170 if (tempPos >= contentSize)
171 error("function marker at end of item");
172 itemEntry->keyPress = content[tempPos];
173 switch (content[functionPos + 1]) {
174 case '1': itemEntry->keyPress = kSciKeyF1; break;
175 case '2': itemEntry->keyPress = kSciKeyF2; break;
176 case '3': itemEntry->keyPress = kSciKeyF3; break;
177 case '4': itemEntry->keyPress = kSciKeyF4; break;
178 case '5': itemEntry->keyPress = kSciKeyF5; break;
179 case '6': itemEntry->keyPress = kSciKeyF6; break;
180 case '7': itemEntry->keyPress = kSciKeyF7; break;
181 case '8': itemEntry->keyPress = kSciKeyF8; break;
182 case '9': itemEntry->keyPress = kSciKeyF9; break;
183 case '0': itemEntry->keyPress = kSciKeyF10; break;
184 default:
185 error("illegal function key specified");
186 }
187 }
188
189 // Now get all strings
190 tempPos = endPos;
191 if (rightAlignedPos) {
192 tempPos = rightAlignedPos;
193 } else if (tagPos) {
194 tempPos = tagPos;
195 }
196 curPos = beginPos;
197 separatorCount = 0;
198 while (curPos < tempPos) {
199 switch (content[curPos]) {
200 case '!':
201 case '-':
202 case ' ':
203 separatorCount++;
204 break;
205 case '%':
206 case '#':
207 // Some multilingual sci01 games use e.g. '--!%G--!' (which doesn't really make sense)
208 separatorCount += 2;
209 curPos++;
210 break;
211 default:
212 break;
213 }
214 curPos++;
215 }
216 if (separatorCount == tempPos - beginPos) {
217 itemEntry->separatorLine = true;
218 } else {
219 // We don't strSplit here, because multilingual SCI01 support
220 // language switching on the fly, so we have to do this everytime
221 // the menu is called.
222 itemEntry->text = Common::String(content.c_str() + beginPos, tempPos - beginPos);
223
224 // LSL6 uses "Ctrl-" prefix string instead of ^ like all the other games do
225 tempPtr = itemEntry->text.c_str();
226 tempPtr = strstr(tempPtr, "Ctrl-");
227 if (tempPtr) {
228 itemEntry->keyModifier = kSciKeyModCtrl;
229 itemEntry->keyPress = tolower(tempPtr[5]);
230 }
231 }
232 itemEntry->textVmPtr = contentVmPtr;
233 itemEntry->textVmPtr.incOffset(beginPos);
234
235 if (rightAlignedPos) {
236 rightAlignedPos++;
237 tempPos = endPos;
238 // some games have tagPos in front of right rightAlignedPos
239 // some have it the other way... (qfg1ega)
240 if (tagPos && tagPos >= rightAlignedPos)
241 tempPos = tagPos;
242 itemEntry->textRightAligned = Common::String(content.c_str() + rightAlignedPos, tempPos - rightAlignedPos);
243 // Remove ending space, if there is one. Strangely sometimes there
244 // are lone spaces at the end in some games
245 if (itemEntry->textRightAligned.hasSuffix(" "))
246 itemEntry->textRightAligned.deleteLastChar();
247 // - and + are used sometimes for volume control/animation speed,
248 // = sometimes for animation speed
249 if (itemEntry->textRightAligned == "-") {
250 itemEntry->keyPress = '-';
251 } else if (itemEntry->textRightAligned == "+") {
252 itemEntry->keyPress = '+';
253 } else if (itemEntry->textRightAligned == "=") {
254 itemEntry->keyPress = '=';
255 }
256 }
257
258 if (tagPos) {
259 tempPos = functionPos + 1;
260 if (tempPos >= contentSize)
261 error("tag marker at end of item");
262 itemEntry->tag = atoi(content.c_str() + tempPos);
263 }
264
265 curPos = endPos + 1;
266
267 _itemList.push_back(itemEntry);
268 } while (curPos < contentSize);
269 }
270
findItem(uint16 menuId,uint16 itemId)271 GuiMenuItemEntry *GfxMenu::findItem(uint16 menuId, uint16 itemId) {
272 GuiMenuItemList::iterator listIterator;
273 GuiMenuItemList::iterator listEnd = _itemList.end();
274 GuiMenuItemEntry *listEntry;
275
276 listIterator = _itemList.begin();
277 while (listIterator != listEnd) {
278 listEntry = *listIterator;
279 if ((listEntry->menuId == menuId) && (listEntry->id == itemId))
280 return listEntry;
281
282 listIterator++;
283 }
284 return NULL;
285 }
286
kernelSetAttribute(uint16 menuId,uint16 itemId,uint16 attributeId,reg_t value)287 void GfxMenu::kernelSetAttribute(uint16 menuId, uint16 itemId, uint16 attributeId, reg_t value) {
288 GuiMenuItemEntry *itemEntry = findItem(menuId, itemId);
289
290 if (!itemEntry) {
291 // PQ2 demo calls this, for example, but has no menus (bug report #4948). Some SCI
292 // fan games (Al Pond 2, Aquarius) call this too on non-existent menu items. The
293 // original interpreter ignored these as well.
294 debugC(kDebugLevelGraphics, "Tried to setAttribute() on non-existent menu-item %d:%d", menuId, itemId);
295 return;
296 }
297
298 switch (attributeId) {
299 case SCI_MENU_ATTRIBUTE_ENABLED:
300 itemEntry->enabled = !value.isNull();
301 break;
302 case SCI_MENU_ATTRIBUTE_SAID:
303 itemEntry->saidVmPtr = value;
304 break;
305 case SCI_MENU_ATTRIBUTE_TEXT:
306 itemEntry->text = _segMan->getString(value);
307 itemEntry->textVmPtr = value;
308 // We assume here that no script ever creates a separatorLine dynamically
309 break;
310 case SCI_MENU_ATTRIBUTE_KEYPRESS:
311 itemEntry->keyPress = tolower(value.getOffset());
312 itemEntry->keyModifier = 0;
313 // TODO: Find out how modifier is handled
314 debug("setAttr keypress %X %X", value.getSegment(), value.getOffset());
315 break;
316 case SCI_MENU_ATTRIBUTE_TAG:
317 itemEntry->tag = value.getOffset();
318 break;
319 default:
320 // Happens when loading a game in LSL3 - attribute 1A
321 warning("setAttribute() called with unsupported attributeId %X", attributeId);
322 }
323 }
324
kernelGetAttribute(uint16 menuId,uint16 itemId,uint16 attributeId)325 reg_t GfxMenu::kernelGetAttribute(uint16 menuId, uint16 itemId, uint16 attributeId) {
326 GuiMenuItemEntry *itemEntry = findItem(menuId, itemId);
327 if (!itemEntry)
328 error("Tried to getAttribute() on non-existent menu-item %d:%d", menuId, itemId);
329 switch (attributeId) {
330 case SCI_MENU_ATTRIBUTE_ENABLED:
331 if (itemEntry->enabled)
332 return make_reg(0, 1);
333 break;
334 case SCI_MENU_ATTRIBUTE_SAID:
335 return itemEntry->saidVmPtr;
336 case SCI_MENU_ATTRIBUTE_TEXT:
337 return itemEntry->textVmPtr;
338 case SCI_MENU_ATTRIBUTE_KEYPRESS:
339 // TODO: Find out how modifier is handled
340 return make_reg(0, itemEntry->keyPress);
341 case SCI_MENU_ATTRIBUTE_TAG:
342 return make_reg(0, itemEntry->tag);
343 default:
344 error("getAttribute() called with unsupported attributeId %X", attributeId);
345 }
346 return NULL_REG;
347 }
348
drawBar()349 void GfxMenu::drawBar() {
350 GuiMenuEntry *listEntry;
351 GuiMenuList::iterator listIterator;
352 GuiMenuList::iterator listEnd = _list.end();
353
354 // Hardcoded black on white and a black line afterwards
355 _paint16->fillRect(_ports->_menuBarRect, 1, _screen->getColorWhite());
356 _paint16->fillRect(_ports->_menuLine, 1, 0);
357 _ports->penColor(0);
358 if (!g_sci->isLanguageRTL())
359 _ports->moveTo(8, 1);
360 else
361 _ports->moveTo(_screen->getWidth() - 8, 1);
362
363 listIterator = _list.begin();
364 while (listIterator != listEnd) {
365 listEntry = *listIterator;
366 int16 textWidth;
367 int16 textHeight;
368 if (g_sci->isLanguageRTL()) {
369 _text16->StringWidth(listEntry->textSplit.c_str(), _text16->GetFontId(), textWidth, textHeight);
370 _ports->_curPort->curLeft -= textWidth;
371 }
372 int16 origCurLeft = _ports->_curPort->curLeft;
373 _text16->DrawString(listEntry->textSplit.c_str());
374 if (g_sci->isLanguageRTL())
375 _ports->_curPort->curLeft = origCurLeft;
376
377 listIterator++;
378 }
379 }
380
381 // This helper calculates all text widths for all menus (only)
calculateMenuWidth()382 void GfxMenu::calculateMenuWidth() {
383 GuiMenuList::iterator menuIterator;
384 GuiMenuList::iterator menuEnd = _list.end();
385 GuiMenuEntry *menuEntry;
386 int16 dummyHeight;
387
388 menuIterator = _list.begin();
389 while (menuIterator != menuEnd) {
390 menuEntry = *menuIterator;
391 menuEntry->textSplit = g_sci->strSplit(menuEntry->text.c_str(), NULL);
392 _text16->StringWidth(menuEntry->textSplit.c_str(), 0, menuEntry->textWidth, dummyHeight);
393
394 menuIterator++;
395 }
396 }
397
398 // This helper calculates all text widths for all menus/items
calculateMenuAndItemWidth()399 void GfxMenu::calculateMenuAndItemWidth() {
400 GuiMenuItemList::iterator itemIterator;
401 GuiMenuItemList::iterator itemEnd = _itemList.end();
402 GuiMenuItemEntry *itemEntry;
403 int16 dummyHeight;
404
405 calculateMenuWidth();
406
407 itemIterator = _itemList.begin();
408 while (itemIterator != itemEnd) {
409 itemEntry = *itemIterator;
410 // Split the text now for multilingual SCI01 games
411 itemEntry->textSplit = g_sci->strSplit(itemEntry->text.c_str(), NULL);
412 _text16->StringWidth(itemEntry->textSplit.c_str(), 0, itemEntry->textWidth, dummyHeight);
413 _text16->StringWidth(itemEntry->textRightAligned.c_str(), 0, itemEntry->textRightAlignedWidth, dummyHeight);
414
415 itemIterator++;
416 }
417 }
418
kernelSelect(reg_t eventObject,bool pauseSound)419 reg_t GfxMenu::kernelSelect(reg_t eventObject, bool pauseSound) {
420 int16 eventType = readSelectorValue(_segMan, eventObject, SELECTOR(type));
421 int16 keyPress, keyModifier;
422 GuiMenuItemList::iterator itemIterator = _itemList.begin();
423 GuiMenuItemList::iterator itemEnd = _itemList.end();
424 GuiMenuItemEntry *itemEntry = NULL;
425 bool forceClaimed = false;
426
427 switch (eventType) {
428 case kSciEventKeyDown:
429 keyPress = readSelectorValue(_segMan, eventObject, SELECTOR(message));
430 keyModifier = readSelectorValue(_segMan, eventObject, SELECTOR(modifiers));
431
432 // ASCII control characters are put in the `message` field when
433 // Ctrl+<key> is pressed, but this kMenuSelect implementation matches
434 // on modifier + printable character, so we must convert the control
435 // characters to their lower-case latin printed equivalents
436 if ((keyModifier & kSciKeyModNonSticky) == kSciKeyModCtrl && keyPress > 0 && keyPress < 27) {
437 keyPress += 96;
438 }
439
440 switch (keyPress) {
441 case 0:
442 break;
443 case kSciKeyEsc:
444 interactiveStart(pauseSound);
445 itemEntry = interactiveWithKeyboard();
446 interactiveEnd(pauseSound);
447 forceClaimed = true;
448 break;
449 default:
450 while (itemIterator != itemEnd) {
451 itemEntry = *itemIterator;
452
453 // Tab and Ctrl+I share the same ASCII character, but this
454 // method also checks the modifier (whereas SSCI looked only at
455 // the character), so a Tab keypress must be converted here
456 // to Ctrl+I or the modifier check will fail and the Tab key
457 // won't do anything. (This is also why Ctrl+I and Ctrl+Shift+I
458 // would both bring up the inventory in SSCI QFG1EGA)
459 if (keyPress == kSciKeyTab) {
460 keyModifier = kSciKeyModCtrl;
461 keyPress = 'i';
462 }
463
464 // We need to isolate the lower byte when checking modifiers
465 // because of a keyboard driver bug (see engine/kevent.cpp /
466 // kGetEvent)
467 keyModifier &= 0xFF;
468
469 if (itemEntry->keyPress == keyPress &&
470 itemEntry->keyModifier == keyModifier &&
471 itemEntry->enabled)
472 break;
473 itemIterator++;
474 }
475 if (itemIterator == itemEnd)
476 itemEntry = NULL;
477 }
478 break;
479
480 case kSciEventSaid:
481 while (itemIterator != itemEnd) {
482 itemEntry = *itemIterator;
483
484 if (!itemEntry->saidVmPtr.isNull() && itemEntry->enabled) {
485 byte *saidSpec = _segMan->derefBulkPtr(itemEntry->saidVmPtr, 0);
486
487 if (!saidSpec) {
488 warning("Could not dereference saidSpec");
489 continue;
490 }
491
492 if (said(saidSpec, 0) != SAID_NO_MATCH)
493 break;
494 }
495 itemIterator++;
496 }
497 if (itemIterator == itemEnd)
498 itemEntry = NULL;
499 break;
500
501 case kSciEventMousePress: {
502 Common::Point mousePosition;
503 mousePosition.x = readSelectorValue(_segMan, eventObject, SELECTOR(x));
504 mousePosition.y = readSelectorValue(_segMan, eventObject, SELECTOR(y));
505 if (mousePosition.y < 10) {
506 interactiveStart(pauseSound);
507 itemEntry = interactiveWithMouse();
508 interactiveEnd(pauseSound);
509 forceClaimed = true;
510 }
511 } break;
512
513 default:
514 break;
515 }
516
517 if (!_menuSaveHandle.isNull()) {
518 _paint16->bitsRestore(_menuSaveHandle);
519 // Display line inbetween menubar and actual menu
520 Common::Rect menuLine = _menuRect;
521 menuLine.bottom = menuLine.top + 1;
522 _paint16->bitsShow(menuLine);
523 _paint16->kernelGraphRedrawBox(_menuRect);
524 _menuSaveHandle = NULL_REG;
525 }
526 if (!_barSaveHandle.isNull()) {
527 _paint16->bitsRestore(_barSaveHandle);
528 _paint16->bitsShow(_ports->_menuRect);
529 _barSaveHandle = NULL_REG;
530 }
531 if (_oldPort) {
532 _ports->setPort(_oldPort);
533 _oldPort = NULL;
534 }
535
536 if ((itemEntry) || (forceClaimed))
537 writeSelector(_segMan, eventObject, SELECTOR(claimed), make_reg(0, 1));
538 if (itemEntry)
539 return make_reg(0, (itemEntry->menuId << 8) | (itemEntry->id));
540 return NULL_REG;
541 }
542
interactiveGetItem(uint16 menuId,uint16 itemId,bool menuChanged)543 GuiMenuItemEntry *GfxMenu::interactiveGetItem(uint16 menuId, uint16 itemId, bool menuChanged) {
544 GuiMenuItemList::iterator itemIterator = _itemList.begin();
545 GuiMenuItemList::iterator itemEnd = _itemList.end();
546 GuiMenuItemEntry *itemEntry;
547 GuiMenuItemEntry *firstItemEntry = NULL;
548 GuiMenuItemEntry *lastItemEntry = NULL;
549
550 // Fixup menuId if needed
551 if (menuId > _list.size())
552 menuId = 1;
553 if (menuId == 0)
554 menuId = _list.size();
555 while (itemIterator != itemEnd) {
556 itemEntry = *itemIterator;
557 if (itemEntry->menuId == menuId) {
558 if (itemEntry->id == itemId)
559 return itemEntry;
560 if (!firstItemEntry)
561 firstItemEntry = itemEntry;
562 if ((!lastItemEntry) || (itemEntry->id > lastItemEntry->id))
563 lastItemEntry = itemEntry;
564 }
565 itemIterator++;
566 }
567 if ((itemId == 0) || (menuChanged))
568 return lastItemEntry;
569 return firstItemEntry;
570 }
571
drawMenu(uint16 oldMenuId,uint16 newMenuId)572 void GfxMenu::drawMenu(uint16 oldMenuId, uint16 newMenuId) {
573 GuiMenuEntry *listEntry;
574 GuiMenuList::iterator listIterator;
575 GuiMenuList::iterator listEnd = _list.end();
576 GuiMenuItemEntry *listItemEntry;
577 GuiMenuItemList::iterator listItemIterator;
578 GuiMenuItemList::iterator listItemEnd = _itemList.end();
579 Common::Rect menuTextRect;
580 uint16 listNr = 0;
581 int16 maxTextWidth = 0, maxTextRightAlignedWidth = 0;
582 int16 topPos;
583 Common::Point pixelPos;
584
585 // Remove menu, if one is displayed
586 if (!_menuSaveHandle.isNull()) {
587 _paint16->bitsRestore(_menuSaveHandle);
588 // Display line inbetween menubar and actual menu
589 Common::Rect menuLine = _menuRect;
590 menuLine.bottom = menuLine.top + 1;
591 _paint16->bitsShow(menuLine);
592 _paint16->kernelGraphRedrawBox(_menuRect);
593 }
594
595 // First calculate rect of menu and also invert old and new menu text
596 _menuRect.top = _ports->_menuBarRect.bottom;
597 menuTextRect.top = _ports->_menuBarRect.top;
598 menuTextRect.bottom = _ports->_menuBarRect.bottom;
599 if (!g_sci->isLanguageRTL())
600 menuTextRect.left = menuTextRect.right = 7;
601 else
602 menuTextRect.left = menuTextRect.right = _ports->_menuBarRect.right - 7;
603 listIterator = _list.begin();
604 while (listIterator != listEnd) {
605 listEntry = *listIterator;
606 listNr++;
607 if (!g_sci->isLanguageRTL()) {
608 menuTextRect.left = menuTextRect.right;
609 menuTextRect.right += listEntry->textWidth;
610 if (listNr == newMenuId)
611 _menuRect.left = menuTextRect.left;
612 } else {
613 menuTextRect.right = menuTextRect.left;
614 menuTextRect.left -= listEntry->textWidth;
615 if (listNr == newMenuId)
616 _menuRect.right = menuTextRect.right;
617 }
618 if ((listNr == newMenuId) || (listNr == oldMenuId)) {
619 int multiplier = !g_sci->isLanguageRTL() ? 1 : -1;
620 menuTextRect.translate(1 * multiplier, 0);
621 _paint16->invertRect(menuTextRect);
622 menuTextRect.translate(-1 * multiplier, 0);
623 }
624
625 listIterator++;
626 }
627 _paint16->bitsShow(_ports->_menuBarRect);
628
629 _menuRect.bottom = _menuRect.top + 2;
630 listItemIterator = _itemList.begin();
631 while (listItemIterator != listItemEnd) {
632 listItemEntry = *listItemIterator;
633 if (listItemEntry->menuId == newMenuId) {
634 _menuRect.bottom += _ports->_curPort->fontHeight;
635 maxTextWidth = MAX<int16>(maxTextWidth, listItemEntry->textWidth);
636 maxTextRightAlignedWidth = MAX<int16>(maxTextRightAlignedWidth, listItemEntry->textRightAlignedWidth);
637 }
638 listItemIterator++;
639 }
640 if (!g_sci->isLanguageRTL()) {
641 _menuRect.right = _menuRect.left + 16 + 4 + 2;
642 _menuRect.right += maxTextWidth + maxTextRightAlignedWidth;
643 if (!maxTextRightAlignedWidth)
644 _menuRect.right -= 5;
645 } else {
646 _menuRect.left = _menuRect.right - (16 + 4 + 2);
647 _menuRect.left -= (maxTextWidth + maxTextRightAlignedWidth);
648 if (!maxTextRightAlignedWidth)
649 _menuRect.left += 5;
650 }
651
652 // If part of menu window is outside the screen, move it into the screen
653 // (this happens in multilingual sq3 and lsl3).
654 if (_menuRect.right > _screen->getWidth()) {
655 _menuRect.translate(-(_menuRect.right - _screen->getWidth()), 0);
656 }
657 if (_menuRect.left < 0) {
658 warning("GfxMenu::drawMenu: _menuRect.left < 0");
659 }
660
661 // Save background
662 _menuSaveHandle = _paint16->bitsSave(_menuRect, GFX_SCREEN_MASK_VISUAL);
663
664 // Do the drawing
665 _paint16->fillRect(_menuRect, GFX_SCREEN_MASK_VISUAL, 0);
666 _menuRect.left++; _menuRect.right--; _menuRect.bottom--;
667 _paint16->fillRect(_menuRect, GFX_SCREEN_MASK_VISUAL, _screen->getColorWhite());
668
669 if (!g_sci->isLanguageRTL())
670 _menuRect.left += 8;
671 else
672 _menuRect.right -= 8;
673 topPos = _menuRect.top + 1;
674 listItemIterator = _itemList.begin();
675 while (listItemIterator != listItemEnd) {
676 listItemEntry = *listItemIterator;
677 if (listItemEntry->menuId == newMenuId) {
678 if (!listItemEntry->separatorLine) {
679 _ports->textGreyedOutput(!listItemEntry->enabled);
680 if (!g_sci->isLanguageRTL()) {
681 _ports->moveTo(_menuRect.left, topPos);
682 _text16->DrawString(listItemEntry->textSplit.c_str());
683 _ports->moveTo(_menuRect.right - listItemEntry->textRightAlignedWidth - 5, topPos);
684 _text16->DrawString(listItemEntry->textRightAligned.c_str());
685 } else {
686 _ports->moveTo(_menuRect.left + 5, topPos);
687 _text16->DrawString(listItemEntry->textRightAligned.c_str());
688 _ports->moveTo(_menuRect.right - listItemEntry->textWidth, topPos);
689 _text16->DrawString(listItemEntry->textSplit.c_str());
690 }
691 } else {
692 // We dont 100% follow sierra here, we draw the line from left to right. Looks better
693 // BTW. SCI1.1 seems to put 2 pixels and then skip one, we don't do this at all (lsl6)
694 pixelPos.y = topPos + (_ports->_curPort->fontHeight >> 1) - 1;
695 pixelPos.x = _menuRect.left - 7;
696 while (pixelPos.x < (_menuRect.right - 1)) {
697 _screen->putPixel(pixelPos.x, pixelPos.y, GFX_SCREEN_MASK_VISUAL, 0, 0, 0);
698 pixelPos.x += 2;
699 }
700 }
701 topPos += _ports->_curPort->fontHeight;
702 }
703 listItemIterator++;
704 }
705 _ports->textGreyedOutput(false);
706
707 // Draw the black line again
708 _paint16->fillRect(_ports->_menuLine, 1, 0);
709
710 if (!g_sci->isLanguageRTL()) {
711 _menuRect.left -= 8;
712 _menuRect.left--;
713 _menuRect.right++;
714 } else {
715 _menuRect.right += 8;
716 _menuRect.right--;
717 _menuRect.left++;
718 }
719 _menuRect.bottom++;
720 _paint16->bitsShow(_menuRect);
721 }
722
invertMenuSelection(uint16 itemId)723 void GfxMenu::invertMenuSelection(uint16 itemId) {
724 Common::Rect itemRect = _menuRect;
725
726 if (itemId == 0)
727 return;
728
729 itemRect.top += (itemId - 1) * _ports->_curPort->fontHeight + 1;
730 itemRect.bottom = itemRect.top + _ports->_curPort->fontHeight;
731 itemRect.left++; itemRect.right--;
732
733 _paint16->invertRect(itemRect);
734 _paint16->bitsShow(itemRect);
735 }
736
interactiveStart(bool pauseSound)737 void GfxMenu::interactiveStart(bool pauseSound) {
738 _mouseOldState = _cursor->isVisible();
739 _cursor->kernelShow();
740 if (pauseSound)
741 g_sci->_soundCmd->pauseAll(true);
742 }
743
interactiveEnd(bool pauseSound)744 void GfxMenu::interactiveEnd(bool pauseSound) {
745 if (pauseSound)
746 g_sci->_soundCmd->pauseAll(false);
747 if (!_mouseOldState)
748 _cursor->kernelHide();
749 }
750
mouseFindMenuSelection(Common::Point mousePosition)751 uint16 GfxMenu::mouseFindMenuSelection(Common::Point mousePosition) {
752 GuiMenuEntry *listEntry;
753 GuiMenuList::iterator listIterator;
754 GuiMenuList::iterator listEnd = _list.end();
755 uint16 curXstart;
756 if (!g_sci->isLanguageRTL())
757 curXstart = 8;
758 else
759 curXstart = _screen->getWidth() - 8;
760
761 listIterator = _list.begin();
762 while (listIterator != listEnd) {
763 listEntry = *listIterator;
764 if (!g_sci->isLanguageRTL()) {
765 if (mousePosition.x >= curXstart && mousePosition.x < curXstart + listEntry->textWidth) {
766 return listEntry->id;
767 }
768 curXstart += listEntry->textWidth;
769 } else {
770 if (mousePosition.x <= curXstart && mousePosition.x > curXstart - listEntry->textWidth) {
771 return listEntry->id;
772 }
773 curXstart -= listEntry->textWidth;
774 }
775 listIterator++;
776 }
777 return 0;
778 }
779
mouseFindMenuItemSelection(Common::Point mousePosition,uint16 menuId)780 uint16 GfxMenu::mouseFindMenuItemSelection(Common::Point mousePosition, uint16 menuId) {
781 GuiMenuItemEntry *listItemEntry;
782 GuiMenuItemList::iterator listItemIterator;
783 GuiMenuItemList::iterator listItemEnd = _itemList.end();
784 uint16 curYstart = 10;
785 uint16 itemId = 0;
786
787 if (!menuId)
788 return 0;
789
790 if ((mousePosition.x < _menuRect.left) || (mousePosition.x >= _menuRect.right))
791 return 0;
792
793 listItemIterator = _itemList.begin();
794 while (listItemIterator != listItemEnd) {
795 listItemEntry = *listItemIterator;
796 if (listItemEntry->menuId == menuId) {
797 curYstart += _ports->_curPort->fontHeight;
798 // Found it
799 if ((!itemId) && (curYstart > mousePosition.y))
800 itemId = listItemEntry->id;
801 }
802
803 listItemIterator++;
804 }
805 return itemId;
806 }
807
interactiveWithKeyboard()808 GuiMenuItemEntry *GfxMenu::interactiveWithKeyboard() {
809 SciEvent curEvent;
810 uint16 newMenuId = _curMenuId;
811 uint16 newItemId = _curItemId;
812 GuiMenuItemEntry *curItemEntry = findItem(_curMenuId, _curItemId);
813 GuiMenuItemEntry *newItemEntry = curItemEntry;
814
815 // We don't 100% follow Sierra here: we select last item instead of
816 // selecting first item of first menu every time. Also sierra sci didn't
817 // allow mouse interaction, when menu was activated via keyboard.
818
819 _oldPort = _ports->setPort(_ports->_menuPort);
820 calculateMenuAndItemWidth();
821 _barSaveHandle = _paint16->bitsSave(_ports->_menuRect, GFX_SCREEN_MASK_VISUAL);
822
823 _ports->penColor(0);
824 _ports->backColor(_screen->getColorWhite());
825
826 drawBar();
827 drawMenu(0, curItemEntry->menuId);
828 invertMenuSelection(curItemEntry->id);
829 _paint16->bitsShow(_ports->_menuRect);
830 _paint16->bitsShow(_menuRect);
831
832 int multiplier = !g_sci->isLanguageRTL() ? 1 : -1;
833
834 while (true) {
835 curEvent = _event->getSciEvent(kSciEventAny);
836
837 switch (curEvent.type) {
838 case kSciEventKeyDown:
839 // We don't 100% follow sierra here:
840 // - sierra didn't wrap around when changing item id
841 // - sierra allowed item id to be 0, which didn't make any sense
842 do {
843 switch (curEvent.character) {
844 case kSciKeyEsc:
845 _curMenuId = curItemEntry->menuId; _curItemId = curItemEntry->id;
846 return NULL;
847 case kSciKeyEnter:
848 if (curItemEntry->enabled) {
849 _curMenuId = curItemEntry->menuId; _curItemId = curItemEntry->id;
850 return curItemEntry;
851 }
852 break;
853 case kSciKeyLeft:
854 newMenuId -= 1 * multiplier;
855 newItemId = 1;
856 break;
857 case kSciKeyRight:
858 newMenuId += 1 * multiplier;
859 newItemId = 1;
860 break;
861 case kSciKeyUp:
862 newItemId--;
863 break;
864 case kSciKeyDown:
865 newItemId++;
866 break;
867 default:
868 break;
869 }
870 if ((newMenuId != curItemEntry->menuId) || (newItemId != curItemEntry->id)) {
871 // Selection changed, fix up new selection if required
872 newItemEntry = interactiveGetItem(newMenuId, newItemId, newMenuId != curItemEntry->menuId);
873 newMenuId = newItemEntry->menuId; newItemId = newItemEntry->id;
874
875 // if we do this step again because of a separator line -> don't repeat left/right, but go down
876 switch (curEvent.character) {
877 case kSciKeyLeft:
878 case kSciKeyRight:
879 curEvent.character = kSciKeyDown;
880 break;
881 default:
882 break;
883 }
884 }
885 } while (newItemEntry->separatorLine);
886 if ((newMenuId != curItemEntry->menuId) || (newItemId != curItemEntry->id)) {
887 // paint old and new
888 if (newMenuId != curItemEntry->menuId) {
889 // Menu changed, remove cur menu and paint new menu
890 drawMenu(curItemEntry->menuId, newMenuId);
891 } else {
892 invertMenuSelection(curItemEntry->id);
893 }
894 invertMenuSelection(newItemId);
895
896 curItemEntry = newItemEntry;
897 }
898 break;
899
900 case kSciEventMousePress: {
901 Common::Point mousePosition = curEvent.mousePos;
902 if (mousePosition.y < 10) {
903 // Somewhere on the menubar
904 newMenuId = mouseFindMenuSelection(mousePosition);
905 if (newMenuId) {
906 newItemId = 1;
907 newItemEntry = interactiveGetItem(newMenuId, newItemId, newMenuId != curItemEntry->menuId);
908 if (newMenuId != curItemEntry->menuId) {
909 drawMenu(curItemEntry->menuId, newMenuId);
910 } else {
911 invertMenuSelection(curItemEntry->id);
912 }
913 invertMenuSelection(newItemId);
914 curItemEntry = newItemEntry;
915 } else {
916 newMenuId = curItemEntry->menuId;
917 }
918 } else {
919 // Somewhere below menubar
920 newItemId = mouseFindMenuItemSelection(mousePosition, newMenuId);
921 if (newItemId) {
922 newItemEntry = interactiveGetItem(newMenuId, newItemId, false);
923 if ((newItemEntry->enabled) && (!newItemEntry->separatorLine)) {
924 _curMenuId = newItemEntry->menuId; _curItemId = newItemEntry->id;
925 return newItemEntry;
926 }
927 newItemEntry = curItemEntry;
928 }
929 newItemId = curItemEntry->id;
930 }
931 } break;
932
933 case kSciEventNone:
934 g_sci->sleep(2500 / 1000);
935 break;
936
937 case kSciEventQuit:
938 return NULL;
939
940 default:
941 break;
942 }
943 }
944 }
945
946 // Mouse button is currently pressed - we are now interpreting mouse coordinates
947 // till mouse button is released. The menu item that is selected at that time is
948 // chosen. If no menu item is selected we cancel. No keyboard interaction is
949 // allowed, cause that wouldnt make any sense at all.
interactiveWithMouse()950 GuiMenuItemEntry *GfxMenu::interactiveWithMouse() {
951 SciEvent curEvent;
952 uint16 newMenuId = 0, newItemId = 0;
953 uint16 curMenuId = 0, curItemId = 0;
954 bool firstMenuChange = true;
955 GuiMenuItemEntry *curItemEntry = NULL;
956
957 _oldPort = _ports->setPort(_ports->_menuPort);
958 calculateMenuAndItemWidth();
959 _barSaveHandle = _paint16->bitsSave(_ports->_menuRect, GFX_SCREEN_MASK_VISUAL);
960
961 _ports->penColor(0);
962 _ports->backColor(_screen->getColorWhite());
963
964 drawBar();
965 _paint16->bitsShow(_ports->_menuRect);
966
967 while (true) {
968 curEvent = _event->getSciEvent(kSciEventAny);
969
970 switch (curEvent.type) {
971 case kSciEventMouseRelease:
972 if ((curMenuId == 0) || (curItemId == 0))
973 return NULL;
974 if ((!curItemEntry->enabled) || (curItemEntry->separatorLine))
975 return NULL;
976 return curItemEntry;
977
978 case kSciEventNone:
979 g_sci->sleep(2500 / 1000);
980 break;
981
982 default:
983 break;
984 }
985
986 // Find out where mouse is currently pointing to
987 Common::Point mousePosition = curEvent.mousePos;
988 if (mousePosition.y < 10) {
989 // Somewhere on the menubar
990 newMenuId = mouseFindMenuSelection(mousePosition);
991 newItemId = 0;
992 } else {
993 // Somewhere below menubar
994 newItemId = mouseFindMenuItemSelection(mousePosition, newMenuId);
995 curItemEntry = interactiveGetItem(curMenuId, newItemId, false);
996 }
997
998 if (newMenuId != curMenuId) {
999 // Menu changed, remove cur menu and paint new menu
1000 drawMenu(curMenuId, newMenuId);
1001 if (firstMenuChange) {
1002 _paint16->bitsShow(_ports->_menuBarRect);
1003 firstMenuChange = false;
1004 }
1005 curMenuId = newMenuId;
1006 } else {
1007 if (newItemId != curItemId) {
1008 // Item changed
1009 invertMenuSelection(curItemId);
1010 invertMenuSelection(newItemId);
1011 curItemId = newItemId;
1012 }
1013 }
1014
1015 }
1016 return NULL;
1017 }
1018
kernelDrawStatus(const char * text,int16 colorPen,int16 colorBack)1019 void GfxMenu::kernelDrawStatus(const char *text, int16 colorPen, int16 colorBack) {
1020 Port *oldPort = _ports->setPort(_ports->_menuPort);
1021
1022 _paint16->fillRect(_ports->_menuBarRect, 1, colorBack);
1023 _ports->penColor(colorPen);
1024 if (!g_sci->isLanguageRTL()) {
1025 _ports->moveTo(0, 1);
1026 } else {
1027 int16 textWidth;
1028 int16 textHeight;
1029 _text16->StringWidth(text, _text16->GetFontId(), textWidth, textHeight);
1030 _ports->moveTo(_screen->getWidth() - textWidth, 1);
1031 }
1032 _text16->DrawStatus(text);
1033 _paint16->bitsShow(_ports->_menuBarRect);
1034 // Also draw the line under the status bar. Normally, this is never drawn,
1035 // but we need it to be drawn because Dr. Brain 1 Mac draws over it when
1036 // it displays the icon bar. SSCI used negative rectangles to erase the
1037 // area after drawing the icon bar, but this is a much cleaner way of
1038 // achieving the same effect.
1039 _paint16->fillRect(_ports->_menuLine, 1, 0);
1040 _paint16->bitsShow(_ports->_menuLine);
1041 _ports->setPort(oldPort);
1042 }
1043
kernelDrawMenuBar(bool clear)1044 void GfxMenu::kernelDrawMenuBar(bool clear) {
1045 if (!clear) {
1046 Port *oldPort = _ports->setPort(_ports->_menuPort);
1047 calculateMenuWidth();
1048 drawBar();
1049 _paint16->bitsShow(_ports->_menuBarRect);
1050 _ports->setPort(oldPort);
1051 } else {
1052 kernelDrawStatus("", 0, 0);
1053 }
1054 }
1055
1056 } // End of namespace Sci
1057