1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2021, The LegacyClonk 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/* Handles viewport editing in console mode */
18
19#include <C4EditCursor.h>
20
21#include <C4Console.h>
22#include <C4Object.h>
23#include <C4Application.h>
24#include <C4Random.h>
25#include <C4Wrappers.h>
26
27#ifdef _WIN32
28#include "StdStringEncodingConverter.h"
29#include "res/engine_resource.h"
30#endif
31
32#ifdef WITH_DEVELOPER_MODE
33#include <gtk/gtk.h>
34#endif
35
36C4EditCursor::C4EditCursor()
37{
38 Default();
39}
40
41C4EditCursor::~C4EditCursor()
42{
43 Clear();
44}
45
46void C4EditCursor::Execute()
47{
48 // alt check
49 bool fAltIsDown = Application.IsAltDown();
50 if (fAltIsDown != fAltWasDown) if (fAltWasDown = fAltIsDown) AltDown(); else AltUp();
51 // drawing
52 switch (Mode)
53 {
54 case C4CNS_ModeEdit:
55 // Hold selection
56 if (Hold)
57 EMMoveObject(eAction: EMMO_Move, tx: 0, ty: 0, pTargetObj: nullptr, pObjs: &Selection);
58 break;
59
60 case C4CNS_ModeDraw:
61 switch (Console.ToolsDlg.Tool)
62 {
63 case C4TLS_Fill:
64 if (Hold) if (!Game.HaltCount) if (Console.Editing) ApplyToolFill();
65 break;
66 }
67 break;
68 }
69 // selection update
70 if (fSelectionChanged)
71 {
72 fSelectionChanged = false;
73 UpdateStatusBar();
74 Console.PropertyDlg.Update(rSelection&: Selection);
75 Console.ObjectListDlg.Update(rSelection&: Selection);
76 }
77}
78
79bool C4EditCursor::Init()
80{
81#ifdef _WIN32
82 if (!(hMenu = LoadMenu(Application.hInstance, MAKEINTRESOURCE(IDR_CONTEXTMENUS))))
83 return false;
84#else // _WIN32
85#ifdef WITH_DEVELOPER_MODE
86 menuContext = gtk_menu_new();
87
88 itemDelete = gtk_menu_item_new_with_label(label: LoadResStrGtk(id: C4ResStrTableKey::IDS_MNU_DELETE).c_str());
89 itemDuplicate = gtk_menu_item_new_with_label(label: LoadResStrGtk(id: C4ResStrTableKey::IDS_MNU_DUPLICATE).c_str());
90 itemGrabContents = gtk_menu_item_new_with_label(label: LoadResStrGtk(id: C4ResStrTableKey::IDS_MNU_CONTENTS).c_str());
91 itemProperties = gtk_menu_item_new_with_label(label: ""); // Set dynamically in DoContextMenu
92
93 gtk_menu_shell_append(GTK_MENU_SHELL(menuContext), child: itemDelete);
94 gtk_menu_shell_append(GTK_MENU_SHELL(menuContext), child: itemDuplicate);
95 gtk_menu_shell_append(GTK_MENU_SHELL(menuContext), child: itemGrabContents);
96 gtk_menu_shell_append(GTK_MENU_SHELL(menuContext), GTK_WIDGET(gtk_separator_menu_item_new()));
97 gtk_menu_shell_append(GTK_MENU_SHELL(menuContext), child: itemProperties);
98
99 g_signal_connect(G_OBJECT(itemDelete), "activate", G_CALLBACK(OnDelete), this);
100 g_signal_connect(G_OBJECT(itemDuplicate), "activate", G_CALLBACK(OnDuplicate), this);
101 g_signal_connect(G_OBJECT(itemGrabContents), "activate", G_CALLBACK(OnGrabContents), this);
102 g_signal_connect(G_OBJECT(itemProperties), "activate", G_CALLBACK(OnProperties), this);
103
104 gtk_widget_show_all(widget: menuContext);
105#endif // WITH_DEVELOPER_MODe
106#endif // _WIN32
107 Console.UpdateModeCtrls(iMode: Mode);
108
109 return true;
110}
111
112void C4EditCursor::ClearPointers(C4Object *pObj)
113{
114 if (Target == pObj) Target = nullptr;
115 if (Selection.ClearPointers(pObj))
116 OnSelectionChanged();
117}
118
119bool C4EditCursor::Move(int32_t iX, int32_t iY, uint16_t wKeyFlags)
120{
121 // Offset movement
122 int32_t xoff = iX - X; int32_t yoff = iY - Y; X = iX; Y = iY;
123
124 switch (Mode)
125 {
126 case C4CNS_ModeEdit:
127 // Hold
128 if (!DragFrame && Hold)
129 {
130 MoveSelection(iXOff: xoff, iYOff: yoff);
131 UpdateDropTarget(wKeyFlags);
132 }
133 // Update target
134 // Shift always indicates a target outside the current selection
135 else
136 {
137 Target = ((wKeyFlags & MK_SHIFT) && Selection.Last) ? Selection.Last->Obj : nullptr;
138 do
139 {
140 Target = Game.FindObject(id: 0, iX: X, iY: Y, iWdt: 0, iHgt: 0, ocf: OCF_NotContained, szAction: nullptr, pActionTarget: nullptr, pExclude: nullptr, pContainer: nullptr, iOwner: ANY_OWNER, pFindNext: Target);
141 } while ((wKeyFlags & MK_SHIFT) && Target && Selection.GetLink(pObj: Target));
142 }
143 break;
144
145 case C4CNS_ModeDraw:
146 switch (Console.ToolsDlg.Tool)
147 {
148 case C4TLS_Brush:
149 if (Hold) ApplyToolBrush();
150 break;
151 case C4TLS_Line: case C4TLS_Rect:
152 break;
153 }
154 break;
155 }
156
157 // Update
158 UpdateStatusBar();
159 return true;
160}
161
162bool C4EditCursor::UpdateStatusBar()
163{
164 std::string text;
165 switch (Mode)
166 {
167 case C4CNS_ModePlay:
168 if (Game.MouseControl.GetCaption())
169 {
170 std::string caption{Game.MouseControl.GetCaption()};
171 text = std::move(caption).substr(pos: 0, n: caption.find(c: '|'));
172 }
173 break;
174
175 case C4CNS_ModeEdit:
176 text = std::format(fmt: "{}/{} ({})", args&: X, args&: Y, args: (Target ? (Target->GetName()) : LoadResStr(id: C4ResStrTableKey::IDS_CNS_NOTHING)));
177 break;
178
179 case C4CNS_ModeDraw:
180 text = std::format(fmt: "{}/{} ({})", args&: X, args&: Y, args: (MatValid(mat: GBackMat(x: X, y: Y)) ? Game.Material.Map[GBackMat(x: X, y: Y)].Name : LoadResStr(id: C4ResStrTableKey::IDS_CNS_NOTHING)));
181 break;
182 }
183 return Console.UpdateCursorBar(szCursor: text.c_str());
184}
185
186void C4EditCursor::OnSelectionChanged()
187{
188 fSelectionChanged = true;
189}
190
191bool C4EditCursor::LeftButtonDown(bool fControl)
192{
193 // Hold
194 Hold = true;
195
196 switch (Mode)
197 {
198 case C4CNS_ModeEdit:
199 if (fControl)
200 {
201 // Toggle target
202 if (Target)
203 if (!Selection.Remove(pObj: Target))
204 Selection.Add(nObj: Target, eSort: C4ObjectList::stNone);
205 }
206 else
207 {
208 // Click on unselected: select single
209 if (Target && !Selection.GetLink(pObj: Target))
210 {
211 Selection.Clear(); Selection.Add(nObj: Target, eSort: C4ObjectList::stNone);
212 }
213 // Click on nothing: drag frame
214 if (!Target)
215 {
216 Selection.Clear(); DragFrame = true; X2 = X; Y2 = Y;
217 }
218 }
219 break;
220
221 case C4CNS_ModeDraw:
222 switch (Console.ToolsDlg.Tool)
223 {
224 case C4TLS_Brush: ApplyToolBrush(); break;
225 case C4TLS_Line: DragLine = true; X2 = X; Y2 = Y; break;
226 case C4TLS_Rect: DragFrame = true; X2 = X; Y2 = Y; break;
227 case C4TLS_Fill:
228 if (Game.HaltCount)
229 {
230 Hold = false; Console.Message(szMessage: LoadResStr(id: C4ResStrTableKey::IDS_CNS_FILLNOHALT)); return false;
231 }
232 break;
233 case C4TLS_Picker: ApplyToolPicker(); break;
234 }
235 break;
236 }
237
238 DropTarget = nullptr;
239
240 OnSelectionChanged();
241 return true;
242}
243
244bool C4EditCursor::RightButtonDown(bool fControl)
245{
246 switch (Mode)
247 {
248 case C4CNS_ModeEdit:
249 if (!fControl)
250 {
251 // Check whether cursor is on anything in the selection
252 bool fCursorIsOnSelection = false;
253 for (C4ObjectLink *pLnk = Selection.First; pLnk; pLnk = pLnk->Next)
254 if (pLnk->Obj->At(ctx: X, cty: Y))
255 {
256 fCursorIsOnSelection = true;
257 break;
258 }
259 if (!fCursorIsOnSelection)
260 {
261 // Click on unselected
262 if (Target && !Selection.GetLink(pObj: Target))
263 {
264 Selection.Clear(); Selection.Add(nObj: Target, eSort: C4ObjectList::stNone);
265 }
266 // Click on nothing
267 if (!Target) Selection.Clear();
268 }
269 }
270 break;
271 }
272
273 OnSelectionChanged();
274 return true;
275}
276
277bool C4EditCursor::LeftButtonUp()
278{
279 // Finish edit/tool
280 switch (Mode)
281 {
282 case C4CNS_ModeEdit:
283 if (DragFrame) FrameSelection();
284 if (DropTarget) PutContents();
285 break;
286
287 case C4CNS_ModeDraw:
288 switch (Console.ToolsDlg.Tool)
289 {
290 case C4TLS_Line:
291 if (DragLine) ApplyToolLine();
292 break;
293 case C4TLS_Rect:
294 if (DragFrame) ApplyToolRect();
295 break;
296 }
297 break;
298 }
299
300 // Release
301 Hold = false;
302 DragFrame = false;
303 DragLine = false;
304 DropTarget = nullptr;
305 // Update
306 UpdateStatusBar();
307 return true;
308}
309
310#ifdef _WIN32
311
312bool SetMenuItemEnable(HMENU hMenu, WORD id, bool fEnable)
313{
314 return EnableMenuItem(hMenu, id, MF_BYCOMMAND | MF_ENABLED | (fEnable ? 0 : MF_GRAYED));
315}
316
317bool SetMenuItemText(HMENU hMenu, WORD id, const char *szText)
318{
319 std::wstring text{StdStringEncodingConverter::WinAcpToUtf16(szText)};
320 MENUITEMINFO minfo{};
321 minfo.cbSize = sizeof(minfo);
322 minfo.fMask = MIIM_ID | MIIM_TYPE | MIIM_DATA;
323 minfo.fType = MFT_STRING;
324 minfo.wID = id;
325 minfo.dwTypeData = text.data();
326 minfo.cch = checked_cast<UINT>(text.size());
327 return SetMenuItemInfo(hMenu, id, FALSE, &minfo);
328}
329
330#endif
331
332bool C4EditCursor::RightButtonUp()
333{
334 Target = nullptr;
335
336 DoContextMenu();
337
338 // Update
339 UpdateStatusBar();
340 return true;
341}
342
343void C4EditCursor::MiddleButtonUp()
344{
345 if (Hold) return;
346
347 ApplyToolPicker();
348}
349
350bool C4EditCursor::Delete()
351{
352 if (!EditingOK()) return false;
353 EMMoveObject(eAction: EMMO_Remove, tx: 0, ty: 0, pTargetObj: nullptr, pObjs: &Selection);
354 if (Game.Control.isCtrlHost())
355 {
356 OnSelectionChanged();
357 }
358 return true;
359}
360
361bool C4EditCursor::OpenPropTools()
362{
363 switch (Mode)
364 {
365 case C4CNS_ModeEdit: case C4CNS_ModePlay:
366 Console.PropertyDlg.Open();
367 Console.PropertyDlg.Update(rSelection&: Selection);
368 break;
369 case C4CNS_ModeDraw:
370 Console.ToolsDlg.Open();
371 break;
372 }
373 return true;
374}
375
376bool C4EditCursor::Duplicate()
377{
378 EMMoveObject(eAction: EMMO_Duplicate, tx: 0, ty: 0, pTargetObj: nullptr, pObjs: &Selection);
379 return true;
380}
381
382void C4EditCursor::Draw(C4FacetEx &cgo)
383{
384 // Draw selection marks
385 C4Object *cobj; C4ObjectLink *clnk; C4Facet frame;
386 for (clnk = Selection.First; clnk && (cobj = clnk->Obj); clnk = clnk->Next)
387 {
388 // target pos (parallax)
389 int32_t cotx = cgo.TargetX, coty = cgo.TargetY; cobj->TargetPos(riTx&: cotx, riTy&: coty, fctViewport: cgo);
390 frame.Set(nsfc: cgo.Surface,
391 nx: cobj->x + cobj->Shape.x + cgo.X - cotx,
392 ny: cobj->y + cobj->Shape.y + cgo.Y - coty,
393 nwdt: cobj->Shape.Wdt,
394 nhgt: cobj->Shape.Hgt);
395 DrawSelectMark(cgo&: frame);
396 // highlight selection if shift is pressed
397 if (Application.IsShiftDown())
398 {
399 uint32_t dwOldMod = cobj->ColorMod;
400 uint32_t dwOldBlitMode = cobj->BlitMode;
401 cobj->ColorMod = 0xffffff;
402 cobj->BlitMode = C4GFXBLIT_CLRSFC_MOD2 | C4GFXBLIT_ADDITIVE;
403 cobj->Draw(cgo, iByPlayer: -1);
404 cobj->DrawTopFace(cgo, iByPlayer: -1);
405 cobj->ColorMod = dwOldMod;
406 cobj->BlitMode = dwOldBlitMode;
407 }
408 }
409 // Draw drag frame
410 if (DragFrame)
411 Application.DDraw->DrawFrame(sfcDest: cgo.Surface, x1: (std::min)(a: X, b: X2) + cgo.X - cgo.TargetX, y1: (std::min)(a: Y, b: Y2) + cgo.Y - cgo.TargetY, x2: (std::max)(a: X, b: X2) + cgo.X - cgo.TargetX, y2: (std::max)(a: Y, b: Y2) + cgo.Y - cgo.TargetY, col: CWhite);
412 // Draw drag line
413 if (DragLine)
414 Application.DDraw->DrawLine(sfcTarget: cgo.Surface, x1: X + cgo.X - cgo.TargetX, y1: Y + cgo.Y - cgo.TargetY, x2: X2 + cgo.X - cgo.TargetX, y2: Y2 + cgo.Y - cgo.TargetY, byCol: CWhite);
415 // Draw drop target
416 if (DropTarget)
417 Game.GraphicsResource.fctDropTarget.Draw(sfcTarget: cgo.Surface,
418 iX: DropTarget->x + cgo.X - cgo.TargetX - Game.GraphicsResource.fctDropTarget.Wdt / 2,
419 iY: DropTarget->y + DropTarget->Shape.y + cgo.Y - cgo.TargetY - Game.GraphicsResource.fctDropTarget.Hgt);
420}
421
422void C4EditCursor::DrawSelectMark(C4Facet &cgo)
423{
424 if ((cgo.Wdt < 1) || (cgo.Hgt < 1)) return;
425
426 if (!cgo.Surface) return;
427
428 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X), ty: static_cast<float>(cgo.Y), dwCol: 0xFFFFFF);
429 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X + 1), ty: static_cast<float>(cgo.Y), dwCol: 0xFFFFFF);
430 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X), ty: static_cast<float>(cgo.Y + 1), dwCol: 0xFFFFFF);
431
432 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X), ty: static_cast<float>(cgo.Y + cgo.Hgt - 1), dwCol: 0xFFFFFF);
433 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X + 1), ty: static_cast<float>(cgo.Y + cgo.Hgt - 1), dwCol: 0xFFFFFF);
434 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X), ty: static_cast<float>(cgo.Y + cgo.Hgt - 2), dwCol: 0xFFFFFF);
435
436 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X + cgo.Wdt - 1), ty: static_cast<float>(cgo.Y), dwCol: 0xFFFFFF);
437 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X + cgo.Wdt - 2), ty: static_cast<float>(cgo.Y), dwCol: 0xFFFFFF);
438 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X + cgo.Wdt - 1), ty: static_cast<float>(cgo.Y + 1), dwCol: 0xFFFFFF);
439
440 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X + cgo.Wdt - 1), ty: static_cast<float>(cgo.Y + cgo.Hgt - 1), dwCol: 0xFFFFFF);
441 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X + cgo.Wdt - 2), ty: static_cast<float>(cgo.Y + cgo.Hgt - 1), dwCol: 0xFFFFFF);
442 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: static_cast<float>(cgo.X + cgo.Wdt - 1), ty: static_cast<float>(cgo.Y + cgo.Hgt - 2), dwCol: 0xFFFFFF);
443}
444
445void C4EditCursor::MoveSelection(int32_t iXOff, int32_t iYOff)
446{
447 EMMoveObject(eAction: EMMO_Move, tx: iXOff, ty: iYOff, pTargetObj: nullptr, pObjs: &Selection);
448}
449
450void C4EditCursor::FrameSelection()
451{
452 Selection.Clear();
453 C4Object *cobj; C4ObjectLink *clnk;
454 for (clnk = Game.Objects.First; clnk && (cobj = clnk->Obj); clnk = clnk->Next)
455 if (cobj->Status) if (cobj->OCF & OCF_NotContained)
456 {
457 if (Inside(ival: cobj->x, lbound: (std::min)(a: X, b: X2), rbound: (std::max)(a: X, b: X2)) && Inside(ival: cobj->y, lbound: (std::min)(a: Y, b: Y2), rbound: (std::max)(a: Y, b: Y2)))
458 Selection.Add(nObj: cobj, eSort: C4ObjectList::stNone);
459 }
460 Console.PropertyDlg.Update(rSelection&: Selection);
461}
462
463bool C4EditCursor::In(const char *szText)
464{
465 EMMoveObject(eAction: EMMO_Script, tx: 0, ty: 0, pTargetObj: nullptr, pObjs: &Selection, szScript: szText);
466 return true;
467}
468
469void C4EditCursor::Default()
470{
471 fAltWasDown = false;
472 Mode = C4CNS_ModePlay;
473 X = Y = X2 = Y2 = 0;
474 Target = DropTarget = nullptr;
475#ifdef _WIN32
476 hMenu = nullptr;
477#endif
478 Hold = DragFrame = DragLine = false;
479 Selection.Default();
480 fSelectionChanged = false;
481}
482
483void C4EditCursor::Clear()
484{
485#ifdef _WIN32
486 if (hMenu) DestroyMenu(hMenu); hMenu = nullptr;
487#endif
488 Selection.Clear();
489}
490
491bool C4EditCursor::SetMode(int32_t iMode)
492{
493 // Store focus
494#ifdef _WIN32
495 HWND hFocus = GetFocus();
496#endif
497 // Update console buttons (always)
498 Console.UpdateModeCtrls(iMode);
499 // No change
500 if (iMode == Mode) return true;
501 // Set mode
502 Mode = iMode;
503 // Update prop tools by mode
504 bool fOpenPropTools = false;
505 switch (Mode)
506 {
507 case C4CNS_ModeEdit: case C4CNS_ModePlay:
508 if (Console.ToolsDlg.Active || Console.PropertyDlg.Active) fOpenPropTools = true;
509 Console.ToolsDlg.Clear();
510 if (fOpenPropTools) OpenPropTools();
511 break;
512
513 case C4CNS_ModeDraw:
514 if (Console.ToolsDlg.Active || Console.PropertyDlg.Active) fOpenPropTools = true;
515 Console.PropertyDlg.Clear();
516 if (fOpenPropTools) OpenPropTools();
517 break;
518 }
519 // Update cursor
520 if (Mode == C4CNS_ModePlay) Game.MouseControl.ShowCursor();
521 else Game.MouseControl.HideCursor();
522 // Restore focus
523#ifdef _WIN32
524 SetFocus(hFocus);
525#endif
526 // Done
527 return true;
528}
529
530bool C4EditCursor::ToggleMode()
531{
532 if (!EditingOK()) return false;
533
534 // Step through modes
535 int32_t iNewMode;
536 switch (Mode)
537 {
538 case C4CNS_ModePlay: iNewMode = C4CNS_ModeEdit; break;
539 case C4CNS_ModeEdit: iNewMode = C4CNS_ModeDraw; break;
540 case C4CNS_ModeDraw: iNewMode = C4CNS_ModePlay; break;
541 default: iNewMode = C4CNS_ModePlay; break;
542 }
543
544 // Set new mode
545 SetMode(iNewMode);
546
547 return true;
548}
549
550void C4EditCursor::ApplyToolBrush()
551{
552 if (!EditingOK()) return;
553 C4ToolsDlg *pTools = &Console.ToolsDlg;
554 // execute/send control
555 EMControl(eCtrlType: CID_EMDrawTool, pCtrl: new C4ControlEMDrawTool(EMDT_Brush, Game.Landscape.Mode, X, Y, 0, 0, pTools->Grade, !!pTools->ModeIFT, pTools->Material, pTools->Texture));
556}
557
558void C4EditCursor::ApplyToolLine()
559{
560 if (!EditingOK()) return;
561 C4ToolsDlg *pTools = &Console.ToolsDlg;
562 // execute/send control
563 EMControl(eCtrlType: CID_EMDrawTool, pCtrl: new C4ControlEMDrawTool(EMDT_Line, Game.Landscape.Mode, X, Y, X2, Y2, pTools->Grade, !!pTools->ModeIFT, pTools->Material, pTools->Texture));
564}
565
566void C4EditCursor::ApplyToolRect()
567{
568 if (!EditingOK()) return;
569 C4ToolsDlg *pTools = &Console.ToolsDlg;
570 // execute/send control
571 EMControl(eCtrlType: CID_EMDrawTool, pCtrl: new C4ControlEMDrawTool(EMDT_Rect, Game.Landscape.Mode, X, Y, X2, Y2, pTools->Grade, !!pTools->ModeIFT, pTools->Material, pTools->Texture));
572}
573
574void C4EditCursor::ApplyToolFill()
575{
576 if (!EditingOK()) return;
577 C4ToolsDlg *pTools = &Console.ToolsDlg;
578 // execute/send control
579 EMControl(eCtrlType: CID_EMDrawTool, pCtrl: new C4ControlEMDrawTool(EMDT_Fill, Game.Landscape.Mode, X, Y, 0, Y2, pTools->Grade, false, pTools->Material));
580}
581
582bool C4EditCursor::DoContextMenu()
583{
584 bool fObjectSelected = Selection.ObjectCount();
585#ifdef _WIN32
586 POINT point; GetCursorPos(&point);
587 HMENU hContext = GetSubMenu(hMenu, 0);
588 SetMenuItemEnable(hContext, IDM_VIEWPORT_DELETE, fObjectSelected && Console.Editing);
589 SetMenuItemEnable(hContext, IDM_VIEWPORT_DUPLICATE, fObjectSelected && Console.Editing);
590 SetMenuItemEnable(hContext, IDM_VIEWPORT_CONTENTS, fObjectSelected && Selection.GetObject()->Contents.ObjectCount() && Console.Editing);
591 SetMenuItemEnable(hContext, IDM_VIEWPORT_PROPERTIES, Mode != C4CNS_ModePlay);
592 SetMenuItemText(hContext, IDM_VIEWPORT_DELETE, LoadResStr(C4ResStrTableKey::IDS_MNU_DELETE));
593 SetMenuItemText(hContext, IDM_VIEWPORT_DUPLICATE, LoadResStr(C4ResStrTableKey::IDS_MNU_DUPLICATE));
594 SetMenuItemText(hContext, IDM_VIEWPORT_CONTENTS, LoadResStr(C4ResStrTableKey::IDS_MNU_CONTENTS));
595 SetMenuItemText(hContext, IDM_VIEWPORT_PROPERTIES, LoadResStrChoice(Mode == C4CNS_ModeEdit, C4ResStrTableKey::IDS_CNS_PROPERTIES, C4ResStrTableKey::IDS_CNS_TOOLS));
596 int32_t iItem = TrackPopupMenu(
597 hContext,
598 TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_NONOTIFY,
599 point.x, point.y, 0,
600 Console.hWindow,
601 nullptr);
602 switch (iItem)
603 {
604 case IDM_VIEWPORT_DELETE: Delete(); break;
605 case IDM_VIEWPORT_DUPLICATE: Duplicate(); break;
606 case IDM_VIEWPORT_CONTENTS: GrabContents(); break;
607 case IDM_VIEWPORT_PROPERTIES: OpenPropTools(); break;
608 }
609#elif defined(WITH_DEVELOPER_MODE)
610 gtk_widget_set_sensitive(widget: itemDelete, sensitive: fObjectSelected && Console.Editing);
611 gtk_widget_set_sensitive(widget: itemDuplicate, sensitive: fObjectSelected && Console.Editing);
612 gtk_widget_set_sensitive(widget: itemGrabContents, sensitive: fObjectSelected && Selection.GetObject()->Contents.ObjectCount() && Console.Editing);
613 gtk_widget_set_sensitive(widget: itemProperties, sensitive: Mode != C4CNS_ModePlay);
614
615 GtkLabel *label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(itemProperties)));
616 if (Mode == C4CNS_ModeEdit)
617 {
618 gtk_label_set_text(label, str: LoadResStrGtk(id: C4ResStrTableKey::IDS_CNS_PROPERTIES).c_str());
619 }
620 else
621 {
622 gtk_label_set_text(label, str: LoadResStrGtk(id: C4ResStrTableKey::IDS_CNS_TOOLS).c_str());
623 }
624
625 gtk_menu_popup_at_pointer(GTK_MENU(menuContext), trigger_event: nullptr);
626#endif
627 return true;
628}
629
630void C4EditCursor::GrabContents()
631{
632 // Set selection
633 C4Object *pFrom;
634 if (!(pFrom = Selection.GetObject())) return;
635 Selection.Copy(rList: pFrom->Contents);
636 Console.PropertyDlg.Update(rSelection&: Selection);
637 Hold = true;
638
639 // Exit all objects
640 EMMoveObject(eAction: EMMO_Exit, tx: 0, ty: 0, pTargetObj: nullptr, pObjs: &Selection);
641}
642
643void C4EditCursor::UpdateDropTarget(uint16_t wKeyFlags)
644{
645 C4Object *cobj; C4ObjectLink *clnk;
646
647 DropTarget = nullptr;
648
649 if (wKeyFlags & MK_CONTROL)
650 if (Selection.GetObject())
651 for (clnk = Game.Objects.First; clnk && (cobj = clnk->Obj); clnk = clnk->Next)
652 if (cobj->Status)
653 if (!cobj->Contained)
654 if (Inside<int32_t>(ival: X - (cobj->x + cobj->Shape.x), lbound: 0, rbound: cobj->Shape.Wdt - 1))
655 if (Inside<int32_t>(ival: Y - (cobj->y + cobj->Shape.y), lbound: 0, rbound: cobj->Shape.Hgt - 1))
656 if (!Selection.GetLink(pObj: cobj))
657 {
658 DropTarget = cobj; break;
659 }
660}
661
662void C4EditCursor::PutContents()
663{
664 if (!DropTarget) return;
665 EMMoveObject(eAction: EMMO_Enter, tx: 0, ty: 0, pTargetObj: DropTarget, pObjs: &Selection);
666}
667
668C4Object *C4EditCursor::GetTarget()
669{
670 return Target;
671}
672
673bool C4EditCursor::EditingOK()
674{
675 if (!Console.Editing)
676 {
677 Hold = false;
678 Console.Message(szMessage: LoadResStr(id: C4ResStrTableKey::IDS_CNS_NONETEDIT));
679 return false;
680 }
681 return true;
682}
683
684int32_t C4EditCursor::GetMode()
685{
686 return Mode;
687}
688
689void C4EditCursor::ApplyToolPicker()
690{
691 int32_t iMaterial;
692 uint8_t byIndex;
693 switch (Game.Landscape.Mode)
694 {
695 case C4LSC_Static:
696 // Material-texture from map
697 if (byIndex = Game.Landscape.GetMapIndex(iX: X / Game.Landscape.MapZoom, iY: Y / Game.Landscape.MapZoom))
698 {
699 const C4TexMapEntry *pTex = Game.TextureMap.GetEntry(iIndex: byIndex & (IFT - 1));
700 if (pTex)
701 {
702 Console.ToolsDlg.SelectMaterial(szMaterial: pTex->GetMaterialName());
703 Console.ToolsDlg.SelectTexture(szTexture: pTex->GetTextureName());
704 Console.ToolsDlg.SetIFT(byIndex & ~(IFT - 1));
705 }
706 }
707 else
708 Console.ToolsDlg.SelectMaterial(C4TLS_MatSky);
709 break;
710 case C4LSC_Exact:
711 // Material only from landscape
712 if (MatValid(mat: iMaterial = GBackMat(x: X, y: Y)))
713 {
714 Console.ToolsDlg.SelectMaterial(szMaterial: Game.Material.Map[iMaterial].Name);
715 Console.ToolsDlg.SetIFT(GBackIFT(x: X, y: Y));
716 }
717 else
718 Console.ToolsDlg.SelectMaterial(C4TLS_MatSky);
719 break;
720 }
721 Hold = false;
722}
723
724void C4EditCursor::EMMoveObject(C4ControlEMObjectAction eAction, int32_t tx, int32_t ty, C4Object *pTargetObj, const C4ObjectList *pObjs, const char *szScript)
725{
726 // construct object list
727 int32_t iObjCnt = 0; int32_t *pObjIDs = nullptr;
728 if (pObjs && (iObjCnt = pObjs->ObjectCount()))
729 {
730 pObjIDs = new int32_t[iObjCnt];
731 // fill
732 int32_t i = 0;
733 for (C4ObjectLink *pLnk = pObjs->First; pLnk; pLnk = pLnk->Next, i++)
734 if (pLnk->Obj && pLnk->Obj->Status)
735 pObjIDs[i] = pLnk->Obj->Number;
736 }
737
738 // execute control
739 EMControl(eCtrlType: CID_EMMoveObj, pCtrl: new C4ControlEMMoveObject(eAction, tx, ty, pTargetObj, iObjCnt, pObjIDs, szScript, Config.Developer.ConsoleScriptStrictness));
740}
741
742void C4EditCursor::EMControl(C4PacketType eCtrlType, C4ControlPacket *pCtrl)
743{
744 Game.Control.DoInput(eCtrlType, pPkt: pCtrl, eDelivery: CDT_Decide);
745}
746
747#ifdef WITH_DEVELOPER_MODE
748
749// GTK+ callbacks
750
751void C4EditCursor::OnDelete(GtkWidget *widget, gpointer data)
752{
753 static_cast<C4EditCursor *>(data)->Delete();
754}
755
756void C4EditCursor::OnDuplicate(GtkWidget *widget, gpointer data)
757{
758 static_cast<C4EditCursor *>(data)->Duplicate();
759}
760
761void C4EditCursor::OnGrabContents(GtkWidget *widget, gpointer data)
762{
763 static_cast<C4EditCursor *>(data)->GrabContents();
764}
765
766void C4EditCursor::OnProperties(GtkWidget *widget, gpointer data)
767{
768 static_cast<C4EditCursor *>(data)->OpenPropTools();
769}
770
771#endif
772
773bool C4EditCursor::AltDown()
774{
775 // alt only has an effect in draw mode (picker)
776 if (Mode == C4CNS_ModeDraw)
777 {
778 Console.ToolsDlg.SetAlternateTool();
779 }
780 // key not processed - allow further usages of Alt
781 return false;
782}
783
784bool C4EditCursor::AltUp()
785{
786 if (Mode == C4CNS_ModeDraw)
787 {
788 Console.ToolsDlg.ResetAlternateTool();
789 }
790 // key not processed - allow further usages of Alt
791 return false;
792}
793