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