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