1 /***************************************************************************
2  *      Mechanized Assault and Exploration Reloaded Projectfile            *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19 
20 #include "ui/graphical/widget.h"
21 
22 #include "ui/graphical/application.h"
23 #include "settings.h"
24 #include "video.h"
25 #include "utility/drawing.h"
26 #include "utility/color.h"
27 
28 /*static*/ bool cWidget::drawDebugFrames = false;
29 /*static*/ cSignal<void ()> cWidget::drawDebugFramesChanged;
30 
31 //------------------------------------------------------------------------------
toggleDrawDebugFrames()32 /*static*/ void cWidget::toggleDrawDebugFrames()
33 {
34 	drawDebugFrames = !drawDebugFrames;
35 	drawDebugFramesChanged();
36 }
37 
38 //------------------------------------------------------------------------------
cWidget()39 cWidget::cWidget() :
40 	parent (nullptr),
41 	enabled (true),
42 	hidden (false),
43 	area (cPosition (0, 0), cPosition (0, 0))
44 {
45 	createFrameSurface();
46 
47 	signalConnectionManager.connect (drawDebugFramesChanged, [this]() { createFrameSurface(); });
48 }
49 
50 //------------------------------------------------------------------------------
cWidget(const cPosition & position)51 cWidget::cWidget (const cPosition& position) :
52 	parent (nullptr),
53 	enabled (true),
54 	hidden (false),
55 	area (position, position)
56 {
57 	createFrameSurface();
58 
59 	signalConnectionManager.connect (drawDebugFramesChanged, [this]() { createFrameSurface(); });
60 }
61 
62 //------------------------------------------------------------------------------
cWidget(const cBox<cPosition> & area_)63 cWidget::cWidget (const cBox<cPosition>& area_) :
64 	parent (nullptr),
65 	enabled (true),
66 	hidden (false),
67 	area (area_)
68 {
69 	createFrameSurface();
70 
71 	signalConnectionManager.connect (drawDebugFramesChanged, [this]() { createFrameSurface(); });
72 }
73 
74 //------------------------------------------------------------------------------
~cWidget()75 cWidget::~cWidget()
76 {
77 	auto application = getActiveApplication();
78 
79 	if (application)
80 	{
81 		// release the mouse or key focus of this widget or any of its children.
82 		// We have to release the focus of the children as well because after the parent is deleted
83 		// the children have no possibility to access the application anymore
84 		releaseFocusRecursive (*application);
85 	}
86 
87 	// just to make sure the children will not access the parent during their clean up.
88 	for (auto& child : children)
89 	{
90 		child->setParent (nullptr);
91 	}
92 }
93 
94 //------------------------------------------------------------------------------
getParent() const95 cWidget* cWidget::getParent() const
96 {
97 	return parent;
98 }
99 
100 //------------------------------------------------------------------------------
isEnabled() const101 bool cWidget::isEnabled() const
102 {
103 	return enabled;
104 }
105 
106 //------------------------------------------------------------------------------
disable()107 void cWidget::disable()
108 {
109 	enabled = false;
110 }
111 
112 //------------------------------------------------------------------------------
enable()113 void cWidget::enable()
114 {
115 	enabled = true;
116 }
117 
118 //------------------------------------------------------------------------------
isHidden() const119 bool cWidget::isHidden() const
120 {
121 	return hidden;
122 }
123 
124 //------------------------------------------------------------------------------
hide()125 void cWidget::hide()
126 {
127 	hidden = true;
128 }
129 
130 //------------------------------------------------------------------------------
show()131 void cWidget::show()
132 {
133 	hidden = false;
134 }
135 
136 //------------------------------------------------------------------------------
getPosition() const137 const cPosition& cWidget::getPosition() const
138 {
139 	return area.getMinCorner();
140 }
141 
142 //------------------------------------------------------------------------------
getEndPosition() const143 const cPosition& cWidget::getEndPosition() const
144 {
145 	return area.getMaxCorner();
146 }
147 
148 //------------------------------------------------------------------------------
moveTo(const cPosition & newPosition)149 void cWidget::moveTo (const cPosition& newPosition)
150 {
151 	auto offset = newPosition - area.getMinCorner();
152 	move (offset);
153 }
154 
155 //------------------------------------------------------------------------------
move(const cPosition & offset)156 void cWidget::move (const cPosition& offset)
157 {
158 	area.getMinCorner() += offset;
159 	area.getMaxCorner() += offset;
160 	handleMoved (offset);
161 }
162 
163 //------------------------------------------------------------------------------
getSize() const164 cPosition cWidget::getSize() const
165 {
166 	return area.getSize();
167 }
168 
169 //------------------------------------------------------------------------------
resize(const cPosition & newSize)170 void cWidget::resize (const cPosition& newSize)
171 {
172 	const auto oldSize = getSize();
173 
174 	if (oldSize != newSize)
175 	{
176 		area.resize (newSize);
177 		handleResized (oldSize);
178 	}
179 }
180 
181 //------------------------------------------------------------------------------
fitToChildren()182 void cWidget::fitToChildren()
183 {
184 	if (children.empty())
185 	{
186 		resize (cPosition (0, 0));
187 	}
188 	else
189 	{
190 		cBox<cPosition> newArea (children[0]->getPosition(), children[0]->getPosition());
191 
192 		for (size_t i = 0; i < children.size(); ++i)
193 		{
194 			newArea.add (children[i]->getArea());
195 		}
196 
197 		setArea (newArea);
198 	}
199 }
200 
201 //------------------------------------------------------------------------------
getArea() const202 const cBox<cPosition>& cWidget::getArea() const
203 {
204 	return area;
205 }
206 
207 //------------------------------------------------------------------------------
setArea(const cBox<cPosition> & area_)208 void cWidget::setArea (const cBox<cPosition>& area_)
209 {
210 	const cPosition newSize = area_.getSize();
211 	const cPosition offset = area_.getMinCorner() - getArea().getMinCorner();
212 
213 	move (offset);
214 	resize (newSize);
215 }
216 
217 //------------------------------------------------------------------------------
addShortcut(std::unique_ptr<cShortcut> shortcut)218 cShortcut* cWidget::addShortcut (std::unique_ptr<cShortcut> shortcut)
219 {
220 	if(shortcut == nullptr) return nullptr;
221 
222 	shortcuts.push_back (std::move (shortcut));
223 	return shortcuts.back().get();
224 }
225 
226 //------------------------------------------------------------------------------
getShortcuts() const227 const std::vector<std::unique_ptr<cShortcut>>& cWidget::getShortcuts() const
228 {
229 	return shortcuts;
230 }
231 
232 //------------------------------------------------------------------------------
getChildAt(const cPosition & position) const233 cWidget* cWidget::getChildAt (const cPosition& position) const
234 {
235 	// reverse order because the last child is the one that will be drawn last and therefor
236 	// is visually the one above all the others.
237 	// We want to find this one first, because we will abort on the first child that
238 	// intersects the point, regardless of whether there are other overlapping children.
239 	for (auto i = children.rbegin(); i != children.rend(); ++i)
240 	{
241 		auto child = i->get();
242 
243 		if (!child->isEnabled()) continue;
244 
245 		if (child->isAt (position))
246 		{
247 			auto childChild = child->getChildAt (position);
248 			return childChild ? childChild : child;
249 		}
250 	}
251 	return nullptr;
252 }
253 
254 //------------------------------------------------------------------------------
isAt(const cPosition & position) const255 bool cWidget::isAt (const cPosition& position) const
256 {
257 	return !isHidden() && area.withinOrTouches (position);
258 }
259 
260 //------------------------------------------------------------------------------
draw(SDL_Surface & destination,const cBox<cPosition> & clipRect)261 void cWidget::draw (SDL_Surface& destination, const cBox<cPosition>& clipRect)
262 {
263 	if (isHidden()) return;
264 
265 	if (frameSurface && getArea().intersects (clipRect))
266 	{
267 		auto clipedArea = getArea().intersection (clipRect);
268 
269 		SDL_Rect position = clipedArea.toSdlRect();
270 
271 		clipedArea.getMinCorner() -= getPosition();
272 		clipedArea.getMaxCorner() -= getPosition();
273 
274 		SDL_Rect source = clipedArea.toSdlRect();
275 
276 		SDL_BlitSurface (frameSurface.get(), &source, &destination, &position);
277 	}
278 
279 	for (auto i = children.begin(); i != children.end(); ++i)
280 	{
281 		auto& child = *i->get();
282 
283 		if (child.isHidden()) continue;
284 
285 		if (child.getArea().intersects (clipRect))
286 		{
287 			child.draw (destination, child.getArea().intersection (clipRect));
288 		}
289 	}
290 }
291 
292 //------------------------------------------------------------------------------
handleMouseMoved(cApplication & application,cMouse & mouse,const cPosition & offset)293 bool cWidget::handleMouseMoved (cApplication& application, cMouse& mouse, const cPosition& offset)
294 {
295 	return false;
296 }
297 
298 //------------------------------------------------------------------------------
handleMousePressed(cApplication & application,cMouse & mouse,eMouseButtonType button)299 bool cWidget::handleMousePressed (cApplication& application, cMouse& mouse, eMouseButtonType button)
300 {
301 	return false;
302 }
303 
304 //------------------------------------------------------------------------------
handleMouseReleased(cApplication & application,cMouse & mouse,eMouseButtonType button)305 bool cWidget::handleMouseReleased (cApplication& application, cMouse& mouse, eMouseButtonType button)
306 {
307 	return false;
308 }
309 
310 //------------------------------------------------------------------------------
handleMouseWheelMoved(cApplication & application,cMouse & mouse,const cPosition & amount)311 bool cWidget::handleMouseWheelMoved (cApplication& application, cMouse& mouse, const cPosition& amount)
312 {
313 	return false;
314 }
315 
316 //------------------------------------------------------------------------------
handleKeyPressed(cApplication & application,cKeyboard & keyboard,SDL_Keycode key)317 bool cWidget::handleKeyPressed (cApplication& application, cKeyboard& keyboard, SDL_Keycode key)
318 {
319 	for (auto i = children.begin(); i != children.end(); ++i)
320 	{
321 		auto& child = *i->get();
322 		if (child.handleKeyPressed (application, keyboard, key))
323 		{
324 			return true;
325 		}
326 	}
327 	return false;
328 }
329 
330 //------------------------------------------------------------------------------
handleKeyReleased(cApplication & application,cKeyboard & keyboard,SDL_Keycode key)331 bool cWidget::handleKeyReleased (cApplication& application, cKeyboard& keyboard, SDL_Keycode key)
332 {
333 	for (auto i = children.begin(); i != children.end(); ++i)
334 	{
335 		auto& child = *i->get();
336 		if (child.handleKeyReleased (application, keyboard, key))
337 		{
338 			return true;
339 		}
340 	}
341 	return false;
342 }
343 
344 //------------------------------------------------------------------------------
handleTextEntered(cApplication & application,cKeyboard & keyboard,const char * text)345 void cWidget::handleTextEntered (cApplication& application, cKeyboard& keyboard, const char* text)
346 {}
347 
348 ////------------------------------------------------------------------------------
349 //void cWidget::handleHoveredOn (cApplication& application, cMouse& mouse)
350 //{}
351 //
352 ////------------------------------------------------------------------------------
353 //void  cWidget::handleHoveredAway (cApplication& application, cMouse& mouse)
354 //{}
355 
356 //------------------------------------------------------------------------------
handleGetKeyFocus(cApplication & application)357 bool cWidget::handleGetKeyFocus (cApplication& application)
358 {
359 	return false;
360 }
361 
362 //------------------------------------------------------------------------------
handleLooseKeyFocus(cApplication & application)363 void cWidget::handleLooseKeyFocus (cApplication& application)
364 {}
365 
366 //------------------------------------------------------------------------------
handleGetMouseFocus(cApplication & application)367 void cWidget::handleGetMouseFocus (cApplication& application)
368 {}
369 
370 //------------------------------------------------------------------------------
handleLooseMouseFocus(cApplication & application)371 void cWidget::handleLooseMouseFocus (cApplication& application)
372 {}
373 
374 //------------------------------------------------------------------------------
handleMoved(const cPosition & offset)375 void cWidget::handleMoved (const cPosition& offset)
376 {
377 	for (auto i = children.begin(); i != children.end(); ++i)
378 	{
379 		(*i)->move (offset);
380 	}
381 }
382 
383 //------------------------------------------------------------------------------
handleResized(const cPosition &)384 void cWidget::handleResized (const cPosition&)
385 {
386 	createFrameSurface();
387 }
388 
389 //------------------------------------------------------------------------------
setParent(cWidget * parent_)390 void cWidget::setParent (cWidget* parent_)
391 {
392 	parent = parent_;
393 }
394 
395 //------------------------------------------------------------------------------
removeChildren()396 void cWidget::removeChildren()
397 {
398 	children.clear();
399 }
400 
401 //------------------------------------------------------------------------------
hasChildren() const402 bool cWidget::hasChildren() const
403 {
404 	return !children.empty();
405 }
406 
407 //------------------------------------------------------------------------------
getActiveMouse() const408 cMouse* cWidget::getActiveMouse() const
409 {
410 	return parent ? parent->getActiveMouse() : nullptr;
411 }
412 
413 //------------------------------------------------------------------------------
getActiveKeyboard() const414 cKeyboard* cWidget::getActiveKeyboard() const
415 {
416 	return parent ? parent->getActiveKeyboard() : nullptr;
417 }
418 
419 //------------------------------------------------------------------------------
getActiveApplication() const420 cApplication* cWidget::getActiveApplication() const
421 {
422 	return parent ? parent->getActiveApplication() : nullptr;
423 }
424 
425 //------------------------------------------------------------------------------
createFrameSurface()426 void cWidget::createFrameSurface()
427 {
428 	if (drawDebugFrames)
429 	{
430 		const auto size = getSize();
431 
432 		if (size.x() == 0 || size.y() == 0) return;
433 
434 		frameSurface = AutoSurface (SDL_CreateRGBSurface (0, size.x(), size.y(), Video.getColDepth(), 0, 0, 0, 0));
435 		if (!frameSurface) return; // can happen when for some reason the size is invalid (e.g. negative)
436 		SDL_SetColorKey (frameSurface.get(), SDL_TRUE, 0xFF00FF);
437 		SDL_FillRect (frameSurface.get(), nullptr, 0xFF00FF);
438 
439 		drawRectangle (*frameSurface, cBox<cPosition> (cPosition (0, 0), size), cRgbColor::red());
440 	}
441 	else
442 	{
443 		frameSurface = nullptr;
444 	}
445 }
446 
447 //------------------------------------------------------------------------------
hitShortcuts(const cKeySequence & keySequence)448 bool cWidget::hitShortcuts (const cKeySequence& keySequence)
449 {
450 	// TODO: remove code duplication with cApplication::hitShortcuts
451 
452 	bool anyMatch = false;
453 	for (const auto& shortcut : shortcuts)
454 	{
455 		if (!shortcut->isActive()) continue;
456 
457 		const auto& shortcutSequence = shortcut->getKeySequence();
458 
459 		if (shortcutSequence.length() > keySequence.length()) continue;
460 
461 		bool match = true;
462 		for (size_t j = 1; j <= shortcutSequence.length(); ++j)
463 		{
464 			if (keySequence[keySequence.length() - j] != shortcutSequence[shortcutSequence.length() - j])
465 			{
466 				match = false;
467 				break;
468 			}
469 		}
470 
471 		if (match)
472 		{
473 			shortcut->triggered();
474 			anyMatch = true;
475 		}
476 	}
477 
478 	for (const auto& child : children)
479 	{
480 		anyMatch = child->hitShortcuts (keySequence) || anyMatch;
481 	}
482 	return anyMatch;
483 }
484 
485 //------------------------------------------------------------------------------
releaseFocusRecursive(cApplication & application)486 void cWidget::releaseFocusRecursive (cApplication& application)
487 {
488 	if (application.hasKeyFocus (*this)) application.releaseKeyFocus (*this);
489 	if (application.hasMouseFocus (*this)) application.releaseMouseFocus (*this);
490 
491 	if (application.hasKeyFocus() || application.hasMouseFocus())
492 	{
493 		for (auto& child : children)
494 		{
495 			child->releaseFocusRecursive (application);
496 		}
497 	}
498 }
499