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 /* Editor windows using Qt*/
18 
19 #include "C4Include.h"
20 #include "editor/C4ConsoleQtState.h"
21 #include "editor/C4ConsoleQtPropListViewer.h"
22 #include "editor/C4ConsoleQtObjectListViewer.h"
23 #include "editor/C4ConsoleQtDefinitionListViewer.h"
24 #include "editor/C4ConsoleQtNewScenario.h"
25 #include "editor/C4ConsoleQtViewport.h"
26 #include "editor/C4ConsoleQtShapes.h"
27 #include "editor/C4ConsoleQtLocalizeOverview.h"
28 #include "editor/C4Console.h"
29 #include "platform/StdRegistry.h"
30 #include "landscape/C4Landscape.h"
31 #include "player/C4PlayerList.h"
32 #include "object/C4Def.h"
33 #include "object/C4Object.h"
34 #include "game/C4Viewport.h"
35 #include "config/C4Config.h"
36 
37 #ifdef USE_WIN32_WINDOWS
38 #include <shellapi.h>
39 #endif
40 
41 /* String translation */
42 
translate(const char * context,const char * sourceText,const char * disambiguation,int n) const43 QString C4ConsoleQtTranslator::translate(const char * context, const char * sourceText, const char * disambiguation, int n) const
44 {
45 	// Map to LoadResStr for all QStrings marked as res
46 	if (disambiguation && !strcmp(disambiguation, "res"))
47 		return QString(LoadResStr(sourceText));
48 	else
49 		return QString(sourceText);
50 }
51 
52 C4ConsoleQtTranslator qt_translator;
53 
54 /* Kick client action */
55 
C4ConsoleClientAction(int32_t client_id,const char * text,QObject * parent,C4ConsoleGUI::ClientOperation op)56 C4ConsoleClientAction::C4ConsoleClientAction(int32_t client_id, const char *text, QObject *parent, C4ConsoleGUI::ClientOperation op)
57 	: QAction(text, parent), client_id(client_id), op(op)
58 {
59 	connect(this, SIGNAL(triggered()), this, SLOT(Execute()));
60 }
61 
Execute()62 void C4ConsoleClientAction::Execute()
63 {
64 	if (!::Control.isCtrlHost()) return;
65 	switch (op)
66 	{
67 	case C4ConsoleGUI::CO_Deactivate:
68 		::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(client_id, CUT_Activate, false), CDT_Sync);
69 		break;
70 	case C4ConsoleGUI::CO_Activate:
71 		::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(client_id, CUT_Activate, true), CDT_Sync);
72 		break;
73 	case C4ConsoleGUI::CO_Kick:
74 		::Game.Clients.CtrlRemove(Game.Clients.getClientByID(client_id), LoadResStr("IDS_MSG_KICKBYMENU"));
75 		break;
76 	}
77 }
78 
79 
80 /* Remove player action */
81 
C4ConsoleRemovePlayerAction(int32_t player_num,const char * text,QObject * parent)82 C4ConsoleRemovePlayerAction::C4ConsoleRemovePlayerAction(int32_t player_num, const char *text, QObject *parent)
83 	: QAction(text, parent), player_num(player_num)
84 {
85 	connect(this, SIGNAL(triggered()), this, SLOT(Execute()));
86 }
87 
Execute()88 void C4ConsoleRemovePlayerAction::Execute()
89 {
90 	C4Player *plr = ::Players.Get(player_num);
91 	if (!plr) return;
92 	::Control.Input.Add(CID_PlrAction, C4ControlPlayerAction::Eliminate(plr));
93 }
94 
95 
96 /* Add viewport for player action */
97 
C4ConsoleOpenViewportAction(int32_t player_num,const char * text,QObject * parent)98 C4ConsoleOpenViewportAction::C4ConsoleOpenViewportAction(int32_t player_num, const char *text, QObject *parent)
99 	: QAction(text, parent), player_num(player_num)
100 {
101 	connect(this, SIGNAL(triggered()), this, SLOT(Execute()));
102 }
103 
Execute()104 void C4ConsoleOpenViewportAction::Execute()
105 {
106 	::Viewports.CreateViewport(player_num);
107 }
108 
109 
110 /* Recursion check to avoid some crashing Qt re-entry */
111 
112 class ExecRecursionCheck
113 {
114 	static int counter;
115 public:
ExecRecursionCheck()116 	ExecRecursionCheck() { ++counter; }
~ExecRecursionCheck()117 	~ExecRecursionCheck() { --counter; }
118 
IsRecursion() const119 	bool IsRecursion() const { return counter > 1; }
120 };
121 
122 int ExecRecursionCheck::counter = 0;
123 
124 
125 /* Console main window */
126 
C4ConsoleQtMainWindow(C4AbstractApp * app,C4ConsoleGUIState * state)127 C4ConsoleQtMainWindow::C4ConsoleQtMainWindow(C4AbstractApp *app, C4ConsoleGUIState *state)
128 	: QMainWindow(nullptr), state(state)
129 {
130 }
131 
keyPressEvent(QKeyEvent * event)132 void C4ConsoleQtMainWindow::keyPressEvent(QKeyEvent * event)
133 {
134 	if (HandleEditorKeyDown(event)) event->setAccepted(true);
135 	QMainWindow::keyPressEvent(event);
136 }
137 
keyReleaseEvent(QKeyEvent * event)138 void C4ConsoleQtMainWindow::keyReleaseEvent(QKeyEvent * event)
139 {
140 	if (HandleEditorKeyUp(event)) event->setAccepted(true);
141 	QMainWindow::keyPressEvent(event);
142 }
143 
144 // Editor window state config file
145 struct EditorWindowState
146 {
147 	StdCopyBuf geometry, window_state;
148 
CompileFuncEditorWindowState149 	void CompileFunc(StdCompiler *comp)
150 	{
151 		comp->Value(geometry);
152 		comp->Value(window_state);
153 	}
154 };
155 
closeEvent(QCloseEvent * event)156 void C4ConsoleQtMainWindow::closeEvent(QCloseEvent *event)
157 {
158 	// Store window settings
159 	EditorWindowState ws;
160 	QByteArray geometry = saveGeometry(), window_state = saveState();
161 	ws.geometry.Copy(geometry.constData(), geometry.size());
162 	ws.window_state.Copy(window_state.constData(), window_state.size());
163 	StdBuf ws_contents = DecompileToBuf<StdCompilerBinWrite>(ws);
164 	ws_contents.SaveToFile(Config.AtUserDataPath(C4CFN_EditorGeometry));
165 	// Perform close
166 	QMainWindow::closeEvent(event);
167 	::Console.Close();
168 }
169 
LoadGeometry()170 void C4ConsoleQtMainWindow::LoadGeometry()
171 {
172 	// Restore window settings from file
173 	StdBuf ws_contents;
174 	if (ws_contents.LoadFromFile(Config.AtUserDataPath(C4CFN_EditorGeometry)))
175 	{
176 		try
177 		{
178 			EditorWindowState ws;
179 			CompileFromBuf<StdCompilerBinRead>(ws, ws_contents);
180 			QByteArray geometry(static_cast<const char *>(ws.geometry.getData()), ws.geometry.getSize()),
181 					   window_state(static_cast<const char *>(ws.window_state.getData()), ws.window_state.getSize());
182 			restoreGeometry(geometry);
183 			restoreState(window_state);
184 		}
185 		catch (StdCompiler::Exception *e)
186 		{
187 			Log("Editor: Could not restore window settings");
188 			delete e;
189 		}
190 	}
191 }
192 
PlayPressed(bool down)193 void C4ConsoleQtMainWindow::PlayPressed(bool down)
194 {
195 	if (down)
196 		::Console.DoPlay();
197 	else // cannot un-check by pressing again
198 		state->ui.actionPlay->setChecked(true);
199 }
200 
PausePressed(bool down)201 void C4ConsoleQtMainWindow::PausePressed(bool down)
202 {
203 	if (down)
204 		::Console.DoHalt();
205 	else // can un-check by pressing again!
206 		::Console.DoPlay();
207 }
208 
CursorGamePressed(bool down)209 void C4ConsoleQtMainWindow::CursorGamePressed(bool down)
210 {
211 	if (down)
212 	{
213 		::Console.EditCursor.SetMode(C4CNS_ModePlay);
214 	}
215 	else
216 	{
217 		// cannot un-check by pressing again
218 		state->ui.actionCursorGame->setChecked(true);
219 	}
220 }
221 
CursorSelectPressed(bool down)222 void C4ConsoleQtMainWindow::CursorSelectPressed(bool down)
223 {
224 	if (down)
225 	{
226 		::Console.EditCursor.SetMode(C4CNS_ModeEdit);
227 		// When the select cursor is activated, always show either the property dock
228 		state->ui.propertyDockWidget->raise();
229 	}
230 	else
231 	{
232 		// cannot un-check by pressing again
233 		state->ui.actionCursorSelect->setChecked(true);
234 	}
235 }
236 
CursorCreateObjPressed(bool down)237 void C4ConsoleQtMainWindow::CursorCreateObjPressed(bool down)
238 {
239 	if (down)
240 	{
241 		::Console.EditCursor.SetMode(C4CNS_ModeCreateObject);
242 		// When the creator cursor is activated, always show the defintiion list
243 		state->ui.creatorDockWidget->raise();
244 	}
245 	else
246 	{
247 		// cannot un-check by pressing again
248 		state->ui.actionCursorCreateObj->setChecked(true);
249 	}
250 }
251 
CursorDrawPenPressed(bool down)252 void C4ConsoleQtMainWindow::CursorDrawPenPressed(bool down)
253 {
254 	if (down)
255 	{
256 		::Console.EditCursor.SetMode(C4CNS_ModeDraw);
257 		::Console.ToolsDlg.SetTool(C4TLS_Brush, false);
258 	}
259 	else // cannot un-check by pressing again
260 		state->ui.actionCursorDrawPen->setChecked(true);
261 }
262 
CursorDrawLinePressed(bool down)263 void C4ConsoleQtMainWindow::CursorDrawLinePressed(bool down)
264 {
265 	if (down)
266 	{
267 		::Console.EditCursor.SetMode(C4CNS_ModeDraw);
268 		::Console.ToolsDlg.SetTool(C4TLS_Line, false);
269 	}
270 	else // cannot un-check by pressing again
271 		state->ui.actionCursorDrawLine->setChecked(true);
272 }
273 
CursorDrawRectPressed(bool down)274 void C4ConsoleQtMainWindow::CursorDrawRectPressed(bool down)
275 {
276 	if (down)
277 	{
278 		::Console.EditCursor.SetMode(C4CNS_ModeDraw);
279 		::Console.ToolsDlg.SetTool(C4TLS_Rect, false);
280 	}
281 	else // cannot un-check by pressing again
282 		state->ui.actionCursorDrawRect->setChecked(true);
283 }
284 
CursorFillPressed(bool down)285 void C4ConsoleQtMainWindow::CursorFillPressed(bool down)
286 {
287 	if (down)
288 	{
289 		::Console.EditCursor.SetMode(C4CNS_ModeDraw);
290 		::Console.ToolsDlg.SetTool(C4TLS_Fill, false);
291 	}
292 	else // cannot un-check by pressing again
293 		state->ui.actionCursorFill->setChecked(true);
294 }
295 
296 
CursorPickerPressed(bool down)297 void C4ConsoleQtMainWindow::CursorPickerPressed(bool down)
298 {
299 	if (down)
300 	{
301 		::Console.EditCursor.SetMode(C4CNS_ModeDraw);
302 		::Console.ToolsDlg.SetTool(C4TLS_Picker, false);
303 	}
304 	else // cannot un-check by pressing again
305 		state->ui.actionCursorPicker->setChecked(true);
306 }
307 
DynamicLandscapePressed(bool down)308 void C4ConsoleQtMainWindow::DynamicLandscapePressed(bool down)
309 {
310 	if (down)
311 		::Console.ToolsDlg.SetLandscapeMode(LandscapeMode::Dynamic, false);
312 	else // cannot un-check by pressing again
313 		state->ui.actionDynamicLandscape->setChecked(true);
314 }
315 
StaticLandscapePressed(bool down)316 void C4ConsoleQtMainWindow::StaticLandscapePressed(bool down)
317 {
318 	if (down)
319 		::Console.ToolsDlg.SetLandscapeMode(LandscapeMode::Static, false);
320 	else // cannot un-check by pressing again
321 		state->ui.actionStaticLandscape->setChecked(true);
322 }
323 
StaticFlatLandscapePressed(bool down)324 void C4ConsoleQtMainWindow::StaticFlatLandscapePressed(bool down)
325 {
326 	if (down)
327 		::Console.ToolsDlg.SetLandscapeMode(LandscapeMode::Static, true);
328 	else // cannot un-check by pressing again
329 		state->ui.actionStaticFlatLandscape->setChecked(true);
330 }
331 
ExactLandscapePressed(bool down)332 void C4ConsoleQtMainWindow::ExactLandscapePressed(bool down)
333 {
334 	if (down)
335 		::Console.ToolsDlg.SetLandscapeMode(LandscapeMode::Exact, false);
336 	else // cannot un-check by pressing again
337 		state->ui.actionExactLandscape->setChecked(true);
338 }
339 
DrawSizeChanged(int newval)340 void C4ConsoleQtMainWindow::DrawSizeChanged(int newval)
341 {
342 	::Console.ToolsDlg.SetGrade(newval);
343 }
344 
OpenTranslationsOverview()345 void C4ConsoleQtMainWindow::OpenTranslationsOverview()
346 {
347 	// Open/refresh translations overview dialogue
348 	if (!state->translation_overview_dialogue)
349 	{
350 		state->translation_overview_dialogue.reset(new C4ConsoleQtLocalizeOverviewDlg(this));
351 	}
352 	state->translation_overview_dialogue->Refresh();
353 	state->translation_overview_dialogue->show();
354 	state->translation_overview_dialogue->resize(size() * 8/10);
355 	int32_t margin = size().width() / 10;
356 	QRect geom = geometry();
357 	geom.adjust(margin, margin, -margin, -margin);
358 	state->translation_overview_dialogue->setGeometry(geom);
359 	state->translation_overview_dialogue->raise();
360 	state->translation_overview_dialogue->activateWindow();
361 }
362 
363 // File menu
FileNew()364 void C4ConsoleQtMainWindow::FileNew() { ::Console.FileNew(); }
FileOpen()365 void C4ConsoleQtMainWindow::FileOpen() { ::Console.FileOpen(nullptr, false); }
FileOpenInNetwork()366 void C4ConsoleQtMainWindow::FileOpenInNetwork() { ::Console.FileOpen(nullptr, true); }
FileOpenWithPlayers()367 void C4ConsoleQtMainWindow::FileOpenWithPlayers() { Console.FileOpenWPlrs(); }
FileRecord()368 void C4ConsoleQtMainWindow::FileRecord() { ::Console.FileRecord(); }
FileSave()369 void C4ConsoleQtMainWindow::FileSave() { ::Console.FileSave(); }
FileSaveAs()370 void C4ConsoleQtMainWindow::FileSaveAs() { ::Console.FileSaveAs(false); }
FileSaveGameAs()371 void C4ConsoleQtMainWindow::FileSaveGameAs() { ::Console.FileSaveAs(true); }
FileExportPacked()372 void C4ConsoleQtMainWindow::FileExportPacked() { ::Console.FileSaveAs(false, true); }
FileClose()373 void C4ConsoleQtMainWindow::FileClose() { ::Console.FileClose(); }
FileQuit()374 void C4ConsoleQtMainWindow::FileQuit() { ::Console.FileQuit(); }
375 
FileReInitScenario()376 void C4ConsoleQtMainWindow::FileReInitScenario()
377 {
378 	::Control.DoInput(CID_ReInitScenario, new C4ControlReInitScenario(), CDT_Decide);
379 }
380 
381 // Player menu
PlayerJoin()382 void C4ConsoleQtMainWindow::PlayerJoin() { ::Console.PlayerJoin(); }
383 // Window menu
ViewportNew()384 void C4ConsoleQtMainWindow::ViewportNew() { ::Console.ViewportNew(); }
385 // Help menu
HelpAbout()386 void C4ConsoleQtMainWindow::HelpAbout() { ::Console.HelpAbout(); }
387 
HelpToggle(bool enabled)388 void C4ConsoleQtMainWindow::HelpToggle(bool enabled)
389 {
390 	::Config.Developer.ShowHelp = enabled;
391 	::Console.EditCursor.InvalidateSelection();
392 	repaint();
393 }
394 
395 // Script enter
MainConsoleEditEnter()396 void C4ConsoleQtMainWindow::MainConsoleEditEnter()
397 {
398 	QLineEdit *main_console_edit = state->ui.consoleInputBox->lineEdit();
399 	::Console.RegisterRecentInput(main_console_edit->text().toUtf8(), C4Console::MRU_Scenario);
400 	::Console.In(main_console_edit->text().toUtf8());
401 }
402 
PropertyConsoleEditEnter()403 void C4ConsoleQtMainWindow::PropertyConsoleEditEnter()
404 {
405 	QLineEdit *property_console_edit = state->ui.propertyInputBox->lineEdit();
406 	::Console.EditCursor.In(property_console_edit->text().toUtf8());
407 }
408 
409 // View selection changes
OnCreatorSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)410 void C4ConsoleQtMainWindow::OnCreatorSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
411 {
412 	state->OnCreatorSelectionChanged(selected, deselected);
413 }
414 
OnCreatorCurrentChanged(const QModelIndex & current,const QModelIndex & previous)415 void C4ConsoleQtMainWindow::OnCreatorCurrentChanged(const QModelIndex & current, const QModelIndex & previous)
416 {
417 	state->OnCreatorCurrentChanged(current, previous);
418 }
419 
OnObjectListSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)420 void C4ConsoleQtMainWindow::OnObjectListSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
421 {
422 	state->OnObjectListSelectionChanged(selected, deselected);
423 }
424 
AscendPropertyPath()425 void C4ConsoleQtMainWindow::AscendPropertyPath()
426 {
427 	state->property_model->AscendPath();
428 	::Console.EditCursor.InvalidateSelection();
429 }
430 
AddArrayElement()431 void C4ConsoleQtMainWindow::AddArrayElement()
432 {
433 	if (state->property_model) state->property_model->AddArrayElement();
434 }
435 
RemoveArrayElement()436 void C4ConsoleQtMainWindow::RemoveArrayElement()
437 {
438 	if (state->property_model) state->property_model->RemoveArrayElement();
439 }
440 
441 
HandleEditorKeyDown(QKeyEvent * event)442 bool C4ConsoleQtMainWindow::HandleEditorKeyDown(QKeyEvent *event)
443 {
444 	switch (event->key())
445 	{
446 	case Qt::Key_Delete:
447 		::Console.EditCursor.Delete();
448 		return true;
449 	case Qt::Key_F2:
450 		::Console.EditCursor.Duplicate();
451 		return true;
452 	}
453 	uint32_t shift = 0;
454 	if (event->modifiers() & Qt::AltModifier) shift |= MK_ALT;
455 	if (event->modifiers() & Qt::ControlModifier) shift |= MK_CONTROL;
456 	if (event->modifiers() & Qt::ShiftModifier) shift |= MK_SHIFT;
457 	::Console.EditCursor.KeyDown(event->nativeScanCode(), shift);
458 	// key not handled (ignore shift handling done in EditCursor)
459 	return false;
460 }
461 
HandleEditorKeyUp(QKeyEvent * event)462 bool C4ConsoleQtMainWindow::HandleEditorKeyUp(QKeyEvent *event)
463 {
464 	uint32_t shift = 0;
465 	if (event->modifiers() & Qt::AltModifier) shift |= MK_ALT;
466 	if (event->modifiers() & Qt::ControlModifier) shift |= MK_CONTROL;
467 	if (event->modifiers() & Qt::ShiftModifier) shift |= MK_SHIFT;
468 	::Console.EditCursor.KeyUp(event->nativeScanCode(), shift);
469 	// key not handled (ignore shift handling done in EditCursor)
470 	return false;
471 }
472 
SplitMaterialTexture(const QString & mat_tex,QString * mat,QString * tex)473 void SplitMaterialTexture(const QString &mat_tex, QString *mat, QString *tex)
474 {
475 	int sep = mat_tex.indexOf('-');
476 	if (sep < 0)
477 	{
478 		*mat = mat_tex;
479 		*tex = QString();
480 	}
481 	else
482 	{
483 		*mat = mat_tex.mid(0, sep);
484 		*tex = mat_tex.mid(sep + 1);
485 	}
486 }
487 
ForegroundMaterialChanged(const QString & new_selection)488 void C4ConsoleQtMainWindow::ForegroundMaterialChanged(const QString &new_selection)
489 {
490 	QString mat, tex;
491 	SplitMaterialTexture(new_selection, &mat, &tex);
492 	if (mat.size() > 0) ::Console.ToolsDlg.SelectMaterial(mat.toUtf8(), true);
493 	if (tex.size() > 0) ::Console.ToolsDlg.SelectTexture(tex.toUtf8(), true);
494 }
495 
BackgroundMaterialChanged(const QString & new_selection)496 void C4ConsoleQtMainWindow::BackgroundMaterialChanged(const QString &new_selection)
497 {
498 	QString mat, tex;
499 	SplitMaterialTexture(new_selection, &mat, &tex);
500 	if (mat.size() > 0) ::Console.ToolsDlg.SelectBackMaterial(mat.toUtf8(), true);
501 	if (tex.size() > 0) ::Console.ToolsDlg.SelectBackTexture(tex.toUtf8(), true);
502 }
503 
WelcomeLinkActivated(const QString & link)504 void C4ConsoleQtMainWindow::WelcomeLinkActivated(const QString &link)
505 {
506 	// Default links
507 	if (link == "new") FileNew();
508 	else if (link == "open") FileOpen();
509 	else if (link == "exploreuserpath")
510 	{
511 		bool success = false;
512 #ifdef USE_WIN32_WINDOWS
513 		StdStrBuf path(::Config.General.UserDataPath);
514 		intptr_t iError = (intptr_t) ::ShellExecute(nullptr, L"open", path.GetWideChar(), nullptr, path.GetWideChar(), SW_SHOW);
515 		if (iError > 32) success = true;
516 #else
517 		success = QDesktopServices::openUrl(QUrl::fromLocalFile(::Config.General.UserDataPath));
518 #endif
519 		if (!success)
520 			QMessageBox::critical(this, LoadResStr("IDS_MNU_EXPLOREUSERPATH"), LoadResStr("IDS_ERR_EXPLOREUSERPATH"));
521 	}
522 	// Open recent link
523 	else if (link.startsWith("open:"))
524 	{
525 		QString open_file = link.mid(5);
526 		::Console.FileOpen(open_file.toUtf8());
527 	}
528 }
529 
SelectionDelete()530 void C4ConsoleQtMainWindow::SelectionDelete()
531 {
532 	::Console.EditCursor.Delete();
533 }
534 
SelectionDuplicate()535 void C4ConsoleQtMainWindow::SelectionDuplicate()
536 {
537 	::Console.EditCursor.Duplicate();
538 }
539 
SelectionEjectContents()540 void C4ConsoleQtMainWindow::SelectionEjectContents()
541 {
542 	::Console.EditCursor.GrabContents();
543 }
544 
FocusGlobalScriptBox()545 void C4ConsoleQtMainWindow::FocusGlobalScriptBox()
546 {
547 	state->ui.logDockWidget->show();
548 	state->ui.logDockWidget->raise();
549 	state->ui.consoleInputBox->setFocus();
550 }
551 
FocusObjectScriptBox()552 void C4ConsoleQtMainWindow::FocusObjectScriptBox()
553 {
554 	state->ui.propertyDockWidget->show();
555 	state->ui.propertyDockWidget->raise();
556 	state->ui.propertyInputBox->setFocus();
557 }
558 
OpenMaterialSelection()559 void C4ConsoleQtMainWindow::OpenMaterialSelection()
560 {
561 	if (state->ui.foregroundMatTexComboBox->isEnabled())
562 	{
563 		state->ui.foregroundMatTexComboBox->setFocus();
564 		state->ui.foregroundMatTexComboBox->showPopup();
565 	}
566 }
567 
FocusNextViewport()568 void C4ConsoleQtMainWindow::FocusNextViewport()
569 {
570 	// Focus viewport after the one that has focus
571 	bool has_focus_vp = false;
572 	for (C4ConsoleQtViewportDockWidget *vp : state->viewports)
573 	{
574 		if (has_focus_vp)
575 		{
576 			vp->SetFocus();
577 			return;
578 		}
579 		else if (vp->HasFocus())
580 		{
581 			has_focus_vp = true;
582 		}
583 	}
584 	// No focus or last viewport was focused? Focus first.
585 	if (state->viewports.size())
586 	{
587 		state->viewports.front()->SetFocus();
588 	}
589 }
590 
GradeUp()591 void C4ConsoleQtMainWindow::GradeUp()
592 {
593 	if (state->ui.drawSizeSlider->isEnabled())
594 	{
595 		state->ui.drawSizeSlider->setValue(state->ui.drawSizeSlider->value() + state->ui.drawSizeSlider->singleStep());
596 	}
597 }
598 
GradeDown()599 void C4ConsoleQtMainWindow::GradeDown()
600 {
601 	if (state->ui.drawSizeSlider->isEnabled())
602 	{
603 		state->ui.drawSizeSlider->setValue(state->ui.drawSizeSlider->value() - state->ui.drawSizeSlider->singleStep());
604 	}
605 }
606 
607 
608 /* Common C4ConsoleGUI interface */
609 
C4ConsoleGUIState(C4ConsoleGUI * console)610 C4ConsoleGUIState::C4ConsoleGUIState(C4ConsoleGUI *console) : viewport_area(nullptr),
611 		enabled(false), recording(false), net_enabled(false), landscape_mode(LandscapeMode::Dynamic), flat_chunk_shapes(false),
612 	editcursor_mode(C4CNS_ModePlay), drawing_tool(C4TLS_Brush), is_object_selection_updating(0), disable_shortcut_filter(new C4DisableShortcutFilter(nullptr))
613 {
614 }
615 
616 C4ConsoleGUIState::~C4ConsoleGUIState() = default;
617 
AddToolbarSpacer(int space)618 void C4ConsoleGUIState::AddToolbarSpacer(int space)
619 {
620 	auto spacer = new QWidget();
621 	spacer->setFixedWidth(space);
622 	ui.toolBar->addWidget(spacer);
623 }
624 
CreateConsoleWindow(C4AbstractApp * app)625 bool C4ConsoleGUIState::CreateConsoleWindow(C4AbstractApp *app)
626 {
627 	// No Qt main loop execution during console creation
628 	ExecRecursionCheck no_qt_recursion;
629 
630 	// Initialize OpenGL.
631 	QSurfaceFormat format;
632 	format.setMajorVersion(/*REQUESTED_GL_CTX_MAJOR*/ 3);
633 	format.setMinorVersion(/*REQUESTED_GL_CTX_MINOR*/ 2);
634 	format.setRedBufferSize(8);
635 	format.setGreenBufferSize(8);
636 	format.setBlueBufferSize(8);
637 	format.setDepthBufferSize(8);
638 	format.setProfile(QSurfaceFormat::CoreProfile);
639 	format.setSwapInterval(0); // turn off vsync because otherwise each viewport causes an extra 1/(refesh rate) delay
640 	if (Config.Graphics.DebugOpenGL)
641 		format.setOption(QSurfaceFormat::DebugContext);
642 	QSurfaceFormat::setDefaultFormat(format);
643 	QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
644 
645 
646 	// Basic Qt+Main window setup from .ui file
647 	// Note that QApplication needs at least one valid argument which must
648 	// stay valid over the lifetime of the application.
649 	static int fake_argc = 1;
650 	static const char *fake_argv[] = { "openclonk" };
651 	application = std::make_unique<QApplication>(fake_argc, const_cast<char **>(fake_argv));
652 	application->installTranslator(&qt_translator);
653 	window = std::make_unique<C4ConsoleQtMainWindow>(app, this);
654 	ui.setupUi(window.get());
655 
656 	// Setup some extra stuff that cannot be done easily in the designer
657 	// Divide status bar
658 	status_cursor = new QLabel("", window.get());
659 	status_framecounter = new QLabel("", window.get());
660 	status_timefps = new QLabel("", window.get());
661 	ui.statusbar->addPermanentWidget(status_cursor, 3);
662 	ui.statusbar->addPermanentWidget(status_framecounter, 1);
663 	ui.statusbar->addPermanentWidget(status_timefps, 1);
664 	// Move drawing tools into toolbar
665 	ui.toolBar->addWidget(ui.foregroundMatTexComboBox);
666 	ui.toolBar->addWidget(ui.backgroundMatTexComboBox);
667 	AddToolbarSpacer(5);
668 	ui.toolBar->addWidget(ui.drawSizeLabel);
669 	AddToolbarSpacer(5);
670 	ui.toolBar->addWidget(ui.drawSizeSlider);
671 	ui.drawSizeSlider->setMaximum(C4TLS_GradeMax);
672 	ui.drawSizeSlider->setMinimum(C4TLS_GradeMin);
673 	ui.drawSizeSlider->setValue(C4TLS_GradeDefault);
674 	ui.drawSizeSlider->setSingleStep(1);
675 	// Console input box signal
676 	QLineEdit *main_console_edit = ui.consoleInputBox->lineEdit();
677 	main_console_edit->completer()->setCaseSensitivity(Qt::CaseSensitivity::CaseSensitive);
678 	main_console_edit->connect(main_console_edit, SIGNAL(returnPressed()), window.get(), SLOT(MainConsoleEditEnter()));
679 	QLineEdit *property_console_edit = ui.propertyInputBox->lineEdit();
680 	property_console_edit->connect(property_console_edit, SIGNAL(returnPressed()), window.get(), SLOT(PropertyConsoleEditEnter()));
681 	property_console_edit->completer()->setCaseSensitivity(Qt::CaseSensitivity::CaseSensitive);
682 	// Add window menu actions
683 	window_menu_separator = ui.menuWindows->addSeparator();
684 	QAction *dock_action = ui.creatorDockWidget->toggleViewAction();
685 	dock_action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_1));
686 	ui.menuWindows->addAction(dock_action);
687 	dock_action = ui.objectListDockWidget->toggleViewAction();
688 	dock_action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_2));
689 	ui.menuWindows->addAction(dock_action);
690 	dock_action = ui.propertyDockWidget->toggleViewAction();
691 	dock_action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_3));
692 	ui.menuWindows->addAction(dock_action);
693 	dock_action = ui.logDockWidget->toggleViewAction();
694 	dock_action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_4));
695 	ui.menuWindows->addAction(dock_action);
696 	// Viewport area setup
697 	viewport_area = new QMainWindow();
698 	viewport_area->setWindowFlags(Qt::Widget);
699 	window->setCentralWidget(viewport_area);
700 	window->setDockNestingEnabled(true);
701 	viewport_area->setDockNestingEnabled(true);
702 	QWidget *foo = new QWidget(viewport_area);
703 	viewport_area->setCentralWidget(foo);
704 	foo->hide();
705 	// Default action state
706 	ui.actionHelp->setChecked(::Config.Developer.ShowHelp);
707 
708 	// Disable some shortcuts on actions that are handled internally
709 	// (none right now)
710 
711 	// Property editor
712 	property_delegate_factory = std::make_unique<C4PropertyDelegateFactory>();
713 	ui.propertyTable->setItemDelegateForColumn(1, property_delegate_factory.get());
714 	ui.propertyEditAscendPathButton->setMaximumWidth(ui.propertyEditAscendPathButton->fontMetrics().boundingRect(ui.propertyEditAscendPathButton->text()).width() + 10);
715 	ui.propertyTable->setDropIndicatorShown(true);
716 	ui.propertyTable->setAcceptDrops(true);
717 	property_name_delegate = std::make_unique<C4PropertyNameDelegate>();
718 	ui.propertyTable->setItemDelegateForColumn(0, property_name_delegate.get());
719 	ui.propertyTable->setMouseTracking(true);
720 
721 	// View models
722 	property_model = std::make_unique<C4ConsoleQtPropListModel>(property_delegate_factory.get());
723 	property_delegate_factory->SetPropertyModel(property_model.get());
724 	property_name_delegate->SetPropertyModel(property_model.get());
725 	QItemSelectionModel *m = ui.propertyTable->selectionModel();
726 	ui.propertyTable->setModel(property_model.get());
727 	delete m;
728 	property_model->SetSelectionModel(ui.propertyTable->selectionModel());
729 	object_list_model = std::make_unique<C4ConsoleQtObjectListModel>();
730 	m = ui.objectListView->selectionModel();
731 	ui.objectListView->setModel(object_list_model.get());
732 	delete m;
733 	window->connect(ui.objectListView->selectionModel(), &QItemSelectionModel::selectionChanged, window.get(), &C4ConsoleQtMainWindow::OnObjectListSelectionChanged);
734 	definition_list_model = std::make_unique<C4ConsoleQtDefinitionListModel>();
735 	property_delegate_factory->SetDefinitionListModel(definition_list_model.get());
736 	m = ui.creatorTreeView->selectionModel();
737 	ui.creatorTreeView->setModel(definition_list_model.get());
738 	delete m;
739 	window->connect(ui.creatorTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, window.get(), &C4ConsoleQtMainWindow::OnCreatorSelectionChanged);
740 	window->connect(ui.creatorTreeView->selectionModel(), &QItemSelectionModel::currentChanged, window.get(), &C4ConsoleQtMainWindow::OnCreatorCurrentChanged);
741 	window->connect(ui.propertyTable->selectionModel(), &QItemSelectionModel::currentChanged, window.get(), [this]() {
742 		this->ui.arrayRemoveButton->setDisabled(this->property_model->IsTargetReadonly() || this->ui.propertyTable->selectionModel()->selectedRows().empty());
743 	});
744 
745 	// Double-clicking an item in the object list focuses and raises the property window
746 	window->connect(ui.objectListView, &QTreeView::doubleClicked, window.get(), [this](const QModelIndex &index) {
747 		window->FocusObjectScriptBox();
748 	});
749 
750 	// Initial layout is tabified (somehow I cannot do this in the designer)
751 	window->tabifyDockWidget(ui.objectListDockWidget, ui.propertyDockWidget);
752 	window->tabifyDockWidget(ui.objectListDockWidget, ui.creatorDockWidget);
753 	ui.propertyDockWidget->raise();
754 
755 	// Welcome page
756 	InitWelcomeScreen();
757 	ShowWelcomeScreen();
758 
759 	// Initial empty property page
760 	auto sel = C4EditCursorSelection();
761 	PropertyDlgUpdate(sel, true);
762 
763 	// Restore layout & show!
764 	window->LoadGeometry();
765 	window->show();
766 	return true;
767 }
768 
DeleteConsoleWindow()769 void C4ConsoleGUIState::DeleteConsoleWindow()
770 {
771 	// Reset to a state before CreateConsoleWindow was called
772 	action_object = C4VNull;
773 	is_object_selection_updating = false;
774 
775 	editcursor_mode = C4CNS_ModePlay;
776 	drawing_tool = C4TLS_Brush;
777 	landscape_mode = LandscapeMode::Dynamic;
778 	net_enabled = false;
779 	recording = false;
780 	enabled = false;
781 
782 	window_menu_separator = nullptr;
783 	status_cursor = status_framecounter = status_timefps = nullptr;
784 
785 	while (!viewports.empty())
786 	{
787 		auto vp = viewports.front();
788 		viewports.erase(viewports.begin());
789 
790 		viewport_area->removeDockWidget(vp);
791 		delete vp;
792 	}
793 
794 	client_actions.clear();
795 	player_actions.clear();
796 	viewport_actions.clear();
797 	viewport_area = nullptr;
798 
799 	disable_shortcut_filter.reset(nullptr);
800 	definition_list_model.reset(nullptr);
801 	object_list_model.reset(nullptr);
802 	property_name_delegate.reset(nullptr);
803 	property_delegate_factory.reset(nullptr);
804 	property_model.reset(nullptr);
805 	window.reset(nullptr);
806 	application.reset(nullptr);
807 }
808 
Execute(bool redraw_only)809 void C4ConsoleGUIState::Execute(bool redraw_only)
810 {
811 	// Nothing to do - Qt's event loop is handling everything.
812 }
813 
814 // Set action pressed/checked and enabled states
UpdateActionStates()815 void C4ConsoleGUIState::UpdateActionStates()
816 {
817 	// Enabled states
818 	bool has_draw_tools = enabled && landscape_mode != LandscapeMode::Dynamic;
819 	bool has_exact_draw_tools = enabled && landscape_mode == LandscapeMode::Exact;
820 	bool is_drawing = has_draw_tools && editcursor_mode == C4CNS_ModeDraw;
821 	bool is_lobby = ::Network.isLobbyActive();
822 	ui.actionFileNew->setEnabled(!enabled);
823 	ui.actionFileReInitScenario->setEnabled(enabled);
824 	ui.actionPlay->setEnabled(enabled || is_lobby);
825 	ui.actionPause->setEnabled(enabled);
826 	ui.actionCursorGame->setEnabled(enabled);
827 	ui.actionCursorSelect->setEnabled(enabled);
828 	ui.actionCursorCreateObj->setEnabled(enabled);
829 	ui.actionCursorDrawPen->setEnabled(has_draw_tools);
830 	ui.actionCursorDrawLine->setEnabled(has_draw_tools);
831 	ui.actionCursorDrawRect->setEnabled(has_draw_tools);
832 	ui.actionCursorPicker->setEnabled(has_draw_tools);
833 	ui.actionCursorFill->setEnabled(has_exact_draw_tools);
834 	ui.actionDynamicLandscape->setEnabled(enabled);
835 	ui.actionStaticLandscape->setEnabled(enabled);
836 	ui.actionStaticFlatLandscape->setEnabled(enabled);
837 	ui.actionExactLandscape->setEnabled(enabled);
838 	ui.actionTranslations->setEnabled(enabled);
839 	ui.foregroundMatTexComboBox->setEnabled(is_drawing);
840 	ui.backgroundMatTexComboBox->setEnabled(is_drawing);
841 	ui.drawSizeSlider->setEnabled(is_drawing);
842 	ui.actionFileClose->setEnabled(enabled);
843 	ui.actionFileRecord->setEnabled(enabled && !recording);
844 	ui.actionFileSaveGameAs->setEnabled(enabled);
845 	ui.actionFileSaveScenario->setEnabled(enabled);
846 	ui.actionFileSaveScenarioAs->setEnabled(enabled);
847 	ui.actionFileExportScenarioPacked->setEnabled(enabled);
848 	ui.actionViewportNew->setEnabled(enabled);
849 	ui.actionPlayerJoin->setEnabled(enabled);
850 	ui.menuNet->setEnabled(net_enabled);
851 
852 	// Checked states
853 	ui.actionCursorGame->setChecked(editcursor_mode == C4CNS_ModePlay);
854 	ui.actionCursorSelect->setChecked(editcursor_mode == C4CNS_ModeEdit);
855 	ui.actionCursorCreateObj->setChecked(editcursor_mode == C4CNS_ModeCreateObject);
856 	ui.actionCursorDrawPen->setChecked((editcursor_mode == C4CNS_ModeDraw) && (drawing_tool == C4TLS_Brush));
857 	ui.actionCursorDrawLine->setChecked((editcursor_mode == C4CNS_ModeDraw) && (drawing_tool == C4TLS_Line));
858 	ui.actionCursorDrawRect->setChecked((editcursor_mode == C4CNS_ModeDraw) && (drawing_tool == C4TLS_Rect));
859 	ui.actionCursorFill->setChecked((editcursor_mode == C4CNS_ModeDraw) && (drawing_tool == C4TLS_Fill));
860 	ui.actionCursorPicker->setChecked((editcursor_mode == C4CNS_ModeDraw) && (drawing_tool == C4TLS_Picker));
861 	ui.actionDynamicLandscape->setChecked(landscape_mode == LandscapeMode::Dynamic);
862 	ui.actionStaticLandscape->setChecked(landscape_mode == LandscapeMode::Static && !flat_chunk_shapes);
863 	ui.actionStaticFlatLandscape->setChecked(landscape_mode == LandscapeMode::Static && flat_chunk_shapes);
864 	ui.actionExactLandscape->setChecked(landscape_mode == LandscapeMode::Exact);
865 	ui.actionFileRecord->setChecked(recording);
866 }
867 
868 // Put function list into combo box selectable items
SetComboItems(QComboBox * box,std::list<const char * > & items)869 static void SetComboItems(QComboBox *box, std::list<const char*> &items)
870 {
871 	QString text = box->lineEdit()->text(); // remember and restore current text
872 	box->clear();
873 	for (auto & item : items)
874 	{
875 		if (!item)
876 			box->addItem("----------");
877 		else
878 			box->addItem(item);
879 	}
880 	box->lineEdit()->setText(text);
881 }
882 
UpdateMatTex()883 void C4ConsoleGUIState::UpdateMatTex()
884 {
885 	// Update selection of mattex in combo box
886 	int new_index = 0;
887 	if (material != C4TLS_MatSky) new_index = ui.foregroundMatTexComboBox->findText(QString(FormatString("%s-%s", material.getData(), texture.getData()).getData()));
888 	if (new_index >= 0) ui.foregroundMatTexComboBox->setCurrentIndex(new_index);
889 }
890 
UpdateBackMatTex()891 void C4ConsoleGUIState::UpdateBackMatTex()
892 {
893 	// Update selection of mattex in combo box
894 	int new_index = 0;
895 	if (back_material != C4TLS_MatSky) new_index = ui.backgroundMatTexComboBox->findText(QString(FormatString("%s-%s", back_material.getData(), back_texture.getData()).getData()));
896 	if (new_index >= 0) ui.backgroundMatTexComboBox->setCurrentIndex(new_index);
897 }
898 
AddNetMenuItem(int32_t index,const char * text,C4ConsoleGUI::ClientOperation op)899 void C4ConsoleGUIState::AddNetMenuItem(int32_t index, const char *text, C4ConsoleGUI::ClientOperation op)
900 {
901 	auto *kick_action = new C4ConsoleClientAction(index, text, ui.menuNet, op);
902 	if (op == C4ConsoleGUI::CO_None) kick_action->setDisabled(true);
903 	client_actions.emplace_back(kick_action);
904 	ui.menuNet->addAction(kick_action);
905 }
906 
ClearNetMenu()907 void C4ConsoleGUIState::ClearNetMenu()
908 {
909 	for (auto &action : client_actions) ui.menuNet->removeAction(action.get());
910 	client_actions.clear();
911 }
912 
AddKickPlayerMenuItem(int32_t plr,const char * text,bool item_enabled)913 void C4ConsoleGUIState::AddKickPlayerMenuItem(int32_t plr, const char *text, bool item_enabled)
914 {
915 	auto *kick_action = new C4ConsoleRemovePlayerAction(plr, text, ui.menuPlayers);
916 	kick_action->setEnabled(item_enabled);
917 	player_actions.emplace_back(kick_action);
918 	ui.menuPlayers->addAction(kick_action);
919 }
920 
ClearPlayerMenu()921 void C4ConsoleGUIState::ClearPlayerMenu()
922 {
923 	for (auto &action : player_actions) ui.menuPlayers->removeAction(action.get());
924 	player_actions.clear();
925 }
926 
AddPlayerViewportMenuItem(int32_t plr,const char * text)927 void C4ConsoleGUIState::AddPlayerViewportMenuItem(int32_t plr, const char *text)
928 {
929 	auto *action = new C4ConsoleOpenViewportAction(plr, text, ui.menuWindows);
930 	viewport_actions.emplace_back(action);
931 	ui.menuWindows->insertAction(window_menu_separator, action);
932 }
933 
ClearViewportMenu()934 void C4ConsoleGUIState::ClearViewportMenu()
935 {
936 	for (auto &action : viewport_actions) ui.menuWindows->removeAction(action.get());
937 	viewport_actions.clear();
938 }
939 
AddViewport(C4ViewportWindow * cvp)940 void C4ConsoleGUIState::AddViewport(C4ViewportWindow *cvp)
941 {
942 	if (!viewport_area) return;
943 	C4ConsoleQtViewportDockWidget *new_viewport = new C4ConsoleQtViewportDockWidget(window.get(), viewport_area, cvp);
944 	viewport_area->addDockWidget(Qt::BottomDockWidgetArea, new_viewport);
945 	viewports.push_back(new_viewport);
946 	new_viewport->SetFocus();
947 }
948 
RemoveViewport(C4ViewportWindow * cvp)949 void C4ConsoleGUIState::RemoveViewport(C4ViewportWindow *cvp)
950 {
951 	if (!viewport_area) return;
952 
953 	for (auto iter = viewports.begin(); iter != viewports.end(); )
954 	{
955 		auto vp = *iter;
956 		if (vp->GetViewportWindow() == cvp)
957 		{
958 			viewport_area->removeDockWidget(vp);
959 			iter = viewports.erase(iter);
960 
961 			// cannot use deleteLater here because Qt will then
962 			// still select/deselect the viewport's GL context
963 			// behind the scenes, leaving us with an unselected
964 			// GL context.
965 			// Documented at http://doc.qt.io/qt-5/qopenglwidget.html
966 			// Instead, delete the viewport widget directly.
967 			delete vp;
968 		}
969 		else
970 		{
971 			++iter;
972 		}
973 	}
974 }
975 
SetInputFunctions(std::list<const char * > & functions)976 void C4ConsoleGUIState::SetInputFunctions(std::list<const char*> &functions)
977 {
978 	SetComboItems(ui.consoleInputBox, functions);
979 }
980 
PropertyDlgUpdate(C4EditCursorSelection & rSelection,bool force_function_update)981 void C4ConsoleGUIState::PropertyDlgUpdate(C4EditCursorSelection &rSelection, bool force_function_update)
982 {
983 	int sel_count = rSelection.size();
984 	bool is_array = false;
985 	if (sel_count != 1)
986 	{
987 		// Multi object selection: Hide property view; show info label
988 		property_model->SetBasePropList(nullptr);
989 		ui.propertyTable->setEnabled(false);
990 		ui.selectionInfoLabel->setText(rSelection.GetDataString().getData());
991 		ui.propertyEditAscendPathButton->hide();
992 		UpdateActionObject(nullptr);
993 		ui.selectionHelpLabel->hide();
994 	}
995 	else
996 	{
997 		// Single object selection: Show property view + Object info in label
998 		C4PropList *prev_list = property_model->GetBasePropList(), *new_list = rSelection.front().getPropList();
999 		if (prev_list != new_list)
1000 		{
1001 			property_model->SetBasePropList(new_list);
1002 			ui.propertyTable->setFirstColumnSpanned(0, QModelIndex(), true);
1003 			ui.propertyTable->setFirstColumnSpanned(1, QModelIndex(), true);
1004 			ui.propertyTable->expand(property_model->index(0, 0, QModelIndex()));
1005 			UpdateActionObject(new_list->GetObject());
1006 		}
1007 		else if (::Console.EditCursor.IsSelectionInvalidated())
1008 		{
1009 			property_model->UpdateValue(false);
1010 		}
1011 		ui.selectionInfoLabel->setText(property_model->GetTargetPathText());
1012 		QString help_text = property_model->GetTargetPathHelp();
1013 		if (!help_text.isEmpty() && ::Config.Developer.ShowHelp)
1014 		{
1015 			const char *help_label = property_model->GetTargetPathName();
1016 			if (!help_label) help_label = LoadResStr("IDS_CNS_DESCRIPTION");
1017 			ui.selectionHelpLabel->setText(QString("%1: %2").arg(help_label).arg(help_text));
1018 			ui.selectionHelpLabel->show();
1019 		}
1020 		else
1021 		{
1022 			ui.selectionHelpLabel->hide();
1023 		}
1024 		ui.propertyEditAscendPathButton->setVisible(property_model->GetTargetPathStackSize() >= 1);
1025 		is_array = property_model->IsArray();
1026 		if (is_array)
1027 		{
1028 			bool is_readonly = property_model->IsTargetReadonly();
1029 			ui.arrayAddButton->setDisabled(is_readonly);
1030 			ui.arrayRemoveButton->setDisabled(is_readonly || ui.propertyTable->selectionModel()->selectedRows().empty());
1031 		}
1032 		ui.propertyTable->setEnabled(true);
1033 		::Console.EditCursor.ValidateSelection();
1034 	}
1035 	ui.arrayAddButton->setVisible(is_array);
1036 	ui.arrayRemoveButton->setVisible(is_array);
1037 	// Function update in script combo box
1038 	if (force_function_update)
1039 	{
1040 		auto suggestions = ::Console.GetScriptSuggestions(rSelection.GetObject(), C4Console::MRU_Object);
1041 		SetComboItems(ui.propertyInputBox, suggestions);
1042 	}
1043 }
1044 
ReInitDefinitions()1045 void C4ConsoleGUIState::ReInitDefinitions()
1046 {
1047 	if (definition_list_model) definition_list_model->ReInit();
1048 	// This also affects the object list
1049 	if (object_list_model) object_list_model->Invalidate();
1050 }
1051 
OnCreatorSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)1052 void C4ConsoleGUIState::OnCreatorSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
1053 {
1054 	if (is_object_selection_updating || !definition_list_model) return; // only process callbacks from users interacting with widget
1055 	// Forward to EditCursor
1056 	C4Def *def;
1057 	auto deselected_indexes = deselected.indexes();
1058 	for (const QModelIndex &item : deselected_indexes)
1059 		if ((def = definition_list_model->GetDefByModelIndex(item)))
1060 			::Console.EditCursor.RemoveFromSelection(def);
1061 	auto selected_indexes = selected.indexes();
1062 	for (const QModelIndex &item : selected_indexes)
1063 		if ((def = definition_list_model->GetDefByModelIndex(item)))
1064 			::Console.EditCursor.AddToSelection(def);
1065 	::Console.EditCursor.OnSelectionChanged(true);
1066 	// Switching to def selection mode: Remove any non-defs from selection
1067 	if (!selected.empty())
1068 	{
1069 		ui.objectListView->selectionModel()->clearSelection();
1070 		// ...and switch to creator mode
1071 		::Console.EditCursor.SetMode(C4CNS_ModeCreateObject);
1072 	}
1073 }
1074 
OnObjectListSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)1075 void C4ConsoleGUIState::OnObjectListSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
1076 {
1077 	if (is_object_selection_updating) return; // only process callbacks from users interacting with widget
1078 	// Forward to EditCursor
1079 	C4PropList *p;
1080 	for (const QModelIndex &item : deselected.indexes())
1081 		if ((p = object_list_model->GetItemByModelIndex(item)))
1082 			::Console.EditCursor.RemoveFromSelection(p);
1083 	for (const QModelIndex &item : selected.indexes())
1084 		if ((p = object_list_model->GetItemByModelIndex(item)))
1085 			::Console.EditCursor.AddToSelection(p);
1086 	::Console.EditCursor.OnSelectionChanged(true);
1087 	// Switching to object/effect selection mode: Remove any non-objects/effects from selection
1088 	if (!selected.empty())
1089 	{
1090 		ui.creatorTreeView->selectionModel()->clearSelection();
1091 		// ...and switch to editing mode
1092 		::Console.EditCursor.SetMode(C4CNS_ModeEdit);
1093 	}
1094 }
1095 
SetObjectSelection(class C4EditCursorSelection & rSelection)1096 void C4ConsoleGUIState::SetObjectSelection(class C4EditCursorSelection &rSelection)
1097 {
1098 	if (!window.get()) return;
1099 	// Callback from EditCursor when selection was changed e.g. from viewport
1100 	// Reflect selection change in object and definition view
1101 	C4Def *creator_def = ::Console.EditCursor.GetCreatorDef();
1102 	++is_object_selection_updating;
1103 	ui.objectListView->selectionModel()->clearSelection();
1104 	ui.creatorTreeView->selectionModel()->clearSelection();
1105 	QModelIndex last_idx_obj, last_idx_def, creator_idx;
1106 	for (C4Value &v : rSelection)
1107 	{
1108 		C4PropList *p = v.getPropList();
1109 		if (!p) continue;
1110 		C4Def *def = p->GetDef();
1111 		if (def && !p->GetObject())
1112 		{
1113 
1114 			QModelIndex idx = definition_list_model->GetModelIndexByItem(def);
1115 			if (idx.isValid())
1116 			{
1117 				ui.creatorTreeView->selectionModel()->select(idx, QItemSelectionModel::Select);
1118 				last_idx_def = idx;
1119 				if (def == creator_def) creator_idx = idx;
1120 			}
1121 		}
1122 		else
1123 		{
1124 			QModelIndex idx = object_list_model->GetModelIndexByItem(v.getPropList());
1125 			if (idx.isValid())
1126 			{
1127 				ui.objectListView->selectionModel()->select(idx, QItemSelectionModel::Select);
1128 				last_idx_obj = idx;
1129 			}
1130 		}
1131 	}
1132 	if (last_idx_obj.isValid()) ui.objectListView->scrollTo(last_idx_obj);
1133 	if (last_idx_def.isValid()) ui.creatorTreeView->scrollTo(last_idx_def);
1134 	else if (::Console.EditCursor.GetMode() == C4CNS_ModeCreateObject)
1135 	{
1136 		// Switch away from creator tool if user selected a non-definition
1137 		::Console.EditCursor.SetMode(C4CNS_ModeEdit);
1138 	}
1139 	// Sync creator selection
1140 	if (creator_idx.isValid()) ui.creatorTreeView->selectionModel()->select(creator_idx, QItemSelectionModel::Current);
1141 	--is_object_selection_updating;
1142 }
1143 
OnCreatorCurrentChanged(const QModelIndex & current,const QModelIndex & previous)1144 void C4ConsoleGUIState::OnCreatorCurrentChanged(const QModelIndex & current, const QModelIndex & previous)
1145 {
1146 	// A new definition was selected from the creator definition view
1147 	// Reflect in selection and auto-switch to creation mode if necessery
1148 	if (!definition_list_model) return;
1149 	C4Def *new_def = definition_list_model->GetDefByModelIndex(current);
1150 	//if (new_def) ::Console.EditCursor.SetMode(C4CNS_ModeCreateObject); - done by selection change
1151 	::Console.EditCursor.SetCreatorDef(new_def); // set or clear def in EditCursor
1152 }
1153 
CreateNewScenario(StdStrBuf * out_filename,bool * out_host_as_network)1154 bool C4ConsoleGUIState::CreateNewScenario(StdStrBuf *out_filename, bool *out_host_as_network)
1155 {
1156 	// Show dialogue
1157 	std::unique_ptr<C4ConsoleQtNewScenarioDlg> dlg(new C4ConsoleQtNewScenarioDlg(window.get()));
1158 	if (!dlg->exec()) return false;
1159 	// Dlg said OK! Scenario created
1160 	out_filename->Copy(dlg->GetFilename());
1161 	*out_host_as_network = dlg->IsHostAsNetwork();
1162 	return true;
1163 }
1164 
InitWelcomeScreen()1165 void C4ConsoleGUIState::InitWelcomeScreen()
1166 {
1167 	// Init links
1168 	ui.welcomeNewLabel->setText(QString(R"(<a href="new">%1</a>)").arg(ui.welcomeNewLabel->text()));
1169 	ui.welcomeOpenLabel->setText(QString(R"(<a href="open">%1</a>)").arg(ui.welcomeOpenLabel->text()));
1170 	ui.welcomeExploreUserPathLabel->setText(QString(R"(<a href="exploreuserpath">%1</a>)").arg(ui.welcomeExploreUserPathLabel->text()));
1171 	// Recently opened scenarios
1172 	bool any_file = false;
1173 	int recent_idx = ui.welcomeScrollLayout->indexOf(ui.welcomeRecentLabel);
1174 	for (auto filename : ::Config.Developer.RecentlyEditedSzenarios)
1175 	{
1176 		if (*filename && ::ItemExists(filename))
1177 		{
1178 			StdStrBuf basename(GetFilename(filename), true);
1179 			if (basename == C4CFN_ScenarioCore)
1180 			{
1181 				// If a Scenario.txt was opened, use the enclosing .ocs name
1182 				basename.Copy(filename, strlen(filename) - basename.getLength());
1183 				int32_t len = basename.getLength();
1184 				while (len && (basename.getData()[len - 1] == DirectorySeparator || basename.getData()[len - 1] == AltDirectorySeparator))
1185 					basename.SetLength(--len);
1186 				StdStrBuf base_folder_name(GetFilename(basename.getData()), true);
1187 				basename.Take(base_folder_name);
1188 			}
1189 			RemoveExtension(&basename);
1190 			QLabel *link = new QLabel(ui.welcomeScrollAreaWidgetContents);
1191 			ui.welcomeScrollLayout->insertWidget(++recent_idx, link);
1192 			link->setIndent(ui.welcomeOpenLabel->indent());
1193 			link->setTextInteractionFlags(ui.welcomeOpenLabel->textInteractionFlags());
1194 			link->setText(QString(R"(<a href="open:%1">%2</a>)").arg(filename).arg(basename.getData())); // let's hope file names never contain "
1195 			any_file = true;
1196 			window->connect(link, SIGNAL(linkActivated(QString)), window.get(), SLOT(WelcomeLinkActivated(QString)));
1197 		}
1198 	}
1199 	if (!any_file) ui.welcomeRecentLabel->hide();
1200 }
1201 
ShowWelcomeScreen()1202 void C4ConsoleGUIState::ShowWelcomeScreen()
1203 {
1204 	viewport_area->addDockWidget(Qt::BottomDockWidgetArea, ui.welcomeDockWidget);
1205 }
1206 
HideWelcomeScreen()1207 void C4ConsoleGUIState::HideWelcomeScreen()
1208 {
1209 	ui.welcomeDockWidget->close();
1210 }
1211 
ClearGamePointers()1212 void C4ConsoleGUIState::ClearGamePointers()
1213 {
1214 	if (property_delegate_factory) property_delegate_factory->ClearDelegates();
1215 }
1216 
UpdateActionObject(C4Object * new_action_object)1217 void C4ConsoleGUIState::UpdateActionObject(C4Object *new_action_object)
1218 {
1219 	// No change? Do not recreate buttons then because it may interfere with their usage
1220 	C4Object *prev_object = action_object.getObj();
1221 	if (new_action_object && prev_object == new_action_object) return;
1222 	action_object = C4VObj(new_action_object);
1223 	// Clear old action buttons
1224 	int32_t i = ui.objectActionPanel->count();
1225 	while (i--)
1226 	{
1227 		ui.objectActionPanel->itemAt(i)->widget()->deleteLater();
1228 	}
1229 	// Create new buttons
1230 	// Actions are defined as properties in a local proplist called EditorActions
1231 	if (!new_action_object) return;
1232 	C4PropList *editor_actions_list = new_action_object->GetPropertyPropList(P_EditorActions);
1233 	if (!editor_actions_list) return;
1234 	auto new_properties = editor_actions_list->GetSortedProperties(nullptr);
1235 	int row = 0, column = 0;
1236 	for (C4String *action_def_id : new_properties)
1237 	{
1238 		// Get action definition proplist
1239 		C4Value action_def_val;
1240 		if (!editor_actions_list->GetPropertyByS(action_def_id, &action_def_val))
1241 		{
1242 			// property disappeared (cannot happen)
1243 			continue;
1244 		}
1245 		C4PropList *action_def = action_def_val.getPropList();
1246 		if (!action_def)
1247 		{
1248 			// property is of wrong type (can happen; scripter error)
1249 			continue;
1250 		}
1251 		// Get action name
1252 		C4String *action_name = action_def->GetPropertyStr(P_Name);
1253 		if (!action_name)
1254 		{
1255 			// Fallback to identifier for unnamed actions
1256 			action_name = action_def_id;
1257 		}
1258 		// Get action help
1259 		QString action_help;
1260 		C4String *action_help_s = action_def->GetPropertyStr(P_EditorHelp);
1261 		if (action_help_s)
1262 		{
1263 			action_help = QString(action_help_s->GetCStr()).replace('|', '\n');
1264 		}
1265 		// Script command to execute
1266 		C4RefCntPointer<C4String> script_command = action_def->GetPropertyStr(P_Command);
1267 		int32_t object_number = new_action_object->Number;
1268 		// Create action button
1269 		QPushButton *btn = new QPushButton(action_name->GetCStr(), window.get());
1270 		if (!action_help.isEmpty()) btn->setToolTip(action_help);
1271 		if (script_command)
1272 		{
1273 			bool select_returned_object = action_def->GetPropertyBool(P_Select);
1274 			btn->connect(btn, &QPushButton::pressed, btn, [script_command, object_number, select_returned_object]()
1275 			{
1276 				// Action execution. Replace %player% by first local player.
1277 				StdStrBuf script_command_cpy(script_command->GetData(), true);
1278 				C4Player *local_player = ::Players.GetLocalByIndex(0);
1279 				int32_t local_player_number = local_player ? local_player->Number : NO_OWNER;
1280 				script_command_cpy.Replace("%player%", FormatString("%d", (int)local_player_number).getData());
1281 				::Console.EditCursor.EMControl(CID_Script, new C4ControlScript(script_command_cpy.getData(), object_number, false, select_returned_object));
1282 			});
1283 		}
1284 		ui.objectActionPanel->addWidget(btn, row, column);
1285 		if (++column >= 3)
1286 		{
1287 			column = 0;
1288 			++row;
1289 		}
1290 	}
1291 }
1292