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