1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2013, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16 
17 /* Player and editor viewports in console */
18 
19 #include "C4Include.h"
20 #include "C4ForbidLibraryCompilation.h"
21 #include "script/C4Value.h"
22 #include "game/C4Viewport.h"
23 #include "object/C4Object.h"
24 #include "gui/C4MouseControl.h"
25 #include "landscape/C4Landscape.h"
26 #include "object/C4GameObjects.h"
27 #include "player/C4PlayerList.h"
28 #ifndef USE_CONSOLE
29 #include <GL/glew.h>
30 #endif
31 // See C4ConsoleQt.cpp on the include order
32 #include "editor/C4ConsoleQtViewport.h"
33 #include "editor/C4ConsoleQtState.h"
34 #include "editor/C4Console.h"
35 #include "editor/C4ConsoleQtShapes.h"
36 #include "editor/C4ViewportWindow.h"
37 #include "editor/C4Console.h"
38 
39 /* Console viewports */
40 
C4ConsoleQtViewportView(class C4ConsoleQtViewportScrollArea * scrollarea)41 C4ConsoleQtViewportView::C4ConsoleQtViewportView(class C4ConsoleQtViewportScrollArea *scrollarea)
42 	: QOpenGLWidget(scrollarea->dock), dock(scrollarea->dock), cvp(scrollarea->cvp)
43 {
44 	setAttribute(Qt::WA_ShowWithoutActivating, true);
45 	setFocusPolicy(Qt::WheelFocus);
46 	setMouseTracking(true);
47 	setContextMenuPolicy(Qt::CustomContextMenu);
48 	// Register for viewport
49 	C4ViewportWindow *window = dock->cvp;
50 	window->glwidget = this;
51 	// Enable context menu
52 	connect(this, &QWidget::customContextMenuRequested, this, &C4ConsoleQtViewportView::ShowContextMenu);
53 }
54 
IsPlayViewport() const55 bool C4ConsoleQtViewportView::IsPlayViewport() const
56 {
57 	return (cvp && ::MouseControl.IsViewport(cvp)
58 		&& (::Console.EditCursor.GetMode() == C4CNS_ModePlay));
59 }
60 
61 // On high-DPI screens, Qt's pixels are not equal to device pixels anymore. However, viewports
62 // still work with device pixels, so we have to adjust coordinates from Qt events.
GetDevicePixelRatio()63 qreal C4ConsoleQtViewportView::GetDevicePixelRatio()
64 {
65 	// Find the screen the viewport is on to get its pixel ratio.
66 	auto desktop = QApplication::desktop();
67 	auto screenNumber = desktop->screenNumber(this);
68 	// This can happen while moving to a different screen.
69 	if (screenNumber == -1)
70 		return 1;
71 	auto screen = QApplication::screens()[screenNumber];
72 	return screen->devicePixelRatio();
73 }
74 
AddSelectObjectContextEntry(C4Object * obj,QMenu * menu)75 void C4ConsoleQtViewportView::AddSelectObjectContextEntry(C4Object *obj, QMenu *menu)
76 {
77 	// Add select object item for object and for all contents
78 	if (!obj || !obj->Status) return;
79 	int32_t object_number = obj->Number;
80 	QAction *select_object_action = new QAction(QString("%1 #%2 (%3/%4)").arg(obj->GetName()).arg(object_number).arg(obj->GetX()).arg(obj->GetY()), menu);
81 	connect(select_object_action, &QAction::triggered, menu, [object_number]() {
82 		bool add = !!(QGuiApplication::keyboardModifiers() & Qt::ShiftModifier);
83 		C4Object *obj = ::Objects.SafeObjectPointer(object_number);
84 		if (obj) ::Console.EditCursor.DoContextObjsel(obj, !add);
85 	});
86 	menu->addAction(select_object_action);
87 	if (obj->Contents.ObjectCount())
88 	{
89 		QMenu *submenu = menu->addMenu(FormatString(LoadResStr("IDS_CNS_SELECTCONTENTS"), obj->GetName()).getData());
90 		for (C4Object *cobj : obj->Contents)
91 		{
92 			AddSelectObjectContextEntry(cobj, submenu);
93 		}
94 	}
95 }
96 
ShowContextMenu(const QPoint & pos)97 void C4ConsoleQtViewportView::ShowContextMenu(const QPoint &pos)
98 {
99 	// Coordinates are in viewport (not adjusted by parent scroll window)
100 	if (!IsPlayViewport())
101 	{
102 		// Show context menu in editor viewport
103 		QMenu *menu = new QMenu(this);
104 		// Show current object(s) as unselectable item
105 		auto &ui = dock->main_window->GetConsoleState()->ui;
106 		menu->addSection(ui.selectionInfoLabel->text());
107 		// Object actions. Always shown but grayed out if no object is selected.
108 		bool has_object = false;
109 		int32_t contents_count = 0;
110 		for (const C4Value &sel : ::Console.EditCursor.GetSelection())
111 		{
112 			C4Object *obj = sel.getObj();
113 			if (obj)
114 			{
115 				has_object = true;
116 				contents_count += obj->Contents.ObjectCount();
117 			}
118 		}
119 		for (QAction *act : { ui.actionDeleteObject, ui.actionDuplicateObject, ui.actionEjectContents })
120 		{
121 			act->setEnabled(has_object);
122 			menu->addAction(act);
123 		}
124 		if (!contents_count)
125 		{
126 			ui.actionEjectContents->setEnabled(false);
127 		}
128 		ui.actionEjectContents->setText(QString("%1 (%2)").arg(LoadResStr("IDS_MNU_CONTENTS")).arg((int)contents_count));
129 		// Object selection section for overlapping objects
130 		auto pr = GetDevicePixelRatio();
131 		int32_t x = cvp->WindowToGameX(pr * pos.x()),
132 		        y = cvp->WindowToGameY(pr * pos.y());
133 		auto pFOl = new C4FindObjectAtPoint(x, y); // freed by ~C4FindObjectAnd
134 		auto pFOc = new C4FindObjectContainer(nullptr);  // freed by ~C4FindObjectAnd
135 		C4FindObject *pFO_conds[] = { pFOl , pFOc };
136 		C4FindObjectAnd pFO(2, pFO_conds, false);
137 		std::unique_ptr<C4ValueArray> atcursor(pFO.FindMany(::Objects, ::Objects.Sectors)); // needs freeing (single object ptr)
138 		int itemcount = atcursor->GetSize();
139 		if (itemcount)
140 		{
141 			menu->addSection(LoadResStr("IDS_CNS_SELECTNEARBYOBJECTS"));
142 			for (int32_t i = 0; i < itemcount; ++i)
143 			{
144 				AddSelectObjectContextEntry(atcursor->GetItem(i).getObj(), menu);
145 			}
146 		}
147 		menu->popup(mapToGlobal(pos));
148 	}
149 }
150 
151 // Get Shift state as Win32 wParam
GetShiftWParam(QKeyEvent * event=nullptr)152 uint32_t GetShiftWParam(QKeyEvent * event = nullptr)
153 {
154 	auto modifiers = event ? event->modifiers() : QGuiApplication::keyboardModifiers();
155 	uint32_t result = 0;
156 	if (modifiers & Qt::ShiftModifier) result |= MK_SHIFT;
157 	if (modifiers & Qt::ControlModifier) result |= MK_CONTROL;
158 	if (modifiers & Qt::AltModifier) result |= MK_ALT;
159 	return result;
160 }
161 
mouseMoveEvent(QMouseEvent * eventMove)162 void C4ConsoleQtViewportView::mouseMoveEvent(QMouseEvent *eventMove)
163 {
164 	auto pr = GetDevicePixelRatio();
165 	if (IsPlayViewport())
166 	{
167 		bool is_in_drawrange = (Inside<int32_t>(eventMove->x() - cvp->DrawX, 0, cvp->ViewWdt - 1)
168 		                     && Inside<int32_t>(eventMove->y() - cvp->DrawY, 0, cvp->ViewHgt - 1));
169 		this->setCursor(is_in_drawrange ? Qt::BlankCursor : Qt::CrossCursor);
170 		C4GUI::MouseMove(C4MC_Button_None, eventMove->x() * pr, eventMove->y() * pr, GetShiftWParam(), cvp);
171 	}
172 	else
173 	{
174 		cvp->pWindow->EditCursorMove(eventMove->x() * pr, eventMove->y() * pr, GetShiftWParam());
175 		UpdateCursor();
176 	}
177 }
178 
UpdateCursor()179 void C4ConsoleQtViewportView::UpdateCursor()
180 {
181 	Qt::CursorShape cursor;
182 	if (::Console.EditCursor.HasTransformCursor())
183 		cursor = Qt::SizeAllCursor;
184 	else if (::Console.EditCursor.GetShapes()->HasDragCursor())
185 		cursor = ::Console.EditCursor.GetShapes()->GetDragCursor();
186 	else
187 		cursor = Qt::CrossCursor;
188 	this->setCursor(cursor);
189 }
190 
mousePressEvent(QMouseEvent * eventPress)191 void C4ConsoleQtViewportView::mousePressEvent(QMouseEvent *eventPress)
192 {
193 	auto pr = GetDevicePixelRatio();
194 	if (IsPlayViewport())
195 	{
196 		int32_t btn = C4MC_Button_None;
197 		switch (eventPress->button())
198 		{
199 		case Qt::LeftButton: btn = C4MC_Button_LeftDown; break;
200 		case Qt::RightButton: btn = C4MC_Button_RightDown; break;
201 		case Qt::MiddleButton: btn = C4MC_Button_MiddleDown; break;
202 		case Qt::XButton1: btn = C4MC_Button_X1Down; break;
203 		case Qt::XButton2: btn = C4MC_Button_X2Down; break;
204 		}
205 		C4GUI::MouseMove(btn, eventPress->x() * pr, eventPress->y() * pr, GetShiftWParam(), cvp);
206 	}
207 	else
208 	{
209 		// movement update needed before, so target is always up-to-date
210 		cvp->pWindow->EditCursorMove(eventPress->x() * pr, eventPress->y() * pr, GetShiftWParam());
211 		switch (eventPress->button())
212 		{
213 		case Qt::LeftButton: ::Console.EditCursor.LeftButtonDown(GetShiftWParam()); break;
214 		case Qt::RightButton: ::Console.EditCursor.RightButtonDown(GetShiftWParam()); break;
215 		}
216 	}
217 }
218 
mouseDoubleClickEvent(QMouseEvent * eventPress)219 void C4ConsoleQtViewportView::mouseDoubleClickEvent(QMouseEvent *eventPress)
220 {
221 	if (IsPlayViewport())
222 	{
223 		int32_t btn = C4MC_Button_None;
224 		switch (eventPress->button())
225 		{
226 		case Qt::LeftButton: btn = C4MC_Button_LeftDouble; break;
227 		case Qt::RightButton: btn = C4MC_Button_RightDouble; break;
228 		case Qt::MiddleButton: btn = C4MC_Button_MiddleDouble; break;
229 		case Qt::XButton1: btn = C4MC_Button_X1Double; break;
230 		case Qt::XButton2: btn = C4MC_Button_X2Double; break;
231 		}
232 		auto pr = GetDevicePixelRatio();
233 		C4GUI::MouseMove(btn, eventPress->x() * pr, eventPress->y() * pr, GetShiftWParam(), cvp);
234 	}
235 }
236 
mouseReleaseEvent(QMouseEvent * releaseEvent)237 void C4ConsoleQtViewportView::mouseReleaseEvent(QMouseEvent *releaseEvent)
238 {
239 	if (IsPlayViewport())
240 	{
241 		int32_t btn = C4MC_Button_None;
242 		switch (releaseEvent->button())
243 		{
244 		case Qt::LeftButton: btn = C4MC_Button_LeftUp; break;
245 		case Qt::RightButton: btn = C4MC_Button_RightUp; break;
246 		case Qt::MiddleButton: btn = C4MC_Button_MiddleUp; break;
247 		case Qt::XButton1: btn = C4MC_Button_X1Up; break;
248 		case Qt::XButton2: btn = C4MC_Button_X2Up; break;
249 		}
250 		auto pr = GetDevicePixelRatio();
251 		C4GUI::MouseMove(btn, releaseEvent->x() * pr, releaseEvent->y() * pr, GetShiftWParam(), cvp);
252 	}
253 	else
254 	{
255 		switch (releaseEvent->button())
256 		{
257 		case Qt::LeftButton: ::Console.EditCursor.LeftButtonUp(GetShiftWParam()); break;
258 		case Qt::RightButton: ::Console.EditCursor.RightButtonUp(GetShiftWParam()); break;
259 		}
260 	}
261 }
262 
wheelEvent(QWheelEvent * event)263 void C4ConsoleQtViewportView::wheelEvent(QWheelEvent *event)
264 {
265 	if (IsPlayViewport())
266 	{
267 		int delta = event->delta() / 8;
268 		if (!delta) delta = event->delta(); // abs(delta)<8?
269 		uint32_t shift = (delta>0) ? (delta<<16) : uint32_t(delta<<16);
270 		shift += GetShiftWParam();
271 		auto pr = GetDevicePixelRatio();
272 		C4GUI::MouseMove(C4MC_Button_Wheel, event->x() * pr, event->y() * pr, shift, cvp);
273 	}
274 	else
275 	{
276 		auto delta = event->angleDelta();
277 		auto modifiers = QGuiApplication::keyboardModifiers();
278 		// Zoom with Ctrl + mouse wheel, just like for player viewports.
279 		if (modifiers & Qt::ControlModifier)
280 			cvp->ChangeZoom(pow(C4GFX_ZoomStep, (float) delta.y() / 120));
281 		else
282 		{
283 			// Viewport movement.
284 			float x = -ViewportScrollSpeed * delta.x() / 120, y = -ViewportScrollSpeed * delta.y() / 120;
285 			// Not everyone has a vertical scroll wheel...
286 			if (modifiers & Qt::ShiftModifier)
287 				std::swap(x, y);
288 			cvp->ScrollView(x, y);
289 		}
290 	}
291 	// Event has been handled - do not forward to scroll bars
292 	event->accept();
293 }
294 
focusInEvent(QFocusEvent * event)295 void C4ConsoleQtViewportView::focusInEvent(QFocusEvent * event)
296 {
297 	dock->OnActiveChanged(true);
298 	QWidget::focusInEvent(event);
299 }
300 
focusOutEvent(QFocusEvent * event)301 void C4ConsoleQtViewportView::focusOutEvent(QFocusEvent * event)
302 {
303 	dock->OnActiveChanged(false);
304 	QWidget::focusOutEvent(event);
305 }
306 
307 
308 
309 /* Keyboard scan code mapping from Qt to our keys */
310 
311 /** Convert certain keys to (unix(?)) scancodes (those that differ from scancodes on Windows. Sometimes. Maybe.) */
312 
QtKeyToUnixScancode(const QKeyEvent & event)313 static C4KeyCode QtKeyToUnixScancode(const QKeyEvent &event)
314 {
315 	//LogF("VK: %x   SC: %x    key: %x", event.nativeVirtualKey(), event.nativeScanCode(), event.key());
316 	// Map some special keys
317 	switch (event.key())
318 	{
319 	case Qt::Key_Home:		return K_HOME;
320 	case Qt::Key_End:		return K_END;
321 	case Qt::Key_PageUp:	return K_PAGEUP;
322 	case Qt::Key_PageDown:	return K_PAGEDOWN;
323 	case Qt::Key_Up:		return K_UP;
324 	case Qt::Key_Down:		return K_DOWN;
325 	case Qt::Key_Left:		return K_LEFT;
326 	case Qt::Key_Right:		return K_RIGHT;
327 	case Qt::Key_Clear:		return K_CENTER;
328 	case Qt::Key_Insert:	return K_INSERT;
329 	case Qt::Key_Delete:	return K_DELETE;
330 	case Qt::Key_Menu:		return K_MENU;
331 	case Qt::Key_Pause:		return K_PAUSE;
332 	case Qt::Key_Print:		return K_PRINT;
333 	case Qt::Key_NumLock:	return K_NUM;
334 	case Qt::Key_ScrollLock:return K_SCROLL;
335 	default:
336 		// Some native Win32 key mappings...
337 #ifdef USE_WIN32_WINDOWS
338 		switch (event.nativeVirtualKey())
339 		{
340 		case VK_LWIN:		return K_WIN_L;
341 		case VK_RWIN:		return K_WIN_R;
342 		case VK_NUMPAD1:	return K_NUM1;
343 		case VK_NUMPAD2:	return K_NUM2;
344 		case VK_NUMPAD3:	return K_NUM3;
345 		case VK_NUMPAD4:	return K_NUM4;
346 		case VK_NUMPAD5:	return K_NUM5;
347 		case VK_NUMPAD6:	return K_NUM6;
348 		case VK_NUMPAD7:	return K_NUM7;
349 		case VK_NUMPAD8:	return K_NUM8;
350 		case VK_NUMPAD9:	return K_NUM9;
351 		case VK_NUMPAD0:	return K_NUM0;
352 		}
353 		switch (event.nativeScanCode())
354 		{
355 		case 285: return K_CONTROL_R;
356 		}
357 #endif
358 		// Otherwise rely on native scan code to be the same on all platforms
359 #if defined(USE_WIN32_WINDOWS) || defined(Q_OS_DARWIN)
360 		return event.nativeScanCode();
361 #else
362 		// Linux, FreeBSD, maybe others?
363 		return event.nativeScanCode() - 8;
364 #endif
365 	}
366 }
367 
keyPressEvent(QKeyEvent * event)368 void C4ConsoleQtViewportView::keyPressEvent(QKeyEvent * event)
369 {
370 	// Convert key to our internal mapping
371 	C4KeyCode code = QtKeyToUnixScancode(*event);
372 	// Viewport-only handling
373 	bool handled = false;
374 	if (code == K_SCROLL)
375 	{
376 		cvp->TogglePlayerLock();
377 		handled = true;
378 	}
379 	// Handled if handled as player control or main editor
380 	if (!handled) handled = Game.DoKeyboardInput(code, KEYEV_Down, !!(event->modifiers() & Qt::AltModifier), !!(event->modifiers() & Qt::ControlModifier), !!(event->modifiers() & Qt::ShiftModifier), event->isAutoRepeat(), nullptr);
381 	if (!handled) handled = dock->main_window->HandleEditorKeyDown(event);
382 	// Modifiers may update the cursor state; refresh
383 	if (event->key() == Qt::Key_Shift || event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_AltGr)
384 	{
385 		::Console.EditCursor.Move(GetShiftWParam(event));
386 		UpdateCursor();
387 	}
388 	event->setAccepted(handled);
389 }
390 
keyReleaseEvent(QKeyEvent * event)391 void C4ConsoleQtViewportView::keyReleaseEvent(QKeyEvent * event)
392 {
393 	// Convert key to our internal mapping
394 	C4KeyCode code = QtKeyToUnixScancode(*event);
395 	// Handled if handled as player control
396 	bool handled = Game.DoKeyboardInput(code, KEYEV_Up, !!(event->modifiers() & Qt::AltModifier), !!(event->modifiers() & Qt::ControlModifier), !!(event->modifiers() & Qt::ShiftModifier), event->isAutoRepeat(), nullptr);
397 	if (!handled) handled = dock->main_window->HandleEditorKeyUp(event);
398 	// Modifiers may update the cursor state; refresh
399 	if (event->key() == Qt::Key_Shift || event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_AltGr)
400 	{
401 		::Console.EditCursor.Move(GetShiftWParam(event));
402 		UpdateCursor();
403 	}
404 	event->setAccepted(handled);
405 }
406 
enterEvent(QEvent *)407 void C4ConsoleQtViewportView::enterEvent(QEvent *)
408 {
409 	// TODO: This should better be managed by the viewport
410 	// looks weird when there's multiple viewports open
411 	// but for some reason, the EditCursor drawing stuff is not associated with the viewport (yet)
412 	::Console.EditCursor.SetMouseHover(true);
413 }
414 
leaveEvent(QEvent *)415 void C4ConsoleQtViewportView::leaveEvent(QEvent *)
416 {
417 	// TODO: This should better be managed by the viewport
418 	::Console.EditCursor.SetMouseHover(false);
419 }
420 
initializeGL()421 void C4ConsoleQtViewportView::initializeGL()
422 {
423 	// init extensions
424 	glewExperimental = GL_TRUE;
425 	GLenum err = glewInit();
426 	if (GLEW_OK != err)
427 	{
428 		// Problem: glewInit failed, something is seriously wrong.
429 		LogF("glewInit: %s", reinterpret_cast<const char*>(glewGetErrorString(err)));
430 	}
431 }
432 
resizeGL(int w,int h)433 void C4ConsoleQtViewportView::resizeGL(int w, int h)
434 {
435 	auto pr = GetDevicePixelRatio();
436 	cvp->UpdateOutputSize(w * pr, h * pr);
437 }
438 
paintGL()439 void C4ConsoleQtViewportView::paintGL()
440 {
441 	cvp->ScrollBarsByViewPosition();
442 	cvp->Execute();
443 }
444 
445 
C4ConsoleQtViewportScrollArea(class C4ConsoleQtViewportDockWidget * dock)446 C4ConsoleQtViewportScrollArea::C4ConsoleQtViewportScrollArea(class C4ConsoleQtViewportDockWidget *dock)
447 	: QAbstractScrollArea(dock), dock(dock), cvp(dock->cvp->cvp), is_updating_scrollbars(0)
448 {
449 	cvp->scrollarea = this;
450 	// No scroll bars by default. Neutral viewports will toggle this.
451 	setScrollBarVisibility(false);
452 }
453 
scrollContentsBy(int dx,int dy)454 void C4ConsoleQtViewportScrollArea::scrollContentsBy(int dx, int dy)
455 {
456 	// Just use the absolute position in any case.
457 	if (!is_updating_scrollbars)
458 	{
459 		cvp->SetViewX(horizontalScrollBar()->value());
460 		cvp->SetViewY(verticalScrollBar()->value());
461 	}
462 }
463 
viewportEvent(QEvent * e)464 bool C4ConsoleQtViewportScrollArea::viewportEvent(QEvent *e)
465 {
466 	// Pass everything to the viewport.
467 	return false;
468 }
469 
setupViewport(QWidget * viewport)470 void C4ConsoleQtViewportScrollArea::setupViewport(QWidget *viewport)
471 {
472 	// Don't steal focus from the viewport. This is necessary to make keyboard input work.
473 	viewport->setFocusProxy(nullptr);
474 	ScrollBarsByViewPosition();
475 }
476 
ScrollBarsByViewPosition()477 void C4ConsoleQtViewportScrollArea::ScrollBarsByViewPosition()
478 {
479 	++is_updating_scrollbars; // Do not shift view just from updating scroll bars
480 	int x = viewport()->width() / cvp->GetZoom();
481 	horizontalScrollBar()->setRange(0, ::Landscape.GetWidth() - x);
482 	horizontalScrollBar()->setPageStep(x);
483 	horizontalScrollBar()->setValue(cvp->GetViewX());
484 
485 	int y = viewport()->height() / cvp->GetZoom();
486 	verticalScrollBar()->setRange(0, ::Landscape.GetHeight() - y);
487 	verticalScrollBar()->setPageStep(y);
488 	verticalScrollBar()->setValue(cvp->GetViewY());
489 	--is_updating_scrollbars;
490 }
491 
setScrollBarVisibility(bool visible)492 void C4ConsoleQtViewportScrollArea::setScrollBarVisibility(bool visible)
493 {
494 	if (visible)
495 	{
496 		setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
497 		setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
498 	}
499 	else
500 	{
501 		setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
502 		setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
503 	}
504 }
505 
C4ConsoleQtViewportDockWidget(C4ConsoleQtMainWindow * main_window,QMainWindow * parent,C4ViewportWindow * cvp)506 C4ConsoleQtViewportDockWidget::C4ConsoleQtViewportDockWidget(C4ConsoleQtMainWindow *main_window, QMainWindow *parent, C4ViewportWindow *cvp)
507 	: QDockWidget("", parent), main_window(main_window), cvp(cvp)
508 {
509 	// Translated title or player name
510 	C4Player *plr = ::Players.Get(cvp->cvp->GetPlayer());
511 	setWindowTitle(plr ? plr->GetName() : LoadResStr("IDS_CNS_VIEWPORT"));
512 	// Actual view container, wrapped in scrolling area
513 	auto scrollarea = new C4ConsoleQtViewportScrollArea(this);
514 	view = new C4ConsoleQtViewportView(scrollarea);
515 	scrollarea->setViewport(view);
516 	setWidget(scrollarea);
517 	connect(this, SIGNAL(topLevelChanged(bool)), this, SLOT(TopLevelChanged(bool)));
518 	// Register viewport widget for periodic rendering updates.
519 	cvp->viewport_widget = view;
520 }
521 
mousePressEvent(QMouseEvent * eventPress)522 void C4ConsoleQtViewportDockWidget::mousePressEvent(QMouseEvent *eventPress)
523 {
524 	// Clicking the dock focuses the viewport
525 	view->setFocus();
526 	QDockWidget::mousePressEvent(eventPress);
527 }
528 
OnActiveChanged(bool active)529 void C4ConsoleQtViewportDockWidget::OnActiveChanged(bool active)
530 {
531 	// Title bar of the selected viewport should be drawn in highlight colors to show that keyboard input will now go to the viewport.
532 	// Unfortunately, color and font of the title is not taken from QDockWidget::title but directly from QDockWidget.
533 	// Provide them in both just in case Qt ever changes its definition.
534 	QColor bgclr = QApplication::palette(this).color(QPalette::Highlight);
535 	QColor fontclr = QApplication::palette(this).color(QPalette::HighlightedText);
536 	if (active)
537 		setStyleSheet(QString(
538 			"QDockWidget::title { text-align: left; background: %1; padding-left: 3px; color: %2; font-weight: bold; } QDockWidget { color: %2; font-weight: bold; }").arg(bgclr.name(), fontclr.name()));
539 	else
540 		setStyleSheet(QString());
541 }
542 
TopLevelChanged(bool is_floating)543 void C4ConsoleQtViewportDockWidget::TopLevelChanged(bool is_floating)
544 {
545 	// Ensure focus after undock and after re-docking floating viewport window
546 	view->setFocus();
547 }
548 
closeEvent(QCloseEvent * event)549 void C4ConsoleQtViewportDockWidget::closeEvent(QCloseEvent * event)
550 {
551 	QDockWidget::closeEvent(event);
552 	if (event->isAccepted())
553 	{
554 		if (cvp)
555 		{
556 			// This causes "this" to be deleted:
557 			cvp->Close();
558 		}
559 		else
560 		{
561 			deleteLater();
562 		}
563 	}
564 }
565 
event(QEvent * e)566 bool C4ConsoleQtViewportDockWidget::event(QEvent *e)
567 {
568 	// Focus on title bar click
569 	if (e->type() == QEvent::NonClientAreaMouseButtonPress || e->type() == QEvent::MouseButtonPress) view->setFocus();
570 	return QDockWidget::event(e);
571 }
572