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 "sherlock/tattoo/widget_inventory.h"
24 #include "sherlock/tattoo/tattoo_fixed_text.h"
25 #include "sherlock/tattoo/tattoo_people.h"
26 #include "sherlock/tattoo/tattoo_scene.h"
27 #include "sherlock/tattoo/tattoo_user_interface.h"
28 #include "sherlock/tattoo/tattoo.h"
29 
30 namespace Sherlock {
31 
32 namespace Tattoo {
33 
34 #define INVENTORY_XSIZE 70			// Width of the box that surrounds inventory items
35 #define INVENTORY_YSIZE 70			// Height of the box that surrounds inventory items
36 #define MAX_INV_COMMANDS 10			// Maximum elements in dialog
37 #define NUM_INV_PER_LINE 4			// Number of inentory items per line in the dialog
38 
WidgetInventoryTooltip(SherlockEngine * vm,WidgetInventory * owner)39 WidgetInventoryTooltip::WidgetInventoryTooltip(SherlockEngine *vm, WidgetInventory *owner) :
40 		WidgetTooltipBase(vm), _owner(owner) {
41 }
42 
setText(const Common::String & str)43 void WidgetInventoryTooltip::setText(const Common::String &str) {
44 	// If no text specified, erase any previously displayed tooltip and free its surface
45 	if (str.empty()) {
46 		erase();
47 		_surface.free();
48 		return;
49 	}
50 
51 	int width = _surface.stringWidth(str) + 2;
52 	int height = 0;
53 	Common::String line1 = str, line2;
54 
55 	// See if we need to split it into two lines
56 	if (width > 150) {
57 		// Yes, we do
58 		const char *s = str.c_str();
59 		const char *space = nullptr;
60 		int dif = 10000;
61 
62 		while (*s) {
63 			s = strchr(s, ' ');
64 
65 			if (!s) {
66 				if (!space) {
67 					height = _surface.stringHeight(str) + 2;
68 				} else {
69 					line1 = Common::String(str.c_str(), space);
70 					line2 = Common::String(space + 1);
71 					height = _surface.stringHeight(line1) + _surface.stringHeight(line2) + 4;
72 				}
73 				break;
74 			} else {
75 				line1 = Common::String(str.c_str(), s);
76 				line2 = Common::String(s + 1);
77 				int width1 = _surface.stringWidth(line1);
78 				int width2 = _surface.stringWidth(line2);
79 
80 				if (ABS(width1 - width2) < dif) {
81 					// Found a split point that results in less overall width
82 					space = s;
83 					dif = ABS(width1 - width2);
84 					width = MAX(width1, width2);
85 				}
86 
87 				s++;
88 			}
89 		}
90 	} else {
91 		height = _surface.stringHeight(str) + 2;
92 	}
93 
94 	// Allocate a fresh surface for the new string
95 	_bounds = Common::Rect(width, height);
96 	_surface.create(width, height);
97 	_surface.clear(TRANSPARENCY);
98 
99 	if (line2.empty()) {
100 		_surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP);
101 	} else {
102 		int xp, yp;
103 
104 		xp = (_bounds.width() - _surface.stringWidth(line1) - 2) / 2;
105 		_surface.writeFancyString(line1, Common::Point(xp, 0), BLACK, INFO_TOP);
106 
107 		xp = (_bounds.width() - _surface.stringWidth(line2) - 2) / 2;
108 		yp = _surface.stringHeight(line2) + 2;
109 		_surface.writeFancyString(line2, Common::Point(xp, yp), BLACK, INFO_TOP);
110 	}
111 }
112 
handleEvents()113 void WidgetInventoryTooltip::handleEvents() {
114 	Events &events = *_vm->_events;
115 	FixedText &fixedText = *_vm->_fixedText;
116 	Inventory &inv = *_vm->_inventory;
117 	TattooPeople &people = *(TattooPeople *)_vm->_people;
118 	Scene &scene = *_vm->_scene;
119 	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
120 	Common::Point mousePos = events.mousePos();
121 	Common::String str;
122 	int select = -1, oldSelect = 999;
123 	Common::String strWith = fixedText.getText(kFixedText_With);
124 	Common::String strUse = fixedText.getText(kFixedText_Use);
125 
126 	// Register the tooltip for requiring post-rendering drawing, since we draw directly to the screen if a scene
127 	// mask is active, since the initial draw to the screen will be covered by the mask rendering
128 	if (ui._mask) {
129 		ui._postRenderWidgets.push_back(this);
130 	}
131 
132 	// If we are using an inventory item on an object in the room, display the appropriate text above the mouse cursor
133 	if (_owner->_invVerbMode == 3) {
134 		select = ui._bgFound;
135 		oldSelect = ui._oldBgFound;
136 
137 		if (select != -1 && (select != oldSelect || (select != -1 && _surface.empty()))) {
138 			// See if we're pointing at a shape or a sprite
139 			if (select < 1000) {
140 				Object &obj = scene._bgShapes[select];
141 
142 				if (!obj._description.empty() && !obj._description.hasPrefix(" ")) {
143 					if (_vm->getLanguage() == Common::GR_GRE) {
144 
145 						if (!_owner->_swapItems)
146 							str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), obj._description.c_str(),
147 								inv[_owner->_invSelect]._name.c_str(), _owner->_verb.c_str());
148 						else
149 							str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str(),
150 								obj._description.c_str(), _owner->_verb.c_str());
151 					} else {
152 						if (_owner->_swapItems)
153 							str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), obj._description.c_str(), _owner->_action.c_str(),
154 								inv[_owner->_invSelect]._name.c_str());
155 						else
156 							str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), inv[_owner->_invSelect]._name.c_str(),
157 								_owner->_action.c_str(), obj._description.c_str());
158 					}
159 				}
160 			} else {
161 				Person &person = people[ui._bgFound - 1000];
162 
163 				if (!person._description.empty() && !person._description.hasPrefix(" ")) {
164 					if (_vm->getLanguage() == Common::GR_GRE) {
165 						if (!_owner->_swapItems)
166 							str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), person._description.c_str(),
167 								inv[_owner->_invSelect]._name.c_str(), _owner->_verb.c_str());
168 						else
169 							str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str(),
170 								person._description.c_str(), _owner->_verb.c_str());
171 					} else {
172 
173 						if (_owner->_swapItems)
174 							str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), person._description.c_str(),
175 								_owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str());
176 						else
177 							str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(),
178 								inv[_owner->_invSelect]._name.c_str(), _owner->_action.c_str(), person._description.c_str());
179 					}
180 				}
181 			}
182 		}
183 	} else {
184 		const Common::Rect &b = _owner->_bounds;
185 		Common::Rect r(b.left + 3, b.top + 3, b.right - 3 - BUTTON_SIZE, b.bottom - 3);
186 
187 		if (r.contains(mousePos)) {
188 			select = (mousePos.x - r.left) / (INVENTORY_XSIZE + 3) + NUM_INVENTORY_SHOWN / 2 *
189 				((mousePos.y - r.top) / (INVENTORY_YSIZE + 3)) + inv._invIndex;
190 
191 			if (select >= inv._holdings) {
192 				select = -1;
193 			} else {
194 				oldSelect = _owner->_invSelect;
195 
196 				if (select != _owner->_invSelect || _surface.empty()) {
197 
198 					if (_owner->_invMode == 1) {
199 						// See if we were pointing at a shapre or sprite
200 						if (ui._activeObj < 1000) {
201 							Object &obj = scene._bgShapes[ui._activeObj];
202 
203 							if (!obj._description.empty() && !obj._description.hasPrefix(" "))
204 								str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[select]._name.c_str(),
205 									strWith.c_str(), obj._description.c_str());
206 						} else {
207 							Person &person = people[ui._activeObj - 1000];
208 
209 							if (!person._description.empty() && !person._description.hasPrefix(" "))
210 								str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[select]._name.c_str(),
211 									strWith.c_str(), person._description.c_str());
212 						}
213 					} else {
214 						if (_owner->_invVerbMode == 2)
215 							str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[_owner->_invSelect]._name.c_str(),
216 								strWith.c_str(), inv[select]._name.c_str());
217 						else
218 							str = inv[select]._description.c_str();
219 					}
220 				}
221 			}
222 		}
223 	}
224 
225 	// See if they are pointing at a different inventory object and we need to
226 	// change the graphics of the Text Tag
227 	if (select != oldSelect || (select != -1 && _surface.empty())) {
228 		// Set the text
229 		setText(str);
230 
231 		if (_owner->_invVerbMode != 3)
232 			_owner->_invSelect = select;
233 		else
234 			ui._oldBgFound = select;
235 	} else if (select == -1 && oldSelect != -1) {
236 		setText(Common::String());
237 		return;
238 	}
239 
240 	if (_owner->_invVerbMode == 3)
241 		// Adjust tooltip to be above the inventory item being shown above the standard cursor
242 		mousePos.y -= events._hotspotPos.y;
243 
244 	// Update the position of the tooltip
245 	int xs = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCENE_WIDTH - _bounds.width());
246 	int ys = CLIP(mousePos.y - _bounds.height(), 0, SHERLOCK_SCREEN_HEIGHT - _bounds.height());
247 	_bounds.moveTo(xs, ys);
248 }
249 
250 /*----------------------------------------------------------------*/
251 
WidgetInventoryVerbs(SherlockEngine * vm,WidgetInventory * owner)252 WidgetInventoryVerbs::WidgetInventoryVerbs(SherlockEngine *vm, WidgetInventory *owner) :
253 		WidgetBase(vm), _owner(owner) {
254 	_invVerbSelect = _oldInvVerbSelect = -1;
255 }
256 
load()257 void WidgetInventoryVerbs::load() {
258 	Events &events = *_vm->_events;
259 	Inventory &inv = *_vm->_inventory;
260 	People &people = *_vm->_people;
261 	Scene &scene = *_vm->_scene;
262 	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
263 	Common::Point mousePos = events.mousePos();
264 
265 	// Make the Verb List for this Inventory Item
266 	_inventCommands.clear();
267 	_inventCommands.push_back(FIXED(Look));
268 
269 	// Default the Action word to "with"
270 	_owner->_action = _vm->getLanguage() == Common::GR_GRE ? "" : FIXED(With);
271 
272 	// Search all the bgshapes for any matching Target Fields
273 	for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
274 		Object &obj = scene._bgShapes[idx];
275 
276 		if (obj._type != INVALID && obj._type != HIDDEN) {
277 			for (int useNum = 0; useNum < 6; ++useNum) {
278 				if (!obj._use[useNum]._verb.hasPrefix("*") &&
279 					!obj._use[useNum]._target.compareToIgnoreCase(inv[_owner->_invSelect]._name)) {
280 					// Make sure the Verb is not already in the list
281 					bool found1 = false;
282 					for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) {
283 						if (!_inventCommands[cmdNum].compareToIgnoreCase(obj._use[useNum]._verb))
284 							found1 = true;
285 					}
286 
287 					if (!found1) {
288 						_inventCommands.push_back(obj._use[useNum]._verb);
289 
290 						// Check for any Special Action commands
291 						for (int nameNum = 0; nameNum < 4; ++nameNum) {
292 							if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*V", 2)) {
293 								if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*VSWAP", 6))
294 									_owner->_swapItems = true;
295 								else
296 									_owner->_action = Common::String(obj._use[useNum]._names[nameNum].c_str() + 2);
297 							}
298 						}
299 					}
300 				}
301 			}
302 		}
303 	}
304 
305 	// Search the NPCs for matches as well
306 	for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
307 		for (int useNum = 0; useNum < 2; ++useNum) {
308 			if (!people[idx]._use[useNum]._target.compareToIgnoreCase(inv[_owner->_invSelect]._name) &&
309 				!people[idx]._use[useNum]._verb.empty() && !people[idx]._use[useNum]._verb.hasPrefix(" ")) {
310 				bool found1 = false;
311 				for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) {
312 					if (!_inventCommands[cmdNum].compareToIgnoreCase(people[idx]._use[cmdNum]._verb))
313 						found1 = true;
314 				}
315 
316 				if (!found1)
317 					_inventCommands.push_back(people[idx]._use[useNum]._verb);
318 			}
319 		}
320 	}
321 
322 	// Finally see if the item itself has a verb
323 	if (!inv[_owner->_invSelect]._verb._verb.empty()) {
324 		// Don't add "Solve" to the Foolscap if it's already been "Solved"
325 		if (inv[_owner->_invSelect]._verb._verb.compareToIgnoreCase(FIXED(Solve)) || !_vm->readFlags(299))
326 			_inventCommands.push_back(inv[_owner->_invSelect]._verb._verb);
327 	}
328 
329 	// Now find the widest command in the _inventCommands array
330 	int width = 0;
331 	for (uint idx = 0; idx < _inventCommands.size(); ++idx)
332 		width = MAX(width, _surface.stringWidth(_inventCommands[idx]));
333 
334 	// Set up bounds for the menu
335 	_bounds = Common::Rect(width + _surface.widestChar() * 2 + 6,
336 		(_surface.fontHeight() + 7) * _inventCommands.size() + 3);
337 	_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
338 
339 	// Create the surface
340 	_surface.create(_bounds.width(), _bounds.height());
341 	_surface.clear(TRANSPARENCY);
342 	makeInfoArea();
343 
344 	// Draw the Verb commands and the lines separating them
345 	ImageFile &images = *ui._interfaceImages;
346 	for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) {
347 		_surface.writeString(_inventCommands[idx], Common::Point((_bounds.width() -
348 			_surface.stringWidth(_inventCommands[idx])) / 2, (_surface.fontHeight() + 7) * idx + 5), INFO_TOP);
349 
350 		if (idx < (int)_inventCommands.size() - 1) {
351 			_surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1), _bounds.right - 4, INFO_TOP);
352 			_surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.right - 4, INFO_MIDDLE);
353 			_surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.right - 4, INFO_BOTTOM);
354 
355 			_surface.SHtransBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1)));
356 			_surface.SHtransBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width,
357 				(_surface.fontHeight() + 7) * (idx + 1) - 1));
358 		}
359 	}
360 
361 	summonWindow();
362 }
363 
handleEvents()364 void WidgetInventoryVerbs::handleEvents() {
365 	Events &events = *_vm->_events;
366 	Inventory &inv = *_vm->_inventory;
367 	TattooScene &scene = *(TattooScene *)_vm->_scene;
368 	Common::Point mousePos = events.mousePos();
369 	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
370 	TattooEngine &vm = *(TattooEngine *)_vm;
371 
372 	// Handle changing highlighted verb entry
373 	highlightControls();
374 
375 	// See if they want to close the menu (by clicking outside the menu)
376 	Common::Rect innerBounds = _bounds;
377 	innerBounds.grow(-3);
378 
379 	// Flag is they are pressing outside of the menu
380 	if (!innerBounds.contains(mousePos))
381 		_outsideMenu = true;
382 
383 	if (events._released || events._rightReleased || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
384 		ui._scrollHighlight = SH_NONE;
385 		banishWindow();
386 
387 		if (_outsideMenu || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
388 			_owner->_invVerbMode = 0;
389 		} else if (innerBounds.contains(mousePos)) {
390 			_outsideMenu = false;
391 
392 			// Check if they are trying to solve the Foolscap puzzle, or looking at the completed puzzle
393 			bool doFoolscap = !inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) &&
394 				!_inventCommands[_invVerbSelect].compareToIgnoreCase(FIXED(Solve));
395 			doFoolscap |= (!inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) || !inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv7)))
396 				&& !_inventCommands[_invVerbSelect].compareToIgnoreCase(FIXED(Look)) && vm.readFlags(299);
397 
398 			if (doFoolscap) {
399 				// Close the entire Inventory and return to Standard Mode
400 				_owner->_invVerbMode = 0;
401 
402 				_owner->_tooltipWidget.banishWindow();
403 				_owner->banishWindow();
404 				inv.freeInv();
405 
406 				events.clearEvents();
407 				vm.doFoolscapPuzzle();
408 			} else if (_invVerbSelect == 0) {
409 				// They have released the mouse on the Look Verb command, so Look at the inventory item
410 				ui._invLookFlag = true;
411 				inv.freeInv();
412 				ui._windowOpen = false;
413 				ui._lookPos = mousePos;
414 				ui.printObjectDesc(inv[_owner->_invSelect]._examine, true);
415 			} else {
416 				_owner->_invVerbMode = 3;
417 				ui._oldBgFound = -1;
418 
419 				// See if the selected Verb with the selected Iventory Item, is to be used by itself
420 				if (!_inventCommands[_invVerbSelect].compareToIgnoreCase(inv[_owner->_invSelect]._verb._verb) ||
421 					!inv[_owner->_invSelect]._verb._target.compareToIgnoreCase("*SELF")) {
422 					inv.freeInv();
423 
424 					ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
425 					events.clearEvents();
426 					ui.checkAction(inv[_owner->_invSelect]._verb, 2000);
427 				} else {
428 					_owner->_verb = _inventCommands[_invVerbSelect];
429 				}
430 
431 				// If we are still in Inventory Mode, setup the graphic to float in front of the mouse cursor
432 				if (ui._menuMode == INV_MODE) {
433 					// Add the inventory item to the cursor
434 					ImageFrame &imgFrame = (*inv._invShapes[_owner->_invSelect - inv._invIndex])[0];
435 					events.setCursor(ARROW, Common::Point(-100, imgFrame._height), imgFrame._frame);
436 
437 					// Close the inventory dialog without banishing it, so it can keep getting events
438 					// to handle tooltips and actually making the selection of what object to use them item on
439 					inv.freeInv();
440 					_owner->_surface.free();
441 				}
442 			}
443 		}
444 	}
445 }
446 
highlightControls()447 void WidgetInventoryVerbs::highlightControls() {
448 	Events &events = *_vm->_events;
449 	Common::Point mousePos = events.mousePos();
450 
451 	Common::Rect innerBounds = _bounds;
452 	innerBounds.grow(-3);
453 
454 	// Set the highlighted verb
455 	_invVerbSelect = -1;
456 	if (innerBounds.contains(mousePos))
457 		_invVerbSelect = (mousePos.y - _bounds.top - 3) / (_surface.fontHeight() + 7);
458 
459 	// See if the highlighted verb has changed
460 	if (_invVerbSelect != _oldInvVerbSelect) {
461 		// Draw the list again, with the new highlighting
462 		for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) {
463 			byte color = (idx == _invVerbSelect) ? COMMAND_HIGHLIGHTED : INFO_TOP;
464 			_surface.writeString(_inventCommands[idx], Common::Point(
465 				(_bounds.width() - _surface.stringWidth(_inventCommands[idx])) / 2,
466 				(_surface.fontHeight() + 7) * idx + 5), color);
467 		}
468 
469 		_oldInvVerbSelect = _invVerbSelect;
470 	}
471 }
472 
473 /*----------------------------------------------------------------*/
474 
WidgetInventory(SherlockEngine * vm)475 WidgetInventory::WidgetInventory(SherlockEngine *vm) : WidgetBase(vm),
476 		_tooltipWidget(vm, this), _verbList(vm, this) {
477 	_invMode = 0;
478 	_invVerbMode = 0;
479 	_invSelect = _oldInvSelect = -1;
480 	_selector = _oldSelector = -1;
481 	_swapItems = false;
482 }
483 
load(int mode)484 void WidgetInventory::load(int mode) {
485 	Events &events = *_vm->_events;
486 	Inventory &inv = *_vm->_inventory;
487 	Screen &screen = *_vm->_screen;
488 	Common::Point mousePos = events.mousePos();
489 
490 	if (mode == 3) {
491 		mode = 2;
492 		mousePos = Common::Point(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
493 	}
494 
495 	if (mode != 0)
496 		_invMode = mode;
497 	_invVerbMode = 0;
498 	_invSelect = _oldInvSelect = -1;
499 	_selector = _oldSelector = -1;
500 	_scroll = true;
501 
502 	if (mode == 0) {
503 		banishWindow();
504 	} else {
505 		_bounds = Common::Rect((INVENTORY_XSIZE + 3) * NUM_INVENTORY_SHOWN / 2 + BUTTON_SIZE + 6,
506 			(INVENTORY_YSIZE + 3) * 2 + 3);
507 		_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
508 	}
509 
510 	// Ensure menu will be on-screen
511 	restrictToScreen();
512 
513 	// Load the inventory data
514 	inv.loadInv();
515 
516 	// Redraw the inventory menu on the widget surface
517 	_surface.create(_bounds.width(), _bounds.height());
518 	_surface.clear(TRANSPARENCY);
519 
520 	// Draw the window background and then the inventory on top of it
521 	makeInfoArea(_surface);
522 	drawBars();
523 	drawInventory();
524 }
525 
drawBars()526 void WidgetInventory::drawBars() {
527 	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
528 	ImageFile &images = *ui._interfaceImages;
529 	int x;
530 
531 	_surface.hLine(3, INVENTORY_YSIZE + 3, _bounds.width() - 4, INFO_TOP);
532 	_surface.hLine(3, INVENTORY_YSIZE + 4, _bounds.width() - 4, INFO_MIDDLE);
533 	_surface.hLine(3, INVENTORY_YSIZE + 5, _bounds.width() - 4, INFO_BOTTOM);
534 	_surface.SHtransBlitFrom(images[4], Common::Point(0, INVENTORY_YSIZE + 2));
535 
536 	for (int idx = 1; idx <= NUM_INVENTORY_SHOWN / 2; ++idx) {
537 		x = idx * (INVENTORY_XSIZE + 3);
538 
539 		_surface.vLine(x, 3, _bounds.height() - 4, INFO_TOP);
540 		_surface.vLine(x + 1, 3, _bounds.height() - 4, INFO_MIDDLE);
541 		_surface.vLine(x + 2, 3, _bounds.height() - 4, INFO_BOTTOM);
542 
543 		_surface.SHtransBlitFrom(images[6], Common::Point(x - 1, 1));
544 		_surface.SHtransBlitFrom(images[7], Common::Point(x - 1, _bounds.height() - 4));
545 		_surface.SHtransBlitFrom(images[6], Common::Point(x - 1, INVENTORY_YSIZE + 5));
546 		_surface.SHtransBlitFrom(images[7], Common::Point(x - 1, INVENTORY_YSIZE + 2));
547 	}
548 
549 	_surface.vLine(x + 2, INVENTORY_YSIZE + 2, INVENTORY_YSIZE + 8, INFO_BOTTOM);
550 }
551 
drawInventory()552 void WidgetInventory::drawInventory() {
553 	Inventory &inv = *_vm->_inventory;
554 
555 	// TODO: Refactor _invIndex into this widget class
556 	for (int idx = 0, itemId = inv._invIndex; idx < NUM_INVENTORY_SHOWN; ++idx, ++itemId) {
557 		// Figure out the drawing position
558 		Common::Point pt(3 + (INVENTORY_XSIZE + 3) * (idx % (NUM_INVENTORY_SHOWN / 2)),
559 			3 + (INVENTORY_YSIZE + 3) * (idx / (NUM_INVENTORY_SHOWN / 2)));
560 
561 		// Draw the box to serve as the background for the item
562 		_surface.hLine(pt.x + 1, pt.y, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY);
563 		_surface.fillRect(Common::Rect(pt.x, pt.y + 1, pt.x + INVENTORY_XSIZE, pt.y + INVENTORY_YSIZE - 1), TRANSPARENCY);
564 		_surface.hLine(pt.x + 1, pt.y + INVENTORY_YSIZE - 1, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY);
565 
566 		// Draw the item
567 		if (itemId < inv._holdings) {
568 			ImageFrame &img = (*inv._invShapes[idx])[0];
569 			_surface.SHtransBlitFrom(img, Common::Point(pt.x + (INVENTORY_XSIZE - img._width) / 2,
570 				pt.y + (INVENTORY_YSIZE - img._height) / 2));
571 		}
572 	}
573 
574 	drawScrollBar(inv._invIndex / NUM_INV_PER_LINE, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE,
575 		(inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE);
576 }
577 
handleEvents()578 void WidgetInventory::handleEvents() {
579 	TattooEngine &vm = *(TattooEngine *)_vm;
580 	Events &events = *_vm->_events;
581 	Inventory &inv = *_vm->_inventory;
582 	People &people = *_vm->_people;
583 	TattooScene &scene = *(TattooScene *)_vm->_scene;
584 	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
585 	Common::Point mousePos = events.mousePos();
586 
587 	if (_invVerbMode == 1) {
588 		checkTabbingKeys(MAX_INV_COMMANDS);
589 	} else if (_invVerbMode == 0) {
590 		checkInvTabbingKeys();
591 
592 		// Handle scrollbar events
593 		int oldScrollIndex = inv._invIndex / NUM_INV_PER_LINE;
594 		int invIndex = inv._invIndex / NUM_INV_PER_LINE;
595 
596 		ScrollHighlight oldHighlight = ui._scrollHighlight;
597 		handleScrollbarEvents(invIndex, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE,
598 			(inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE);
599 
600 		handleScrolling(invIndex, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE,
601 			(inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE);
602 
603 		if (oldScrollIndex != invIndex) {
604 			// Starting visible item index has changed, so set the index and reload inventory graphics
605 			inv._invIndex = invIndex * NUM_INV_PER_LINE;
606 			inv.freeGraphics();
607 			inv.loadGraphics();
608 		}
609 
610 		if (ui._scrollHighlight != oldHighlight || oldScrollIndex != invIndex) {
611 			drawInventory();
612 			return;
613 		}
614 	}
615 
616 	if (_invVerbMode != 1)
617 		_tooltipWidget.handleEvents();
618 
619 	// Flag is they started pressing outside of the menu
620 	if (events._firstPress && !_bounds.contains(mousePos))
621 		_outsideMenu = true;
622 
623 	if (_invVerbMode != 3)
624 		highlightControls();
625 
626 	// See if they released a mouse button button
627 	if (events._released || events._rightReleased || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
628 		ui._scrollHighlight = SH_NONE;
629 
630 		// See if they have a Verb List open for an Inventry Item
631 		if (_invVerbMode == 1)
632 			return;
633 
634 		if (_invVerbMode == 3) {
635 			// Selecting object after inventory verb has been selected
636 			_tooltipWidget.banishWindow();
637 			close();
638 
639 			if (ui._keyState.keycode != Common::KEYCODE_ESCAPE) {
640 				// If user pointed at an item, use the selected inventory item with this item
641 				bool found = false;
642 				if (ui._bgFound != -1) {
643 					if (ui._personFound) {
644 						Person &person = people[ui._bgFound - 1000];
645 
646 						for (int idx = 0; idx < 2; ++idx) {
647 							if (!person._use[idx]._verb.compareToIgnoreCase(_verb) &&
648 								!person._use[idx]._target.compareToIgnoreCase(_invTarget)) {
649 								ui.checkAction(person._use[idx], ui._bgFound);
650 								found = true;
651 							}
652 						}
653 					} else {
654 						for (int idx = 0; idx < 6; ++idx) {
655 							if (!ui._bgShape->_use[idx]._verb.compareToIgnoreCase(_verb) &&
656 									!ui._bgShape->_use[idx]._target.compareToIgnoreCase(_invTarget)) {
657 								ui.checkAction(ui._bgShape->_use[idx], ui._bgFound);
658 								found = true;
659 							}
660 						}
661 					}
662 				}
663 
664 				if (!found)
665 					ui.putMessage("%s", FIXED(NoEffect));
666 			}
667 		} else if ((_outsideMenu && !_bounds.contains(mousePos)) || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
668 			// Want to close the window (clicked outside of it). So close the window and return to Standard
669 			close();
670 
671 		} else if (_bounds.contains(mousePos)) {
672 			// Mouse button was released inside the inventory window
673 			_outsideMenu = false;
674 
675 			//  See if they are pointing at one of the inventory items
676 			if (_invSelect != -1) {
677 				// See if they are in Use Obj with Inv. Mode (they right clicked on an item
678 				// in the room and selected "Use with Inv.")
679 				if (_invMode == 1) {
680 					_tooltipWidget.banishWindow();
681 					banishWindow();
682 
683 					// See if the item in the room that they started with was a person
684 					bool found = false;
685 					if (ui._activeObj >= 1000) {
686 						// Object was a person, activate anything in his two verb fields
687 						for (int idx = 0; idx < 2; ++idx) {
688 							if (!people[ui._activeObj - 1000]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) {
689 								ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj);
690 								found = true;
691 							}
692 						}
693 					} else {
694 						// Object was a regular object, activate anything in its verb fields
695 						for (int idx = 0; idx < 6; ++idx) {
696 							if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) {
697 								ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj);
698 								found = true;
699 							}
700 						}
701 					}
702 					if (!found)
703 						ui.putMessage("%s", FIXED(NoEffect));
704 
705 				} else {
706 					// See if they right clicked on an item
707 					if (events._rightReleased) {
708 						_invVerbMode = 1;
709 						_verbList._oldInvVerbSelect = -1;
710 						_tooltipWidget.banishWindow();
711 
712 						// Keep track of the name of the inventory object so we can check it against the target fields
713 						// of verbs when we activate it
714 						_invTarget = inv[_invSelect]._name;
715 						_swapItems = false;
716 
717 						_verbList.load();
718 					} else {
719 						// They left clicked on an inventory item, so Look at it
720 
721 						// Check if they are looking at the solved Foolscap
722 						if ((!inv[_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) || !inv[_invSelect]._name.compareToIgnoreCase(FIXED(Inv7)))
723 								&& vm.readFlags(299)) {
724 							banishWindow();
725 							_tooltipWidget.erase();
726 
727 							_invVerbMode = 0;
728 							inv.freeInv();
729 
730 							events.clearEvents();
731 							events.setCursor(ARROW);
732 							ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
733 
734 							scene.doBgAnim();
735 							vm.doFoolscapPuzzle();
736 						} else {
737 							ui._invLookFlag = true;
738 							inv.freeInv();
739 
740 							_tooltipWidget.banishWindow();
741 							ui._windowOpen = false;
742 							ui._lookPos = mousePos;
743 							ui.printObjectDesc(inv[_invSelect]._examine, true);
744 						}
745 					}
746 				}
747 			}
748 		}
749 	}
750 }
751 
checkInvTabbingKeys()752 void WidgetInventory::checkInvTabbingKeys() {
753 }
754 
highlightControls()755 void WidgetInventory::highlightControls() {
756 	// TODO
757 }
758 
banishWindow()759 void WidgetInventory::banishWindow() {
760 	WidgetBase::banishWindow();
761 
762 	_verbList.banishWindow();
763 }
764 
draw()765 void WidgetInventory::draw() {
766 	WidgetBase::draw();
767 	_tooltipWidget.draw();
768 }
769 
erase()770 void WidgetInventory::erase() {
771 	WidgetBase::erase();
772 	_tooltipWidget.erase();
773 }
774 
close()775 void WidgetInventory::close() {
776 	Events &events = *_vm->_events;
777 	Inventory &inv = *_vm->_inventory;
778 	TattooScene &scene = *(TattooScene *)_vm->_scene;
779 	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
780 
781 	banishWindow();
782 	inv.freeInv();
783 	events.clearEvents();
784 
785 	events.setCursor(ARROW);
786 	ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
787 }
788 
789 } // End of namespace Tattoo
790 
791 } // End of namespace Sherlock
792