1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/util.h"
24 #include "common/stack.h"
25 #include "common/system.h"
26 #include "graphics/primitives.h"
27 
28 #include "sci/sci.h"
29 #include "sci/event.h"
30 #include "sci/engine/kernel.h"
31 #include "sci/engine/state.h"
32 #include "sci/engine/selector.h"
33 #include "sci/graphics/compare.h"
34 #include "sci/graphics/ports.h"
35 #include "sci/graphics/paint16.h"
36 #include "sci/graphics/font.h"
37 #include "sci/graphics/screen.h"
38 #include "sci/graphics/text16.h"
39 #include "sci/graphics/controls16.h"
40 
41 namespace Sci {
42 
GfxControls16(SegManager * segMan,GfxPorts * ports,GfxPaint16 * paint16,GfxText16 * text16,GfxScreen * screen)43 GfxControls16::GfxControls16(SegManager *segMan, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen)
44 	: _segMan(segMan), _ports(ports), _paint16(paint16), _text16(text16), _screen(screen) {
45 	_texteditBlinkTime = 0;
46 	_texteditCursorVisible = false;
47 }
48 
~GfxControls16()49 GfxControls16::~GfxControls16() {
50 }
51 
52 const char controlListUpArrow[2]	= { 0x18, 0 };
53 const char controlListDownArrow[2]	= { 0x19, 0 };
54 
drawListControl(Common::Rect rect,reg_t obj,int16 maxChars,int16 count,const Common::String * entries,GuiResourceId fontId,int16 upperPos,int16 cursorPos,bool isAlias)55 void GfxControls16::drawListControl(Common::Rect rect, reg_t obj, int16 maxChars, int16 count, const Common::String *entries, GuiResourceId fontId, int16 upperPos, int16 cursorPos, bool isAlias) {
56 	Common::Rect workerRect = rect;
57 	GuiResourceId oldFontId = _text16->GetFontId();
58 	int16 oldPenColor = _ports->_curPort->penClr;
59 	uint16 fontSize = 0;
60 	int16 i;
61 	int16 lastYpos;
62 
63 	// draw basic window
64 	_paint16->eraseRect(workerRect);
65 	workerRect.grow(1);
66 	_paint16->frameRect(workerRect);
67 
68 	// draw UP/DOWN arrows
69 	//  we draw UP arrow one pixel lower than sierra did, because it looks nicer. Also the DOWN arrow has one pixel
70 	//  line inbetween as well
71 	// They "fixed" this in SQ4 by having the arrow character start one pixel line later, we don't adjust there
72 	if (g_sci->getGameId() != GID_SQ4)
73 		workerRect.top++;
74 	_text16->Box(controlListUpArrow, false, workerRect, SCI_TEXT16_ALIGNMENT_CENTER, 0);
75 	workerRect.top = workerRect.bottom - 10;
76 	_text16->Box(controlListDownArrow, false, workerRect, SCI_TEXT16_ALIGNMENT_CENTER, 0);
77 
78 	// Draw inner lines
79 	workerRect.top = rect.top + 9;
80 	workerRect.bottom -= 10;
81 	_paint16->frameRect(workerRect);
82 	workerRect.grow(-1);
83 
84 	_text16->SetFont(fontId);
85 	fontSize = _ports->_curPort->fontHeight;
86 	_ports->penColor(_ports->_curPort->penClr); _ports->backColor(_ports->_curPort->backClr);
87 	workerRect.bottom = workerRect.top + fontSize;
88 	lastYpos = rect.bottom - fontSize;
89 
90 	// Write actual text
91 	for (i = upperPos; i < count; i++) {
92 		_paint16->eraseRect(workerRect);
93 		const Common::String &listEntry = entries[i];
94 		if (listEntry[0]) {
95 			_ports->moveTo(workerRect.left, workerRect.top);
96 			_text16->Draw(listEntry.c_str(), 0, MIN<int16>(maxChars, listEntry.size()), oldFontId, oldPenColor);
97 			if ((!isAlias) && (i == cursorPos)) {
98 				_paint16->invertRect(workerRect);
99 			}
100 		}
101 		workerRect.translate(0, fontSize);
102 		if (workerRect.bottom > lastYpos)
103 			break;
104 	}
105 
106 	_text16->SetFont(oldFontId);
107 }
108 
texteditCursorDraw(Common::Rect rect,const char * text,uint16 curPos)109 void GfxControls16::texteditCursorDraw(Common::Rect rect, const char *text, uint16 curPos) {
110 	int16 textWidth, i;
111 	if (!_texteditCursorVisible) {
112 		textWidth = 0;
113 		for (i = 0; i < curPos; i++) {
114 			textWidth += _text16->_font->getCharWidth((unsigned char)text[i]);
115 		}
116 		_texteditCursorRect.left = rect.left + textWidth;
117 		_texteditCursorRect.top = rect.top;
118 		_texteditCursorRect.bottom = _texteditCursorRect.top + _text16->_font->getHeight();
119 		_texteditCursorRect.right = _texteditCursorRect.left + (text[curPos] == 0 ? 1 : _text16->_font->getCharWidth((unsigned char)text[curPos]));
120 		_paint16->invertRect(_texteditCursorRect);
121 		_paint16->bitsShow(_texteditCursorRect);
122 		_texteditCursorVisible = true;
123 		texteditSetBlinkTime();
124 	}
125 }
126 
texteditCursorErase()127 void GfxControls16::texteditCursorErase() {
128 	if (_texteditCursorVisible) {
129 		_paint16->invertRect(_texteditCursorRect);
130 		_paint16->bitsShow(_texteditCursorRect);
131 		_texteditCursorVisible = false;
132 	}
133 	texteditSetBlinkTime();
134 }
135 
texteditSetBlinkTime()136 void GfxControls16::texteditSetBlinkTime() {
137 	_texteditBlinkTime = g_system->getMillis() + (30 * 1000 / 60);
138 }
139 
kernelTexteditChange(reg_t controlObject,reg_t eventObject)140 void GfxControls16::kernelTexteditChange(reg_t controlObject, reg_t eventObject) {
141 	uint16 cursorPos = readSelectorValue(_segMan, controlObject, SELECTOR(cursor));
142 	uint16 maxChars = readSelectorValue(_segMan, controlObject, SELECTOR(max));
143 	reg_t textReference = readSelector(_segMan, controlObject, SELECTOR(text));
144 	Common::String text;
145 	uint16 textSize, eventType, eventKey = 0, modifiers = 0;
146 	bool textChanged = false;
147 	bool textAddChar = false;
148 	Common::Rect rect;
149 
150 	if (textReference.isNull())
151 		error("kEditControl called on object that doesn't have a text reference");
152 	text = _segMan->getString(textReference);
153 
154 	uint16 oldCursorPos = cursorPos;
155 
156 	if (!eventObject.isNull()) {
157 		textSize = text.size();
158 		eventType = readSelectorValue(_segMan, eventObject, SELECTOR(type));
159 
160 		switch (eventType) {
161 		case kSciEventMousePress:
162 			// TODO: Implement mouse support for cursor change
163 			break;
164 		case kSciEventKeyDown:
165 			eventKey = readSelectorValue(_segMan, eventObject, SELECTOR(message));
166 			modifiers = readSelectorValue(_segMan, eventObject, SELECTOR(modifiers));
167 			switch (eventKey) {
168 			case kSciKeyBackspace:
169 				if (cursorPos > 0) {
170 					cursorPos--; text.deleteChar(cursorPos);
171 					textChanged = true;
172 				}
173 				break;
174 			case kSciKeyDelete:
175 				if (cursorPos < textSize) {
176 					text.deleteChar(cursorPos);
177 					textChanged = true;
178 				}
179 				break;
180 			case kSciKeyHome:
181 				cursorPos = 0; textChanged = true;
182 				break;
183 			case kSciKeyEnd:
184 				cursorPos = textSize; textChanged = true;
185 				break;
186 			case kSciKeyLeft:
187 				if (cursorPos > 0) {
188 					cursorPos--; textChanged = true;
189 				}
190 				break;
191 			case kSciKeyRight:
192 				if (cursorPos + 1 <= textSize) {
193 					cursorPos++; textChanged = true;
194 				}
195 				break;
196 			case kSciKeyEtx:
197 				if (modifiers & kSciKeyModCtrl) {
198 					// Control-C erases the whole line
199 					cursorPos = 0; text.clear();
200 					textChanged = true;
201 				}
202 				break;
203 			default:
204 				if ((modifiers & kSciKeyModCtrl) && eventKey == 99) {
205 					// Control-C in earlier SCI games (SCI0 - SCI1 middle)
206 					// Control-C erases the whole line
207 					cursorPos = 0; text.clear();
208 					textChanged = true;
209 				} else if (eventKey > 31 && eventKey < 256 && textSize < maxChars) {
210 					// insert pressed character
211 					textAddChar = true;
212 					textChanged = true;
213 				}
214 				break;
215 			}
216 			break;
217 		}
218 	}
219 
220 	if (g_sci->getVocabulary() && !textChanged && oldCursorPos != cursorPos) {
221 		assert(!textAddChar);
222 		textChanged = g_sci->getVocabulary()->checkAltInput(text, cursorPos);
223 	}
224 
225 	if (textChanged) {
226 		GuiResourceId oldFontId = _text16->GetFontId();
227 		GuiResourceId fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font));
228 		rect = g_sci->_gfxCompare->getNSRect(controlObject);
229 
230 		_text16->SetFont(fontId);
231 		if (textAddChar) {
232 
233 			const char *textPtr = text.c_str();
234 
235 			// We check if we are really able to add the new char
236 			uint16 textWidth = 0;
237 			while (*textPtr)
238 				textWidth += _text16->_font->getCharWidth((byte)*textPtr++);
239 			textWidth += _text16->_font->getCharWidth(eventKey);
240 
241 			// Does it fit?
242 			if (textWidth >= rect.width()) {
243 				_text16->SetFont(oldFontId);
244 				return;
245 			}
246 
247 			text.insertChar(eventKey, cursorPos++);
248 
249 			// Note: the following checkAltInput call might make the text
250 			// too wide to fit, but SSCI fails to check that too.
251 		}
252 		if (g_sci->getVocabulary())
253 			g_sci->getVocabulary()->checkAltInput(text, cursorPos);
254 		texteditCursorErase();
255 		_paint16->eraseRect(rect);
256 		_text16->Box(text.c_str(), false, rect, SCI_TEXT16_ALIGNMENT_LEFT, -1);
257 		_paint16->bitsShow(rect);
258 		texteditCursorDraw(rect, text.c_str(), cursorPos);
259 		_text16->SetFont(oldFontId);
260 		// Write back string
261 		_segMan->strcpy(textReference, text.c_str());
262 	} else {
263 		if (g_system->getMillis() >= _texteditBlinkTime) {
264 			_paint16->invertRect(_texteditCursorRect);
265 			_paint16->bitsShow(_texteditCursorRect);
266 			_texteditCursorVisible = !_texteditCursorVisible;
267 			texteditSetBlinkTime();
268 		}
269 	}
270 
271 	writeSelectorValue(_segMan, controlObject, SELECTOR(cursor), cursorPos);
272 }
273 
getPicNotValid()274 int GfxControls16::getPicNotValid() {
275 	if (getSciVersion() >= SCI_VERSION_1_1)
276 		return _screen->_picNotValidSci11;
277 	return _screen->_picNotValid;
278 }
279 
kernelDrawButton(Common::Rect rect,reg_t obj,const char * text,uint16 languageSplitter,int16 fontId,int16 style,bool hilite)280 void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 style, bool hilite) {
281 	int16 sci0EarlyPen = 0, sci0EarlyBack = 0;
282 	if (!hilite) {
283 		if (getSciVersion() == SCI_VERSION_0_EARLY) {
284 			// SCI0early actually used hardcoded green/black buttons instead of using the port colors
285 			sci0EarlyPen = _ports->_curPort->penClr;
286 			sci0EarlyBack = _ports->_curPort->backClr;
287 			_ports->penColor(0);
288 			_ports->backColor(2);
289 		}
290 		rect.grow(1);
291 		_paint16->eraseRect(rect);
292 		_paint16->frameRect(rect);
293 		rect.grow(-2);
294 		_ports->textGreyedOutput(!(style & SCI_CONTROLS_STYLE_ENABLED));
295 		_text16->Box(text, languageSplitter, false, rect, SCI_TEXT16_ALIGNMENT_CENTER, fontId);
296 		_ports->textGreyedOutput(false);
297 		rect.grow(1);
298 		if (style & SCI_CONTROLS_STYLE_SELECTED)
299 			_paint16->frameRect(rect);
300 		if (!getPicNotValid()) {
301 			rect.grow(1);
302 			_paint16->bitsShow(rect);
303 		}
304 		if (getSciVersion() == SCI_VERSION_0_EARLY) {
305 			_ports->penColor(sci0EarlyPen);
306 			_ports->backColor(sci0EarlyBack);
307 		}
308 	} else {
309 		// SCI0early used xor to invert button rectangles resulting in pink/white buttons
310 		if (getSciVersion() == SCI_VERSION_0_EARLY)
311 			_paint16->invertRectViaXOR(rect);
312 		else
313 			_paint16->invertRect(rect);
314 		_paint16->bitsShow(rect);
315 	}
316 }
317 
kernelDrawText(Common::Rect rect,reg_t obj,const char * text,uint16 languageSplitter,int16 fontId,TextAlignment alignment,int16 style,bool hilite)318 void GfxControls16::kernelDrawText(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, TextAlignment alignment, int16 style, bool hilite) {
319 	if (!hilite) {
320 		rect.grow(1);
321 		_paint16->eraseRect(rect);
322 		rect.grow(-1);
323 		_text16->Box(text, languageSplitter, false, rect, alignment, fontId);
324 		if (style & SCI_CONTROLS_STYLE_SELECTED) {
325 			_paint16->frameRect(rect);
326 		}
327 		if (!getPicNotValid())
328 			_paint16->bitsShow(rect);
329 	} else {
330 		_paint16->invertRect(rect);
331 		_paint16->bitsShow(rect);
332 	}
333 }
334 
kernelDrawTextEdit(Common::Rect rect,reg_t obj,const char * text,uint16 languageSplitter,int16 fontId,int16 mode,int16 style,int16 cursorPos,int16 maxChars,bool hilite)335 void GfxControls16::kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite) {
336 	Common::Rect textRect = rect;
337 	uint16 oldFontId = _text16->GetFontId();
338 
339 	rect.grow(1);
340 	_texteditCursorVisible = false;
341 	texteditCursorErase();
342 	_paint16->eraseRect(rect);
343 	_text16->Box(text, languageSplitter, false, textRect, SCI_TEXT16_ALIGNMENT_LEFT, fontId);
344 	_paint16->frameRect(rect);
345 	if (style & SCI_CONTROLS_STYLE_SELECTED) {
346 		_text16->SetFont(fontId);
347 		rect.grow(-1);
348 		texteditCursorDraw(rect, text, cursorPos);
349 		_text16->SetFont(oldFontId);
350 		rect.grow(1);
351 	}
352 	if (!getPicNotValid())
353 		_paint16->bitsShow(rect);
354 }
355 
kernelDrawIcon(Common::Rect rect,reg_t obj,GuiResourceId viewId,int16 loopNo,int16 celNo,int16 priority,int16 style,bool hilite)356 void GfxControls16::kernelDrawIcon(Common::Rect rect, reg_t obj, GuiResourceId viewId, int16 loopNo, int16 celNo, int16 priority, int16 style, bool hilite) {
357 	if (!hilite) {
358 		_paint16->drawCelAndShow(viewId, loopNo, celNo, rect.left, rect.top, priority, 0);
359 		if (style & 0x20) {
360 			_paint16->frameRect(rect);
361 		}
362 		if (!getPicNotValid())
363 			_paint16->bitsShow(rect);
364 	} else {
365 		_paint16->invertRect(rect);
366 		_paint16->bitsShow(rect);
367 	}
368 }
369 
kernelDrawList(Common::Rect rect,reg_t obj,int16 maxChars,int16 count,const Common::String * entries,GuiResourceId fontId,int16 style,int16 upperPos,int16 cursorPos,bool isAlias,bool hilite)370 void GfxControls16::kernelDrawList(Common::Rect rect, reg_t obj, int16 maxChars, int16 count, const Common::String *entries, GuiResourceId fontId, int16 style, int16 upperPos, int16 cursorPos, bool isAlias, bool hilite) {
371 	if (!hilite) {
372 		drawListControl(rect, obj, maxChars, count, entries, fontId, upperPos, cursorPos, isAlias);
373 		rect.grow(1);
374 		if (isAlias && (style & SCI_CONTROLS_STYLE_SELECTED)) {
375 			_paint16->frameRect(rect);
376 		}
377 		if (!getPicNotValid())
378 			_paint16->bitsShow(rect);
379 	}
380 }
381 
382 } // End of namespace Sci
383