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/system.h"
24 #include "common/translation.h"
25 #include "gui/message.h"
26 #include "sci/sci.h"
27 #include "sci/console.h"
28 #include "sci/event.h"
29 #include "sci/engine/kernel.h"
30 #include "sci/engine/seg_manager.h"
31 #include "sci/engine/state.h"
32 #include "sci/graphics/cache.h"
33 #include "sci/graphics/compare.h"
34 #include "sci/graphics/controls32.h"
35 #include "sci/graphics/scifont.h"
36 #include "sci/graphics/screen.h"
37 #include "sci/graphics/text32.h"
38
39 namespace Sci {
GfxControls32(SegManager * segMan,GfxCache * cache,GfxText32 * text)40 GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) :
41 _segMan(segMan),
42 _gfxCache(cache),
43 _gfxText32(text),
44 _overwriteMode(false),
45 _nextCursorFlashTick(0),
46 // SSCI used a memory handle for a ScrollWindow object as ID. We use a
47 // simple numeric handle instead.
48 _nextScrollWindowId(10000) {}
49
~GfxControls32()50 GfxControls32::~GfxControls32() {
51 ScrollWindowMap::iterator it;
52 for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
53 delete it->_value;
54 }
55
56 #pragma mark -
57 #pragma mark Text input control
58
kernelEditText(const reg_t controlObject)59 reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
60 TextEditor editor;
61 reg_t textObject = readSelector(_segMan, controlObject, SELECTOR(text));
62 editor.text = _segMan->getString(textObject);
63 editor.foreColor = readSelectorValue(_segMan, controlObject, SELECTOR(fore));
64 editor.backColor = readSelectorValue(_segMan, controlObject, SELECTOR(back));
65 editor.skipColor = readSelectorValue(_segMan, controlObject, SELECTOR(skip));
66 editor.fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font));
67 editor.maxLength = readSelectorValue(_segMan, controlObject, SELECTOR(width));
68 editor.bitmap = readSelector(_segMan, controlObject, SELECTOR(bitmap));
69 editor.cursorCharPosition = 0;
70 editor.cursorIsDrawn = false;
71 editor.borderColor = readSelectorValue(_segMan, controlObject, SELECTOR(borderColor));
72
73 reg_t titleObject = readSelector(_segMan, controlObject, SELECTOR(title));
74
75 int16 titleHeight = 0;
76 GuiResourceId titleFontId = readSelectorValue(_segMan, controlObject, SELECTOR(titleFont));
77 if (!titleObject.isNull()) {
78 GfxFont *titleFont = _gfxCache->getFont(titleFontId);
79 titleHeight += _gfxText32->scaleUpHeight(titleFont->getHeight()) + 1;
80 if (editor.borderColor != -1) {
81 titleHeight += 2;
82 }
83 }
84
85 int16 width = 0;
86 int16 height = titleHeight;
87
88 GfxFont *editorFont = _gfxCache->getFont(editor.fontId);
89 height += _gfxText32->scaleUpHeight(editorFont->getHeight()) + 1;
90 _gfxText32->setFont(editor.fontId);
91 int16 emSize = _gfxText32->getCharWidth('M', true);
92 width += editor.maxLength * emSize + 1;
93 if (editor.borderColor != -1) {
94 width += 4;
95 height += 2;
96 }
97
98 Common::Rect editorPlaneRect(width, height);
99 editorPlaneRect.translate(readSelectorValue(_segMan, controlObject, SELECTOR(x)), readSelectorValue(_segMan, controlObject, SELECTOR(y)));
100
101 reg_t planeObj = readSelector(_segMan, controlObject, SELECTOR(plane));
102 Plane *sourcePlane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
103 if (sourcePlane == nullptr) {
104 sourcePlane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
105 if (sourcePlane == nullptr) {
106 error("Could not find plane %04x:%04x", PRINT_REG(planeObj));
107 }
108 }
109 editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top);
110
111 editor.textRect = Common::Rect(2, titleHeight + 2, width - 1, height - 1);
112 editor.width = width;
113
114 if (editor.bitmap.isNull()) {
115 TextAlign alignment = (TextAlign)readSelectorValue(_segMan, controlObject, SELECTOR(mode));
116
117 if (titleObject.isNull()) {
118 bool dimmed = readSelectorValue(_segMan, controlObject, SELECTOR(dimmed));
119 editor.bitmap = _gfxText32->createFontBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, dimmed, true, false);
120 } else {
121 error("Titled bitmaps are not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
122 }
123 }
124
125 drawCursor(editor);
126
127 Plane *plane = new Plane(editorPlaneRect, kPlanePicTransparent);
128 plane->changePic();
129 g_sci->_gfxFrameout->addPlane(plane);
130
131 CelInfo32 celInfo;
132 celInfo.type = kCelTypeMem;
133 celInfo.bitmap = editor.bitmap;
134
135 ScreenItem *screenItem = new ScreenItem(plane->_object, celInfo, Common::Point(), ScaleInfo());
136 plane->_screenItemList.add(screenItem);
137
138 // frameOut must be called after the screen item is created, and before it
139 // is updated at the end of the event loop, otherwise it has both created
140 // and updated flags set which crashes the engine (updates are handled
141 // before creations, but the screen item is not in the correct state for an
142 // update)
143 g_sci->_gfxFrameout->frameOut(true);
144
145 EventManager *eventManager = g_sci->getEventManager();
146 bool clearTextOnInput = true;
147 bool textChanged = false;
148 for (;;) {
149 // We peek here because the last event needs to be allowed to dispatch a
150 // second time to the normal event handling system. In SSCI, the event
151 // is always consumed and then the last event just gets posted back to
152 // the event manager for reprocessing, but instead, we only remove the
153 // event from the queue *after* we have determined it is not a
154 // defocusing event
155 const SciEvent event = eventManager->getSciEvent(kSciEventAny | kSciEventPeek);
156
157 bool focused = true;
158 // SSCI did not have a QUIT event, but we do, so we have to handle it
159 if (event.type == kSciEventQuit) {
160 focused = false;
161 } else if (event.type == kSciEventMousePress && !editorPlaneRect.contains(event.mousePosSci)) {
162 focused = false;
163 } else if (event.type == kSciEventKeyDown) {
164 switch (event.character) {
165 case kSciKeyEsc:
166 case kSciKeyUp:
167 case kSciKeyDown:
168 case kSciKeyTab:
169 case kSciKeyShiftTab:
170 case kSciKeyEnter:
171 focused = false;
172 break;
173 default:
174 break;
175 }
176 }
177
178 if (!focused) {
179 break;
180 }
181
182 // Consume the event now that we know it is not one of the defocusing
183 // events above
184 if (event.type != kSciEventNone)
185 eventManager->getSciEvent(kSciEventAny);
186
187 if (processEditTextEvent(event, editor, screenItem, clearTextOnInput)) {
188 textChanged = true;
189 }
190 }
191
192 g_sci->_gfxFrameout->deletePlane(*plane);
193 if (readSelectorValue(_segMan, controlObject, SELECTOR(frameOut))) {
194 g_sci->_gfxFrameout->frameOut(true);
195 }
196
197 _segMan->freeBitmap(editor.bitmap);
198
199 if (textChanged) {
200 editor.text.trim();
201 SciArray &string = *_segMan->lookupArray(textObject);
202 string.fromString(editor.text);
203 }
204
205 return make_reg(0, textChanged);
206 }
207
kernelInputText(const reg_t textObject,const reg_t titleObject,const int16 maxTextLength)208 reg_t GfxControls32::kernelInputText(const reg_t textObject, const reg_t titleObject, const int16 maxTextLength) {
209 // kInputText is only known to be used by Phantasmagoria 2 easter eggs
210
211 TextEditor editor;
212 editor.text = _segMan->getString(textObject);
213 editor.foreColor = 0;
214 editor.backColor = 255;
215 editor.skipColor = 250;
216 editor.fontId = -1;
217 editor.maxLength = maxTextLength;
218 editor.cursorCharPosition = 0;
219 editor.cursorIsDrawn = false;
220 editor.borderColor = 0;
221
222 Common::String title = _segMan->getString(titleObject);
223 _gfxText32->setFont(editor.fontId);
224 GfxFont *systemFont = _gfxCache->getFont(editor.fontId);
225 int16 textWidth = _gfxText32->getCharWidth('M', true) * maxTextLength;
226 int16 titleWidth = _gfxText32->getStringWidth(title);
227 int16 textHeight = _gfxText32->scaleUpHeight(systemFont->getHeight());
228 int16 width = MAX(textWidth, titleWidth) + 4;
229 int16 height = (textHeight * 2) + 7;
230
231 // SSCI doesn't scale the position when centering, so the dialog is
232 // centered on the left side of the screen in Phantasmagoria 2.
233 Common::Rect editorPlaneRect(width, height);
234 editorPlaneRect.translate((320 - width) / 2, (200 - height) / 2);
235 editor.textRect = Common::Rect(1, (height / 2) + 1, width - 1, height - 1);
236 editor.width = width;
237
238 editor.bitmap = _gfxText32->createTitledFontBitmap(width, height, editor.textRect, editor.text,
239 editor.foreColor, editor.backColor, editor.skipColor, editor.fontId,
240 kTextAlignLeft, editor.borderColor, title,
241 editor.backColor, editor.foreColor, // title colors are inverse
242 editor.fontId, true, true);
243
244 drawCursor(editor);
245
246 Plane *plane = new Plane(editorPlaneRect, kPlanePicTransparent);
247 plane->changePic();
248 g_sci->_gfxFrameout->addPlane(plane);
249
250 CelInfo32 celInfo;
251 celInfo.type = kCelTypeMem;
252 celInfo.bitmap = editor.bitmap;
253
254 ScreenItem *screenItem = new ScreenItem(plane->_object, celInfo, Common::Point(), ScaleInfo());
255 plane->_screenItemList.add(screenItem);
256
257 g_sci->_gfxFrameout->frameOut(true);
258
259 EventManager *eventManager = g_sci->getEventManager();
260 bool wasEnterPressed = false;
261 bool clearTextOnInput = true;
262 for (;;) {
263 const SciEvent event = eventManager->getSciEvent(kSciEventAny | kSciEventPeek);
264
265 bool exitEventLoop = false;
266 // SSCI did not have a QUIT event, but we do, so we have to handle it
267 if (event.type == kSciEventQuit) {
268 exitEventLoop = true;
269 } else if (event.type == kSciEventKeyDown) {
270 switch (event.character) {
271 case kSciKeyEsc:
272 exitEventLoop = true;
273 break;
274 case kSciKeyEnter:
275 wasEnterPressed = true;
276 exitEventLoop = true;
277 break;
278 default:
279 break;
280 }
281 }
282
283 // consume all events except QUIT so that the engine quits
284 if (event.type != kSciEventNone && event.type != kSciEventQuit) {
285 eventManager->getSciEvent(kSciEventAny);
286 }
287
288 if (exitEventLoop) {
289 break;
290 }
291
292 processEditTextEvent(event, editor, screenItem, clearTextOnInput);
293 }
294
295 g_sci->_gfxFrameout->deletePlane(*plane);
296 g_sci->_gfxFrameout->frameOut(true);
297 _segMan->freeBitmap(editor.bitmap);
298
299 editor.text.trim();
300 SciArray &string = *_segMan->lookupArray(textObject);
301 string.fromString(editor.text);
302
303 return make_reg(0, wasEnterPressed);
304 }
305
processEditTextEvent(const SciEvent & event,TextEditor & editor,ScreenItem * screenItem,bool & clearTextOnInput)306 bool GfxControls32::processEditTextEvent(const SciEvent &event, TextEditor &editor, ScreenItem *screenItem, bool &clearTextOnInput) {
307 // In SSCI, the font and bitmap were reset here on each iteration
308 // through the loop, but this is not necessary since control is not
309 // yielded back to the VM until input is received, which means there is
310 // nothing that could modify the GfxText32's state with a different font
311 // in the meantime
312
313 bool textChanged = false;
314 bool shouldDeleteChar = false;
315 bool shouldRedrawText = false;
316 uint16 lastCursorPosition = editor.cursorCharPosition;
317 if (event.type == kSciEventKeyDown) {
318 switch (event.character) {
319 case kSciKeyLeft:
320 clearTextOnInput = false;
321 if (editor.cursorCharPosition > 0) {
322 --editor.cursorCharPosition;
323 }
324 break;
325
326 case kSciKeyRight:
327 clearTextOnInput = false;
328 if (editor.cursorCharPosition < editor.text.size()) {
329 ++editor.cursorCharPosition;
330 }
331 break;
332
333 case kSciKeyHome:
334 clearTextOnInput = false;
335 editor.cursorCharPosition = 0;
336 break;
337
338 case kSciKeyEnd:
339 clearTextOnInput = false;
340 editor.cursorCharPosition = editor.text.size();
341 break;
342
343 case kSciKeyInsert:
344 clearTextOnInput = false;
345 // Redrawing also changes the cursor rect to reflect the new
346 // insertion mode
347 shouldRedrawText = true;
348 _overwriteMode = !_overwriteMode;
349 break;
350
351 case kSciKeyDelete:
352 clearTextOnInput = false;
353 if (editor.cursorCharPosition < editor.text.size()) {
354 shouldDeleteChar = true;
355 }
356 break;
357
358 case kSciKeyBackspace:
359 clearTextOnInput = false;
360 shouldDeleteChar = true;
361 if (editor.cursorCharPosition > 0) {
362 --editor.cursorCharPosition;
363 }
364 break;
365
366 case kSciKeyEtx:
367 editor.text.clear();
368 editor.cursorCharPosition = 0;
369 shouldRedrawText = true;
370 break;
371
372 default: {
373 if (event.character >= 20 && event.character < 257) {
374 if (clearTextOnInput) {
375 clearTextOnInput = false;
376 editor.text.clear();
377 }
378
379 if (
380 (_overwriteMode && editor.cursorCharPosition < editor.maxLength) ||
381 (editor.text.size() < editor.maxLength && _gfxText32->getCharWidth(event.character, true) + _gfxText32->getStringWidth(editor.text) < editor.textRect.width())
382 ) {
383 if (_overwriteMode && editor.cursorCharPosition < editor.text.size()) {
384 editor.text.setChar(event.character, editor.cursorCharPosition);
385 } else {
386 editor.text.insertChar(event.character, editor.cursorCharPosition);
387 }
388
389 ++editor.cursorCharPosition;
390 shouldRedrawText = true;
391 }
392 }
393 }
394 }
395 }
396
397 if (shouldDeleteChar) {
398 shouldRedrawText = true;
399 if (editor.cursorCharPosition < editor.text.size()) {
400 editor.text.deleteChar(editor.cursorCharPosition);
401 }
402 }
403
404 if (shouldRedrawText) {
405 eraseCursor(editor);
406 _gfxText32->erase(editor.textRect, true);
407 _gfxText32->drawTextBox(editor.text);
408 drawCursor(editor);
409 textChanged = true;
410 screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
411 } else if (editor.cursorCharPosition != lastCursorPosition) {
412 eraseCursor(editor);
413 drawCursor(editor);
414 screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
415 } else {
416 flashCursor(editor);
417 screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
418 }
419
420 g_sci->_gfxFrameout->frameOut(true);
421 g_sci->_gfxFrameout->throttle();
422
423 return textChanged;
424 }
425
drawCursor(TextEditor & editor)426 void GfxControls32::drawCursor(TextEditor &editor) {
427 if (!editor.cursorIsDrawn) {
428 editor.cursorRect.left = editor.textRect.left + _gfxText32->getTextWidth(editor.text, 0, editor.cursorCharPosition);
429
430 const int16 scaledFontHeight = _gfxText32->scaleUpHeight(_gfxText32->_font->getHeight());
431
432 // SSCI branched on borderColor here but the two branches appeared to be
433 // identical, differing only because the compiler decided to be
434 // differently clever when optimising multiplication in each branch
435 if (_overwriteMode) {
436 editor.cursorRect.top = editor.textRect.top;
437 editor.cursorRect.setHeight(scaledFontHeight);
438 } else {
439 editor.cursorRect.top = editor.textRect.top + scaledFontHeight - 1;
440 editor.cursorRect.setHeight(1);
441 }
442
443 const char currentChar = editor.cursorCharPosition < editor.text.size() ? editor.text[editor.cursorCharPosition] : ' ';
444 editor.cursorRect.setWidth(_gfxText32->getCharWidth(currentChar, true));
445
446 _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
447
448 editor.cursorIsDrawn = true;
449 }
450
451 _nextCursorFlashTick = g_sci->getTickCount() + 30;
452 }
453
eraseCursor(TextEditor & editor)454 void GfxControls32::eraseCursor(TextEditor &editor) {
455 if (editor.cursorIsDrawn) {
456 _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
457 editor.cursorIsDrawn = false;
458 }
459
460 _nextCursorFlashTick = g_sci->getTickCount() + 30;
461 }
462
flashCursor(TextEditor & editor)463 void GfxControls32::flashCursor(TextEditor &editor) {
464 if (g_sci->getTickCount() > _nextCursorFlashTick) {
465 _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
466
467 editor.cursorIsDrawn = !editor.cursorIsDrawn;
468 _nextCursorFlashTick = g_sci->getTickCount() + 30;
469 }
470 }
471
472 #pragma mark -
473 #pragma mark Scrollable window control
474
ScrollWindow(SegManager * segMan,const Common::Rect & gameRect,const Common::Point & position,const reg_t plane,const uint8 defaultForeColor,const uint8 defaultBackColor,const GuiResourceId defaultFontId,const TextAlign defaultAlignment,const int16 defaultBorderColor,const uint16 maxNumEntries)475 ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) :
476 _segMan(segMan),
477 _gfxText32(segMan, g_sci->_gfxCache),
478 _maxNumEntries(maxNumEntries),
479 _firstVisibleChar(0),
480 _topVisibleLine(0),
481 _lastVisibleChar(0),
482 _bottomVisibleLine(0),
483 _numLines(0),
484 _numVisibleLines(0),
485 _plane(plane),
486 _foreColor(defaultForeColor),
487 _backColor(defaultBackColor),
488 _borderColor(defaultBorderColor),
489 _fontId(defaultFontId),
490 _alignment(defaultAlignment),
491 _visible(false),
492 _position(position),
493 _screenItem(nullptr),
494 _nextEntryId(1) {
495
496 _entries.reserve(maxNumEntries);
497
498 _gfxText32.setFont(_fontId);
499 _pointSize = _gfxText32._font->getHeight();
500
501 const uint16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
502 const uint16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
503
504 Common::Rect bitmapRect(gameRect);
505 mulinc(bitmapRect, Ratio(_gfxText32._xResolution, scriptWidth), Ratio(_gfxText32._yResolution, scriptHeight));
506
507 _textRect.left = 2;
508 _textRect.top = 2;
509 _textRect.right = bitmapRect.width() - 2;
510 _textRect.bottom = bitmapRect.height() - 2;
511
512 uint8 skipColor = 0;
513 while (skipColor == _foreColor || skipColor == _backColor) {
514 skipColor++;
515 }
516
517 assert(bitmapRect.width() > 0 && bitmapRect.height() > 0);
518 _bitmap = _gfxText32.createFontBitmap(bitmapRect.width(), bitmapRect.height(), _textRect, "", _foreColor, _backColor, skipColor, _fontId, _alignment, _borderColor, false, false, false);
519
520 debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap));
521 }
522
~ScrollWindow()523 ScrollWindow::~ScrollWindow() {
524 _segMan->freeBitmap(_bitmap);
525 // _screenItem will be deleted by GfxFrameout
526 }
527
where() const528 Ratio ScrollWindow::where() const {
529 return Ratio(_topVisibleLine, MAX(_numLines, 1));
530 }
531
show()532 void ScrollWindow::show() {
533 if (_visible) {
534 return;
535 }
536
537 if (_screenItem == nullptr) {
538 CelInfo32 celInfo;
539 celInfo.type = kCelTypeMem;
540 celInfo.bitmap = _bitmap;
541
542 _screenItem = new ScreenItem(_plane, celInfo, _position, ScaleInfo());
543 }
544
545 Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
546
547 if (plane == nullptr) {
548 error("[ScrollWindow::show]: Plane %04x:%04x not found", PRINT_REG(_plane));
549 }
550
551 plane->_screenItemList.add(_screenItem);
552
553 _visible = true;
554 }
555
hide()556 void ScrollWindow::hide() {
557 if (!_visible) {
558 return;
559 }
560
561 g_sci->_gfxFrameout->deleteScreenItem(*_screenItem, _plane);
562 _screenItem = nullptr;
563 g_sci->_gfxFrameout->frameOut(true);
564
565 _visible = false;
566 }
567
add(const Common::String & text,const GuiResourceId fontId,const int16 foreColor,const TextAlign alignment,const bool scrollTo)568 reg_t ScrollWindow::add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
569 if (_entries.size() == _maxNumEntries) {
570 ScrollWindowEntry removedEntry = _entries.remove_at(0);
571 _text.erase(0, removedEntry.text.size());
572 // `_firstVisibleChar` will be reset shortly if `scrollTo` is true, so
573 // there is no reason to update it
574 if (!scrollTo) {
575 _firstVisibleChar -= removedEntry.text.size();
576 }
577 }
578
579 _entries.push_back(ScrollWindowEntry());
580 ScrollWindowEntry &entry = _entries.back();
581
582 // In SSCI, the line ID was a memory handle for the string of this line. We
583 // use a numeric ID instead.
584 entry.id = make_reg(0, _nextEntryId++);
585
586 if (_nextEntryId > _maxNumEntries) {
587 _nextEntryId = 1;
588 }
589
590 // In SSCI, this was updated after _text was updated, which meant there was
591 // an extra unnecessary subtraction operation (subtracting `entry.text`
592 // size)
593 if (scrollTo) {
594 _firstVisibleChar = _text.size();
595 }
596
597 fillEntry(entry, text, fontId, foreColor, alignment);
598 _text += entry.text;
599
600 computeLineIndices();
601 update(true);
602
603 return entry.id;
604 }
605
fillEntry(ScrollWindowEntry & entry,const Common::String & text,const GuiResourceId fontId,const int16 foreColor,const TextAlign alignment)606 void ScrollWindow::fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment) {
607 entry.alignment = alignment;
608 entry.foreColor = foreColor;
609 entry.fontId = fontId;
610
611 Common::String formattedText;
612
613 // NB: There are inconsistencies here.
614 // If there is a multi-line entry with non-default properties, and it
615 // is only partially displayed, it may not be displayed right, since the
616 // property directives are only added to the first line.
617 // (Verified by trying this in SSCI SQ6 with a custom ScrollWindowAdd call.)
618 //
619 // The converse is also a potential issue (but unverified), where lines
620 // with properties -1 can inherit properties from the previously rendered
621 // line instead of the defaults.
622
623 // SSCI added "|s<lineIndex>|" here, but |s| is not a valid control code, so
624 // it just always ended up getting skipped by the text rendering code
625 if (entry.fontId != -1) {
626 formattedText += Common::String::format("|f%d|", entry.fontId);
627 }
628 if (entry.foreColor != -1) {
629 formattedText += Common::String::format("|c%d|", entry.foreColor);
630 }
631 if (entry.alignment != -1) {
632 formattedText += Common::String::format("|a%d|", entry.alignment);
633 }
634 formattedText += text;
635 entry.text = formattedText;
636 }
637
modify(const reg_t id,const Common::String & text,const GuiResourceId fontId,const int16 foreColor,const TextAlign alignment,const bool scrollTo)638 reg_t ScrollWindow::modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
639
640 EntriesList::iterator it = _entries.begin();
641 uint firstCharLocation = 0;
642 for ( ; it != _entries.end(); ++it) {
643 if (it->id == id) {
644 break;
645 }
646 firstCharLocation += it->text.size();
647 }
648
649 if (it == _entries.end()) {
650 return make_reg(0, 0);
651 }
652
653 ScrollWindowEntry &entry = *it;
654
655 uint oldTextLength = entry.text.size();
656
657 fillEntry(entry, text, fontId, foreColor, alignment);
658 _text.replace(firstCharLocation, oldTextLength, entry.text);
659
660 if (scrollTo) {
661 _firstVisibleChar = firstCharLocation;
662 }
663
664 computeLineIndices();
665 update(true);
666
667 return entry.id;
668 }
669
upArrow()670 void ScrollWindow::upArrow() {
671 if (_topVisibleLine == 0) {
672 return;
673 }
674
675 _topVisibleLine--;
676 _bottomVisibleLine--;
677
678 if (_bottomVisibleLine - _topVisibleLine + 1 < _numVisibleLines) {
679 _bottomVisibleLine = _numLines - 1;
680 }
681
682 _firstVisibleChar = _startsOfLines[_topVisibleLine];
683 _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
684
685 _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
686
687 Common::String lineText(_text.c_str() + _startsOfLines[_topVisibleLine], _text.c_str() + _startsOfLines[_topVisibleLine + 1] - 1);
688
689 debugC(3, kDebugLevelGraphics, "ScrollWindow::upArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
690
691 _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollUp);
692
693 if (_visible) {
694 assert(_screenItem);
695
696 _screenItem->update();
697 g_sci->_gfxFrameout->frameOut(true);
698 }
699 }
700
downArrow()701 void ScrollWindow::downArrow() {
702 if (_topVisibleLine + 1 >= _numLines) {
703 return;
704 }
705
706 _topVisibleLine++;
707 _bottomVisibleLine++;
708
709 if (_bottomVisibleLine + 1 >= _numLines) {
710 _bottomVisibleLine = _numLines - 1;
711 }
712
713 _firstVisibleChar = _startsOfLines[_topVisibleLine];
714 _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
715
716 _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
717
718 Common::String lineText;
719 if (_bottomVisibleLine - _topVisibleLine + 1 == _numVisibleLines) {
720 lineText = Common::String(_text.c_str() + _startsOfLines[_bottomVisibleLine], _text.c_str() + _startsOfLines[_bottomVisibleLine + 1] - 1);
721 } else {
722 // scroll in empty string
723 }
724
725 debugC(3, kDebugLevelGraphics, "ScrollWindow::downArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
726
727
728 _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollDown);
729
730 if (_visible) {
731 assert(_screenItem);
732
733 _screenItem->update();
734 g_sci->_gfxFrameout->frameOut(true);
735 }
736 }
737
go(const Ratio location)738 void ScrollWindow::go(const Ratio location) {
739 const int line = (location * _numLines).toInt();
740 if (line < 0 || line > _numLines) {
741 error("Index is Out of Range in ScrollWindow");
742 }
743
744 _firstVisibleChar = _startsOfLines[line];
745 update(true);
746
747 // HACK:
748 // It usually isn't possible to set _topVisibleLine >= _numLines, and so
749 // update() doesn't. However, in this case we should set _topVisibleLine
750 // past the end. This is clearly visible in Phantasmagoria when dragging
751 // the slider in the About dialog to the very end. The slider ends up lower
752 // than where it can be moved by scrolling down with the arrows.
753 if (location.isOne()) {
754 _topVisibleLine = _numLines;
755 }
756 }
757
home()758 void ScrollWindow::home() {
759 if (_firstVisibleChar == 0) {
760 return;
761 }
762
763 _firstVisibleChar = 0;
764 update(true);
765 }
766
end()767 void ScrollWindow::end() {
768 if (_bottomVisibleLine + 1 >= _numLines) {
769 return;
770 }
771
772 int line = _numLines - _numVisibleLines;
773 if (line < 0) {
774 line = 0;
775 }
776 _firstVisibleChar = _startsOfLines[line];
777 update(true);
778 }
779
pageUp()780 void ScrollWindow::pageUp() {
781 if (_topVisibleLine == 0) {
782 return;
783 }
784
785 _topVisibleLine -= _numVisibleLines;
786 if (_topVisibleLine < 0) {
787 _topVisibleLine = 0;
788 }
789
790 _firstVisibleChar = _startsOfLines[_topVisibleLine];
791 update(true);
792 }
793
pageDown()794 void ScrollWindow::pageDown() {
795 if (_topVisibleLine + 1 >= _numLines) {
796 return;
797 }
798
799 _topVisibleLine += _numVisibleLines;
800 if (_topVisibleLine + 1 >= _numLines) {
801 _topVisibleLine = _numLines - 1;
802 }
803
804 _firstVisibleChar = _startsOfLines[_topVisibleLine];
805 update(true);
806 }
807
computeLineIndices()808 void ScrollWindow::computeLineIndices() {
809 _gfxText32.setFont(_fontId);
810 // Unlike SSCI, foreColor and alignment are not set since these properties
811 // do not affect the width of lines
812
813 if (_gfxText32._font->getHeight() != _pointSize) {
814 error("Illegal font size font = %d pointSize = %d, should be %d.", _fontId, _gfxText32._font->getHeight(), _pointSize);
815 }
816
817 Common::Rect lineRect(0, 0, _textRect.width(), _pointSize + 3);
818
819 _startsOfLines.clear();
820
821 // SSCI had a 1000-line limit; we do not enforce any limit since we use
822 // dynamic containers
823 for (uint charIndex = 0; charIndex < _text.size(); ) {
824 _startsOfLines.push_back(charIndex);
825 charIndex += _gfxText32.getTextCount(_text, charIndex, lineRect, false);
826 }
827
828 _numLines = _startsOfLines.size();
829
830 _startsOfLines.push_back(_text.size());
831
832 _lastVisibleChar = _gfxText32.getTextCount(_text, 0, _fontId, _textRect, false) - 1;
833
834 _bottomVisibleLine = 0;
835 while (
836 _bottomVisibleLine < _numLines - 1 &&
837 _startsOfLines[_bottomVisibleLine + 1] < _lastVisibleChar
838 ) {
839 ++_bottomVisibleLine;
840 }
841
842 _numVisibleLines = _bottomVisibleLine + 1;
843 }
844
update(const bool doFrameOut)845 void ScrollWindow::update(const bool doFrameOut) {
846 _topVisibleLine = 0;
847 while (
848 _topVisibleLine < _numLines - 1 &&
849 _firstVisibleChar >= _startsOfLines[_topVisibleLine + 1]
850 ) {
851 ++_topVisibleLine;
852 }
853
854 _bottomVisibleLine = _topVisibleLine + _numVisibleLines - 1;
855 if (_bottomVisibleLine >= _numLines) {
856 _bottomVisibleLine = _numLines - 1;
857 }
858
859 _firstVisibleChar = _startsOfLines[_topVisibleLine];
860
861 if (_bottomVisibleLine >= 0) {
862 _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
863 } else {
864 _lastVisibleChar = -1;
865 }
866
867 _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
868
869 _gfxText32.erase(_textRect, false);
870 _gfxText32.drawTextBox(_visibleText);
871
872 if (_visible) {
873 assert(_screenItem);
874
875 _screenItem->update();
876 if (doFrameOut) {
877 g_sci->_gfxFrameout->frameOut(true);
878 }
879 }
880 }
881
makeScrollWindow(const Common::Rect & gameRect,const Common::Point & position,const reg_t planeObj,const uint8 defaultForeColor,const uint8 defaultBackColor,const GuiResourceId defaultFontId,const TextAlign defaultAlignment,const int16 defaultBorderColor,const uint16 maxNumEntries)882 reg_t GfxControls32::makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) {
883
884 ScrollWindow *scrollWindow = new ScrollWindow(_segMan, gameRect, position, planeObj, defaultForeColor, defaultBackColor, defaultFontId, defaultAlignment, defaultBorderColor, maxNumEntries);
885
886 const uint16 id = _nextScrollWindowId++;
887 _scrollWindows[id] = scrollWindow;
888 return make_reg(0, id);
889 }
890
getScrollWindow(const reg_t id)891 ScrollWindow *GfxControls32::getScrollWindow(const reg_t id) {
892 ScrollWindowMap::iterator it;
893 it = _scrollWindows.find(id.toUint16());
894 if (it == _scrollWindows.end())
895 error("Invalid ScrollWindow ID");
896
897 return it->_value;
898 }
899
destroyScrollWindow(const reg_t id)900 void GfxControls32::destroyScrollWindow(const reg_t id) {
901 ScrollWindow *scrollWindow = getScrollWindow(id);
902 scrollWindow->hide();
903 _scrollWindows.erase(id.getOffset());
904 delete scrollWindow;
905 }
906
907 #pragma mark -
908 #pragma mark Message box
909
showMessageBox(const Common::U32String & message,const Common::U32String & okLabel,const Common::U32String & altLabel,const int16 okValue,const int16 altValue)910 int16 GfxControls32::showMessageBox(const Common::U32String &message, const Common::U32String &okLabel, const Common::U32String &altLabel, const int16 okValue, const int16 altValue) {
911 GUI::MessageDialog dialog(message, okLabel, altLabel);
912 return (dialog.runModal() == GUI::kMessageOK) ? okValue : altValue;
913 }
914
kernelMessageBox(const Common::String & message,const Common::String & title,const uint16 style)915 reg_t GfxControls32::kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style) {
916 PauseToken pt;
917 if (g_engine) {
918 pt = g_engine->pauseEngine();
919 }
920
921 int16 result;
922
923 switch (style & 0xF) {
924 case kMessageBoxOK:
925 result = showMessageBox(message, _("OK"), Common::U32String(), 1, 1);
926 break;
927 case kMessageBoxYesNo:
928 result = showMessageBox(message, _("Yes"), _("No"), 6, 7);
929 break;
930 default:
931 error("Unsupported MessageBox style 0x%x", style & 0xF);
932 }
933
934 return make_reg(0, result);
935 }
936
937 } // End of namespace Sci
938