OpenClonk
C4ConsoleQtShapes.cpp
Go to the documentation of this file.
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 /* Editable shapes in the viewports (like e.g. AI guard range rectangles) */
18 
19 #include "C4Include.h"
20 #include "graphics/C4FacetEx.h"
21 #include "graphics/C4Draw.h"
22 #include "object/C4Object.h"
23 // See C4ConsoleQt.cpp on include order
24 #include "editor/C4Console.h"
27 #include "editor/C4ConsoleQtPropListViewer.h" // for C4PropertyPath
28 
29 /* Generic shape */
30 
31 C4ConsoleQtShape::C4ConsoleQtShape(C4Object *for_obj, C4PropList *props, const class C4PropertyDelegateShape *parent_delegate, class C4ConsoleQtShapes *shape_list)
32  : is_relative(true), dragging_border(-1), selected_border(-1), border_color(0xffff0000), parent_delegate(parent_delegate), shape_list(shape_list)
33 {
34  rel_obj.SetPropList(for_obj);
35  if (props)
36  {
37  is_relative = props->GetPropertyBool(P_Relative, is_relative);
38  border_color = props->GetPropertyInt(P_Color) | 0xff000000;
39  properties = C4VPropList(props);
40  }
41 }
42 
43 uint32_t C4ConsoleQtShape::GetBorderColor(int32_t border_index, bool dragging_border_is_bitmask, uint32_t default_color) const
44 {
45  // Return shape color, or dragged border color if index is the border currently being dragged
46  if (IsDragging())
47  if ((dragging_border == border_index) || (dragging_border_is_bitmask && (dragging_border & border_index)))
48  return 0xffffffff;
49  return default_color ? default_color : border_color;
50 }
51 
52 int32_t C4ConsoleQtShape::AbsX(int32_t rel_x) const
53 {
54  if (is_relative)
55  {
56  C4Object *obj = rel_obj.getObj();
57  if (obj) rel_x += obj->GetX();
58  }
59  return rel_x;
60 }
61 
62 int32_t C4ConsoleQtShape::AbsY(int32_t rel_y) const
63 {
64  if (is_relative)
65  {
66  C4Object *obj = rel_obj.getObj();
67  if (obj) rel_y += obj->GetY();
68  }
69  return rel_y;
70 }
71 
72 void C4ConsoleQtShape::StopDragging()
73 {
74  // Reset drag state and emit signal to send updated value
75  dragging_border = -1;
76  emit ShapeDragged();
77 }
78 
79 bool C4ConsoleQtShape::Select(int32_t border)
80 {
81  selected_border = border;
82  emit BorderSelectionChanged();
83  return true;
84 }
85 
86 void C4ConsoleQtShape::ResetSelection()
87 {
88  selected_border = -1;
89  emit BorderSelectionChanged();
90 }
91 
92 
93 /* Rectangular shape*/
94 
95 C4ConsoleQtRect::C4ConsoleQtRect(C4Object *for_obj, C4PropList *props, const class C4PropertyDelegateShape *parent_delegate, class C4ConsoleQtShapes *shape_list)
96  : C4ConsoleQtShape(for_obj, props, parent_delegate, shape_list), left(0), top(0), right(10), bottom(10), store_as_proplist(false), properties_lowercase(false)
97 {
98  // Def props
99  if (props)
100  {
101  C4String *storage = props->GetPropertyStr(P_Storage);
102  if (storage)
103  {
104  if (storage == &::Strings.P[P_proplist])
105  properties_lowercase = store_as_proplist = true;
106  else if (storage == &::Strings.P[P_Proplist])
107  store_as_proplist = true;
108  }
109  }
110 }
111 
112 bool C4ConsoleQtRect::IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border, bool shift_down, bool ctrl_down)
113 {
114  // Current border pos
115  int32_t left = AbsX(this->left), top = AbsY(this->top);
116  int32_t right = AbsX(this->right), bottom = AbsY(this->bottom);
117  // Distance to each border
118  int32_t dleft = Abs<int32_t>(left - x);
119  int32_t dtop = Abs<int32_t>(top - y);
120  int32_t dright = Abs<int32_t>(right - x);
121  int32_t dbottom = Abs<int32_t>(bottom - y);
122  // In box at all?
123  if (x < left - hit_range || y < top - hit_range || x > right + hit_range || y > bottom + hit_range)
124  return false;
125  // Border hit?
126  bool hit_left = (dleft <= hit_range && dleft < dright);
127  bool hit_top = (dtop <= hit_range && dtop < dbottom);
128  bool hit_right = (!hit_left && dright <= hit_range);
129  bool hit_bottom = (!hit_top && dbottom <= hit_range);
130  // Compose cursor and drag border
131  int32_t idrag_border = (hit_left * CNAT_Left) + (hit_top * CNAT_Top) + (hit_right * CNAT_Right) + (hit_bottom * CNAT_Bottom);
132  if (idrag_border) *drag_border = idrag_border;
133  if (hit_left || hit_right)
134  if (hit_top || hit_bottom)
135  *drag_cursor = (hit_left == hit_top) ? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor;
136  else
137  *drag_cursor = Qt::SizeHorCursor;
138  else if (hit_top || hit_bottom)
139  *drag_cursor = Qt::SizeVerCursor;
140  return !!idrag_border;
141 }
142 
143 void C4ConsoleQtRect::Draw(class C4TargetFacet &cgo, float line_width)
144 {
145  float left = float(AbsX(this->left)) + cgo.X - cgo.TargetX;
146  float top = float(AbsY(this->top)) + cgo.Y - cgo.TargetY;
147  float right = float(AbsX(this->right)) + cgo.X - cgo.TargetX;
148  float bottom = float(AbsY(this->bottom)) + cgo.Y - cgo.TargetY;
149  pDraw->DrawLineDw(cgo.Surface, left, top, right, top, GetBorderColor(CNAT_Top, true), line_width);
150  pDraw->DrawLineDw(cgo.Surface, right, top, right, bottom, GetBorderColor(CNAT_Right, true), line_width);
151  pDraw->DrawLineDw(cgo.Surface, right, bottom, left, bottom, GetBorderColor(CNAT_Bottom, true), line_width);
152  pDraw->DrawLineDw(cgo.Surface, left, bottom, left, top, GetBorderColor(CNAT_Left, true), line_width);
153 }
154 
155 void C4ConsoleQtRect::Drag(int32_t x, int32_t y, int32_t dx, int32_t dy, int32_t hit_range, Qt::CursorShape *drag_cursor)
156 {
157  if (dragging_border & CNAT_Left) left += dx;
158  if (dragging_border & CNAT_Top) top += dy;
159  if (dragging_border & CNAT_Right) right += dx;
160  if (dragging_border & CNAT_Bottom) bottom += dy;
161  if (left > right) std::swap(left, right);
162  if (top > bottom) std::swap(top, bottom);
163 }
164 
165 void C4ConsoleQtRect::SetValue(const C4Value &val)
166 {
167  // Expect rect to be given as proplist with properties X, Y, Wdt, Hgt or array with elements [left,top,width,height]
168  if (store_as_proplist)
169  {
170  C4PropList *vprops = val.getPropList();
171  if (vprops)
172  {
173  left = vprops->GetPropertyInt(properties_lowercase ? P_x : P_X);
174  top = vprops->GetPropertyInt(properties_lowercase ? P_y : P_Y);
175  right = left + vprops->GetPropertyInt(properties_lowercase ? P_wdt : P_Wdt) - 1;
176  bottom = top + vprops->GetPropertyInt(properties_lowercase ? P_hgt : P_Hgt) - 1;
177  }
178  }
179  else
180  {
181  C4ValueArray *varr = val.getArray();
182  if (varr && varr->GetSize() >= 4)
183  {
184  left = varr->GetItem(0).getInt();
185  top = varr->GetItem(1).getInt();
186  right = left + varr->GetItem(2).getInt() - 1; // right/bottom borders are drawn inclusively
187  bottom = top + varr->GetItem(3).getInt() - 1;
188  }
189  }
190 }
191 
192 C4Value C4ConsoleQtRect::GetValue() const
193 {
194  // Return array or proplist: Convert left/top/right/bottom (inclusive) to left/top/width/height
195  if (store_as_proplist)
196  {
197  C4PropList *pos_proplist = new C4PropListScript();
198  pos_proplist->SetProperty(properties_lowercase ? P_x : P_X, C4VInt(left));
199  pos_proplist->SetProperty(properties_lowercase ? P_y : P_Y, C4VInt(top));
200  pos_proplist->SetProperty(properties_lowercase ? P_wdt : P_Wdt, C4VInt(right - left + 1));
201  pos_proplist->SetProperty(properties_lowercase ? P_hgt : P_Hgt, C4VInt(bottom - top + 1));
202  return C4VPropList(pos_proplist);
203  }
204  else
205  {
206  C4ValueArray *pos_array = new C4ValueArray(4);
207  pos_array->SetItem(0, C4VInt(left));
208  pos_array->SetItem(1, C4VInt(top));
209  pos_array->SetItem(2, C4VInt(right - left + 1));
210  pos_array->SetItem(3, C4VInt(bottom - top + 1));
211  return C4VArray(pos_array);
212  }
213 }
214 
215 
216 /* Circle shape */
217 
218 C4ConsoleQtCircle::C4ConsoleQtCircle(class C4Object *for_obj, C4PropList *props, const class C4PropertyDelegateShape *parent_delegate, class C4ConsoleQtShapes *shape_list)
219  : C4ConsoleQtShape(for_obj, props, parent_delegate, shape_list), radius(10), cx(0), cy(0), can_move_center(false)
220 {
221  if (props)
222  {
223  can_move_center = props->GetPropertyBool(P_CanMoveCenter);
224  }
225 }
226 
227 bool C4ConsoleQtCircle::IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border, bool shift_down, bool ctrl_down)
228 {
229  // Get relative circle center pos
230  x -= AbsX(cx);
231  y -= AbsY(cy);
232  int32_t r = x*x + y*y;
233  // Is on circle border? (Higher priority than center to allow resizing circle from 0 radius)
234  if (Inside<int32_t>(r, (radius - hit_range)*(radius - hit_range), (radius + hit_range)*(radius + hit_range)))
235  {
236  // Cursor by position on 60 deg circle segments
237  if (x * 58 / 100 / (y+!y)) // tan(30) ~= 0.58
238  *drag_cursor = Qt::CursorShape::SizeHorCursor;
239  else if (y * 58 / 100 / (x+!x))
240  *drag_cursor = Qt::CursorShape::SizeVerCursor;
241  else if (x*y > 0)
242  *drag_cursor = Qt::CursorShape::SizeFDiagCursor;
243  else
244  *drag_cursor = Qt::CursorShape::SizeBDiagCursor;
245  *drag_border = 0;
246  return true;
247  }
248  // Circle center?
249  if (can_move_center && r <= hit_range*hit_range)
250  {
251  *drag_cursor = Qt::CursorShape::SizeAllCursor;
252  *drag_border = 1;
253  return true;
254  }
255  return false;
256 }
257 
258 void C4ConsoleQtCircle::Draw(class C4TargetFacet &cgo, float line_width)
259 {
260  // Circle
261  pDraw->DrawCircleDw(cgo.Surface, AbsX(cx) + cgo.X - cgo.TargetX, AbsY(cy) + cgo.Y - cgo.TargetY, radius, GetBorderColor(0, false), line_width);
262  // Center if moveable
263  if (can_move_center)
264  pDraw->DrawCircleDw(cgo.Surface, AbsX(cx) + cgo.X - cgo.TargetX, AbsY(cy) + cgo.Y - cgo.TargetY, line_width*3, GetBorderColor(1, false), line_width);
265 }
266 
267 void C4ConsoleQtCircle::C4ConsoleQtCircle::Drag(int32_t x, int32_t y, int32_t dx, int32_t dy, int32_t hit_range, Qt::CursorShape *drag_cursor)
268 {
269  if (dragging_border == 0)
270  {
271  x -= AbsX(cx);
272  y -= AbsY(cy);
273  radius = int32_t(sqrt(double(x*x + y*y)));
274  }
275  else if (dragging_border == 1)
276  {
277  cx += dx;
278  cy += dy;
279  }
280 }
281 
282 void C4ConsoleQtCircle::SetValue(const C4Value &val)
283 {
284  // If center is moveable, expect value as [radius, center_x, center_y]
285  // Otherwise just radius
286  if (can_move_center)
287  {
288  C4ValueArray *aval = val.getArray();
289  if (aval && aval->GetSize() == 3)
290  {
291  radius = aval->GetItem(0).getInt();
292  cx = aval->GetItem(1).getInt();
293  cy = aval->GetItem(2).getInt();
294  }
295  }
296  else
297  {
298  radius = val.getInt();
299  }
300 }
301 
302 C4Value C4ConsoleQtCircle::GetValue() const
303 {
304  // Return single value for non-center-adjustable circles; return [radius, cx, cy] otherwise
305  if (can_move_center)
306  {
307  C4ValueArray *pos_array = new C4ValueArray(3);
308  pos_array->SetItem(0, C4VInt(radius));
309  pos_array->SetItem(1, C4VInt(cx));
310  pos_array->SetItem(2, C4VInt(cy));
311  return C4VArray(pos_array);
312  }
313  else
314  {
315  return C4VInt(radius);
316  }
317 }
318 
319 
320 /* Point shape */
321 
322 C4ConsoleQtPoint::C4ConsoleQtPoint(class C4Object *for_obj, C4PropList *props, const class C4PropertyDelegateShape *parent_delegate, class C4ConsoleQtShapes *shape_list)
323  : C4ConsoleQtShape(for_obj, props, parent_delegate, shape_list), cx(0), cy(0)
324 {
325  if (props)
326  {
327  horizontal_fix = props->GetPropertyBool(P_HorizontalFix);
328  vertical_fix = props->GetPropertyBool(P_VerticalFix);
329  }
330 }
331 
332 bool C4ConsoleQtPoint::IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border, bool shift_down, bool ctrl_down)
333 {
334  // Get relative circle center pos
335  x -= AbsX(cx);
336  y -= AbsY(cy);
337  int32_t r = x*x + y*y;
338  // Hits point?
339  if (r <= hit_range*hit_range*6)
340  {
341  if (horizontal_fix && vertical_fix) *drag_cursor = Qt::CursorShape::ForbiddenCursor;
342  if (horizontal_fix && !vertical_fix) *drag_cursor = Qt::CursorShape::SizeVerCursor;
343  if (!horizontal_fix && vertical_fix) *drag_cursor = Qt::CursorShape::SizeHorCursor;
344  if (!horizontal_fix && !vertical_fix) *drag_cursor = Qt::CursorShape::SizeAllCursor;
345  *drag_border = 0;
346  return true;
347  }
348  return false;
349 }
350 
351 void C4ConsoleQtPoint::Draw(class C4TargetFacet &cgo, float line_width)
352 {
353  // Circle with cross inside
354  uint32_t clr = GetBorderColor(0, false);
355  float d = line_width * 3;
356  float dc = sqrtf(2) * d;
357  int32_t x = AbsX(cx) + cgo.X - cgo.TargetX;
358  int32_t y = AbsY(cy) + cgo.Y - cgo.TargetY;
359  if (horizontal_fix && !vertical_fix)
360  {
361  pDraw->DrawLineDw(cgo.Surface, x, y - d, x, y + d, clr, line_width);
362  pDraw->DrawLineDw(cgo.Surface, x - d / 2, y - d, x + d / 2, y - d, clr, line_width);
363  pDraw->DrawLineDw(cgo.Surface, x - d / 2, y + d, x + d / 2, y + d, clr, line_width);
364  }
365  else if (!horizontal_fix && vertical_fix)
366  {
367  pDraw->DrawLineDw(cgo.Surface, x - d, y, x + d, y, clr, line_width);
368  pDraw->DrawLineDw(cgo.Surface, x - d, y - d / 2, x - d, y + d / 2, clr, line_width);
369  pDraw->DrawLineDw(cgo.Surface, x + d, y - d / 2, x + d, y + d / 2, clr, line_width);
370  }
371  else
372  {
373  pDraw->DrawLineDw(cgo.Surface, x - d, y - d, x + d, y + d, clr, line_width);
374  if (!horizontal_fix)
375  {
376  pDraw->DrawLineDw(cgo.Surface, x - d, y + d, x + d, y - d, clr, line_width);
377  }
378  pDraw->DrawCircleDw(cgo.Surface, x, y, dc, clr, line_width);
379  }
380 }
381 
382 void C4ConsoleQtPoint::Drag(int32_t x, int32_t y, int32_t dx, int32_t dy, int32_t hit_range, Qt::CursorShape *drag_cursor)
383 {
384  if (!horizontal_fix) cx += dx;
385  if (!vertical_fix) cy += dy;
386 }
387 
388 void C4ConsoleQtPoint::SetValue(const C4Value &val)
389 {
390  // Expect value as [x,y]
391  C4ValueArray *aval = val.getArray();
392  if (aval && aval->GetSize() == 2)
393  {
394  cx = aval->GetItem(0).getInt();
395  cy = aval->GetItem(1).getInt();
396  }
397 }
398 
399 C4Value C4ConsoleQtPoint::GetValue() const
400 {
401  // Return [cx, cy]
402  C4ValueArray *pos_array = new C4ValueArray(2);
403  pos_array->SetItem(0, C4VInt(cx));
404  pos_array->SetItem(1, C4VInt(cy));
405  return C4VArray(pos_array);
406 }
407 
408 
409 /* Graph */
410 
411 bool C4ConsoleQtGraph::Edge::connects_to(int32_t vertex_index) const
412 {
413  // Check if this edge connects to given vertex
414  return vertex_indices[0] == vertex_index || vertex_indices[1] == vertex_index;
415 }
416 
417 bool C4ConsoleQtGraph::Edge::connects_to(int32_t vertex_index, int32_t *idx) const
418 {
419  // Check if this edge connects to given vertex. If so, put vertex_index index into *idx
420  assert(idx);
421  if (vertex_indices[0] == vertex_index)
422  {
423  *idx = 0;
424  }
425  else if (vertex_indices[1] == vertex_index)
426  {
427  *idx = 1;
428  }
429  else
430  {
431  return false;
432  }
433  return true;
434 }
435 
436 
437 void C4ConsoleQtGraph::GraphData::SetVerticesValue(const C4ValueArray *vvertices)
438 {
439  vertices.clear();
440  if (vvertices)
441  {
442  vertices.reserve(vvertices->GetSize());
443  for (int32_t i = 0; i < vvertices->GetSize(); ++i)
444  {
445  Vertex vtx;
446  C4PropList *vvertex = vvertices->GetItem(i).getPropList();
447  if (!vvertex) continue;
448  vtx.x = vvertex->GetPropertyInt(P_X);
449  vtx.y = vvertex->GetPropertyInt(P_Y);
450  if (vvertex->HasProperty(&::Strings.P[P_Color])) vtx.color = vvertex->GetPropertyInt(P_Color) | 0xff000000;
451  vertices.push_back(vtx);
452  }
453  }
454 }
455 
456 void C4ConsoleQtGraph::GraphData::SetEdgesValue(const C4ValueArray *vedges)
457 {
458  edges.clear();
459  if (vedges)
460  {
461  edges.reserve(vedges->GetSize());
462  for (int32_t i = 0; i < vedges->GetSize(); ++i)
463  {
464  C4ValueArray *vedgevertices = nullptr;
465  C4PropList *vedgeprops = nullptr;
466  vedgeprops = vedges->GetItem(i).getPropList();
467  if (vedgeprops)
468  {
469  vedgevertices = vedgeprops->GetPropertyArray(P_Vertices);
470  }
471  if (vedgevertices && vedgevertices->GetSize() >= 2)
472  {
473  Edge edge;
474  edge.vertex_indices[0] = vedgevertices->GetItem(0).getInt();
475  edge.vertex_indices[1] = vedgevertices->GetItem(1).getInt();
476  // Ignore invalid edge definitions
477  if (edge.vertex_indices[0] < 0 || edge.vertex_indices[1] < 0) continue;
478  if (edge.vertex_indices[0] >= vertices.size() || edge.vertex_indices[1] >= vertices.size()) continue;
479  // Optional properties
480  if (vedgeprops->HasProperty(&::Strings.P[P_Color])) edge.color = vedgeprops->GetPropertyInt(P_Color) | 0xff000000;
481  edge.line_thickness = vedgeprops->GetPropertyInt(P_LineWidth, edge.line_thickness);
482  edges.push_back(edge);
483  }
484  }
485  }
486 }
487 
488 C4ValueArray *C4ConsoleQtGraph::GraphData::GetVerticesValue() const
489 {
490  // Store vertices
491  C4ValueArray *vvertices = new C4ValueArray();
492  vvertices->SetSize(vertices.size());
493  int32_t i = 0;
494  for (const Vertex &vtx : vertices)
495  {
496  C4Value valvtx;
497  C4PropList *vvtx = C4PropList::New();
498  vvtx->SetProperty(P_X, C4VInt(vtx.x));
499  vvtx->SetProperty(P_Y, C4VInt(vtx.y));
500  if (vtx.color) vvtx->SetProperty(P_Color, C4VInt(vtx.color & 0xffffff));
501  vvertices->SetItem(i++, C4VPropList(vvtx));
502  }
503  return vvertices;
504 }
505 
506 C4ValueArray *C4ConsoleQtGraph::GraphData::GetEdgesValue() const
507 {
508  C4ValueArray *vedges = new C4ValueArray();
509  vedges->SetSize(edges.size());
510  int32_t i = 0;
511  for (const Edge &edge : edges)
512  {
513  C4Value valedge;
514  C4ValueArray *vedge = new C4ValueArray(2);
515  vedge->SetItem(0, C4VInt(edge.vertex_indices[0]));
516  vedge->SetItem(1, C4VInt(edge.vertex_indices[1]));
517  C4PropList *vedgeprops = C4PropList::New();
518  vedgeprops->SetProperty(P_Vertices, C4VArray(vedge));
519  if (edge.color) vedgeprops->SetProperty(P_Color, C4VInt(edge.color & 0xffffff));
520  if (edge.line_thickness != 1) vedgeprops->SetProperty(P_LineWidth, C4VInt(edge.line_thickness));
521  vedges->SetItem(i++, C4VPropList(vedgeprops));
522  }
523  return vedges;
524 }
525 
526 void C4ConsoleQtGraph::GraphData::SetVertexPos(int32_t vertex_index, int32_t new_x, int32_t new_y)
527 {
528  // Validity check
529  if (vertex_index < 0 || vertex_index >= vertices.size()) return;
530  // Do change
531  vertices[vertex_index].x = new_x;
532  vertices[vertex_index].y = new_y;
533 }
534 
535 void C4ConsoleQtGraph::GraphData::EditEdge(int32_t edge_index, int32_t change_vertex_index, int32_t new_vertex_index)
536 {
537  // Validity check
538  if (edge_index < 0 || edge_index >= edges.size()) return;
539  if (new_vertex_index < 0 || new_vertex_index >= vertices.size()) return;
540  if (change_vertex_index < 0 || change_vertex_index > 1) return;
541  // No loopback edges
542  Edge &edge = edges[edge_index];
543  int32_t other_vertex_index = edge.vertex_indices[!change_vertex_index];
544  if (new_vertex_index == other_vertex_index) return;
545  // Do not allow duplicates
546  for (Edge &check_edge : edges)
547  {
548  if (&check_edge != &edge)
549  {
550  if (check_edge.connects_to(new_vertex_index) && check_edge.connects_to(other_vertex_index)) return;
551  }
552  }
553  // Perform change
554  edge.vertex_indices[change_vertex_index] = new_vertex_index;
555 }
556 
557 void C4ConsoleQtGraph::GraphData::InsertEdgeBefore(int32_t insert_edge_index, int32_t vertex1, int32_t vertex2)
558 {
559  // Validity check
560  if (insert_edge_index < 0 || insert_edge_index > edges.size()) return;
561  if (vertex1 < 0 || vertex1 >= vertices.size()) return;
562  if (vertex2 < 0 || vertex2 >= vertices.size()) return;
563  // Do not allow duplicates
564  for (Edge &check_edge : edges)
565  {
566  if (check_edge.connects_to(vertex1) && check_edge.connects_to(vertex2)) return;
567  }
568  // Insert edge at position in edge list
569  Edge new_edge;
570  new_edge.vertex_indices[0] = vertex1;
571  new_edge.vertex_indices[1] = vertex2;
572  edges.insert(edges.begin() + insert_edge_index, new_edge);
573 }
574 
575 void C4ConsoleQtGraph::GraphData::InsertVertexBefore(int32_t insert_vertex_index, int32_t x, int32_t y)
576 {
577  // Validity check
578  if (insert_vertex_index < 0 || insert_vertex_index > vertices.size()) return;
579  // Insert new vertex at desired position
580  Vertex new_vertex;
581  new_vertex.x = x;
582  new_vertex.y = y;
583  vertices.insert(vertices.begin() + insert_vertex_index, new_vertex);
584  // Update all edges pointing to vertices after this one
585  for (Edge &edge : edges)
586  {
587  for (int32_t &vertex_index : edge.vertex_indices)
588  {
589  if (vertex_index >= insert_vertex_index)
590  {
591  ++vertex_index;
592  }
593  }
594  }
595 }
596 
597 void C4ConsoleQtGraph::GraphData::RemoveEdge(int32_t edge_index)
598 {
599  // Validity check
600  if (edge_index < 0 || edge_index >= edges.size()) return;
601  // Kill the edge
602  edges.erase(edges.begin() + edge_index);
603 }
604 
605 void C4ConsoleQtGraph::GraphData::RemoveVertex(int32_t remove_vertex_index)
606 {
607  // Validity vheck
608  if (remove_vertex_index < 0 || remove_vertex_index >= vertices.size()) return;
609  // Always keep at least one vertex
610  if (vertices.size() == 1) return;
611  assert(remove_vertex_index >= 0 && remove_vertex_index < vertices.size() && vertices.size() >= 2);
612  // Remove all edges involving this vertex
613  auto rm_check = [remove_vertex_index](const Edge &edge) { return edge.connects_to(remove_vertex_index); };
614  edges.erase(std::remove_if(edges.begin(), edges.end(), rm_check), edges.end());
615  // Remove the vertex itself
616  vertices.erase(vertices.begin() + remove_vertex_index);
617  // Because vertex indices changed, update all edges that pointed to higher indices
618  for (Edge &edge : edges)
619  {
620  for (int32_t &vi : edge.vertex_indices)
621  {
622  if (vi > remove_vertex_index)
623  {
624  --vi;
625  }
626  }
627  }
628 }
629 
630 int32_t C4ConsoleQtGraph::GraphData::GetEdgeCountForVertex(int32_t vertex_index) const
631 {
632  // Count all edges that connect to the given vertex
633  auto count_check = [vertex_index](const Edge &edge) { return edge.connects_to(vertex_index); };
634  return std::count_if(edges.begin(), edges.end(), count_check);
635 }
636 
637 void C4ConsoleQtGraph::GraphData::EditGraphValue_SetVertexPos(C4ValueArray *vvertices, int32_t vertex_index, int32_t new_x, int32_t new_y)
638 {
639  // Validity check
640  if (vertex_index < 0 || vertex_index >= vvertices->GetSize()) return;
641  // Do change
642  C4PropList *vvertex = vvertices->GetItem(vertex_index).getPropList();
643  if (!vvertex || vvertex->IsFrozen()) return;
644  vvertex->SetProperty(P_X, C4VInt(new_x));
645  vvertex->SetProperty(P_Y, C4VInt(new_y));
646 }
647 
648 void C4ConsoleQtGraph::GraphData::EditGraphValue_EditEdge(C4ValueArray *vvertices, C4ValueArray *vedges, int32_t edge_index, int32_t change_vertex_index, int32_t new_vertex_index)
649 {
650  // Validity check
651  if (edge_index < 0 || edge_index >= vedges->GetSize()) return;
652  if (new_vertex_index < 0 || new_vertex_index >= vvertices->GetSize()) return;
653  if (change_vertex_index < 0 || change_vertex_index > 1) return;
654  C4PropList *edge = vedges->GetItem(edge_index).getPropList();
655  if (!edge) return;
656  C4ValueArray *edge_vertices = edge->GetPropertyArray(P_Vertices);
657  if (!edge_vertices || edge_vertices->IsFrozen()) return;
658  // No loopback edges
659  int32_t other_vertex_index = edge_vertices->GetItem(!change_vertex_index).getInt();
660  if (new_vertex_index == other_vertex_index) return;
661  // Do not allow duplicates
662  for (int32_t i = 0; i < vedges->GetSize(); ++i)
663  {
664  if (i != edge_index)
665  {
666  if (EditGraphValue_EdgeConnectsTo(vedges->GetItem(i).getPropList(), other_vertex_index) && EditGraphValue_EdgeConnectsTo(vedges->GetItem(i).getPropList(), new_vertex_index))
667  {
668  return;
669  }
670  }
671  }
672  // Perform change
673  edge_vertices->SetItem(change_vertex_index, C4VInt(new_vertex_index));
674 }
675 
676 void C4ConsoleQtGraph::GraphData::EditGraphValue_InsertEdgeBefore(C4ValueArray *vvertices, C4ValueArray *vedges, int32_t insert_edge_index, int32_t vertex1, int32_t vertex2)
677 {
678  // Validity check
679  if (insert_edge_index < 0 || insert_edge_index > vedges->GetSize()) return;
680  if (vertex1 < 0 || vertex1 >= vvertices->GetSize()) return;
681  // Do not allow duplicates
682  for (int32_t i = 0; i < vedges->GetSize(); ++i)
683  {
684  if (EditGraphValue_EdgeConnectsTo(vedges->GetItem(i).getPropList(), vertex1) && EditGraphValue_EdgeConnectsTo(vedges->GetItem(i).getPropList(), vertex2))
685  {
686  return;
687  }
688  }
689  // Construct new edge
690  C4PropList *new_edge = C4PropList::New();
691  C4ValueArray *new_edge_indices = new C4ValueArray(2);
692  new_edge_indices->SetItem(0, C4VInt(vertex1));
693  new_edge_indices->SetItem(1, C4VInt(vertex2));
694  new_edge->SetProperty(P_Vertices, C4VArray(new_edge_indices));
695  // Insert edge at position in edge list by moving other edges up
696  vedges->SetSize(vedges->GetSize() + 1);
697  for (int32_t i = vedges->GetSize() - 1; i > insert_edge_index; --i)
698  {
699  vedges->SetItem(i, vedges->GetItem(i - 1));
700  }
701  vedges->SetItem(insert_edge_index, C4VPropList(new_edge));
702 }
703 
704 void C4ConsoleQtGraph::GraphData::EditGraphValue_InsertVertexBefore(C4ValueArray *vvertices, C4ValueArray *vedges, int32_t insert_vertex_index, int32_t x, int32_t y)
705 {
706  // Validity check
707  if (insert_vertex_index < 0 || insert_vertex_index > vvertices->GetSize()) return;
708  // Construct new vertex
709  C4PropList *new_vertex = C4PropList::New();
710  new_vertex->SetProperty(P_X, C4VInt(x));
711  new_vertex->SetProperty(P_Y, C4VInt(y));
712  // Insert new vertex at desired position by moving other vertices up
713  vvertices->SetSize(vvertices->GetSize() + 1);
714  for (int32_t i = vvertices->GetSize() - 1; i > insert_vertex_index; --i)
715  {
716  vvertices->SetItem(i, vvertices->GetItem(i - 1));
717  }
718  vvertices->SetItem(insert_vertex_index, C4VPropList(new_vertex));
719  // Update all edges pointing to vertices after this one
720  if (vedges)
721  {
722  for (int32_t i = 0; i < vedges->GetSize(); ++i)
723  {
724  C4PropList *edge = vedges->GetItem(i).getPropList();
725  if (edge)
726  {
727  C4ValueArray *edge_vertices = edge->GetPropertyArray(P_Vertices);
728  if (edge_vertices && edge_vertices->GetSize() >= 2)
729  {
730  for (int32_t j = 0; j < 2; ++j)
731  {
732  int32_t vertex_index = edge_vertices->GetItem(j).getInt();
733  if (vertex_index >= insert_vertex_index)
734  {
735  edge_vertices->SetItem(j, C4VInt(vertex_index + 1));
736  }
737  }
738  }
739  }
740  }
741  }
742 }
743 
744 void C4ConsoleQtGraph::GraphData::EditGraphValue_RemoveEdge(C4ValueArray *vvertices, C4ValueArray *vedges, int32_t edge_index)
745 {
746  // Validity check
747  if (edge_index < 0 || edge_index >= vedges->GetSize()) return;
748  // Kill the edge
749  for (int32_t i = edge_index; i < vedges->GetSize() - 1; ++i)
750  {
751  vedges->SetItem(i, vedges->GetItem(i + 1));
752  }
753  vedges->SetSize(vedges->GetSize() - 1);
754 }
755 
756 void C4ConsoleQtGraph::GraphData::EditGraphValue_RemoveVertex(C4ValueArray *vvertices, C4ValueArray *vedges, int32_t remove_vertex_index)
757 {
758  // Validity vheck
759  if (remove_vertex_index < 0 || remove_vertex_index >= vvertices->GetSize()) return;
760  // Always keep at least one vertex
761  if (vvertices->GetSize() == 1) return;
762  // Remove the vertex itself
763  for (int32_t i = remove_vertex_index; i < vvertices->GetSize() - 1; ++i)
764  {
765  vvertices->SetItem(i, vvertices->GetItem(i + 1));
766  }
767  vvertices->SetSize(vvertices->GetSize() - 1);
768  // Edge updates
769  if (vedges)
770  {
771  for (int32_t edge_index = vedges->GetSize(); edge_index >= 0; --edge_index)
772  {
773  // Remove all edges involving this vertex and update vertices at higher index because they have been moved down
774  C4PropList *edge = vedges->GetItem(edge_index).getPropList();
775  if (edge)
776  {
777  C4ValueArray *edge_vertices = edge->GetPropertyArray(P_Vertices);
778  if (edge_vertices && edge_vertices->GetSize() >= 2)
779  {
780  for (int32_t j = 0; j < 2; ++j)
781  {
782  int32_t v = edge_vertices->GetItem(j).getInt();
783  if (v == remove_vertex_index)
784  {
785  EditGraphValue_RemoveEdge(vvertices, vedges, edge_index);
786  break;
787  }
788  else if (v > remove_vertex_index)
789  {
790  edge_vertices->SetItem(j, C4VInt(v - 1));
791  }
792  }
793  }
794  }
795  }
796  }
797 }
798 
799 bool C4ConsoleQtGraph::GraphData::EditGraphValue_EdgeConnectsTo(C4PropList *edge, int32_t vertex_index)
800 {
801  // Check if either side of the edge connects to given vertex
802  if (!edge) return false;
803  C4ValueArray *edge_vertices = edge->GetPropertyArray(P_Vertices);
804  if (!edge_vertices || edge_vertices->GetSize() < 2) return false;
805  return edge_vertices->GetItem(0).getInt() == vertex_index || edge_vertices->GetItem(1).getInt() == vertex_index;
806 }
807 
808 
809 C4ConsoleQtGraph::C4ConsoleQtGraph(C4Object *for_obj, C4PropList *props, const class C4PropertyDelegateShape *parent_delegate, class C4ConsoleQtShapes *shape_list)
810  : C4ConsoleQtShape(for_obj, props, parent_delegate, shape_list)
811 {
812  // Def props
813  if (props)
814  {
815  props->GetProperty(P_VertexDelegate, &vertex_delegate);
816  props->GetProperty(P_EdgeDelegate, &edge_delegate);
817  allow_vertex_selection = !!vertex_delegate.getPropList();
818  allow_edge_selection = !!edge_delegate.getPropList();
819  horizontal_fix = props->GetPropertyBool(P_HorizontalFix);
820  vertical_fix = props->GetPropertyBool(P_VerticalFix);
821  structure_fix = props->GetPropertyBool(P_StructureFix);
822  draw_arrows = props->GetPropertyBool(P_DrawArrows, draw_arrows);
823  }
824 }
825 
826 bool C4ConsoleQtGraph::IsHit(int32_t x, int32_t y, int32_t hit_range, Qt::CursorShape *drag_cursor, int32_t *drag_border, bool shift_down, bool ctrl_down)
827 {
828  // Check hit on vertices
829  int32_t i = 0, best_hit_range = hit_range*hit_range * 6*6;
830  bool has_hit = false;
831  for (const Vertex &vtx : graph.vertices)
832  {
833  int32_t dx = x - AbsX(vtx.x);
834  int32_t dy = y - AbsY(vtx.y);
835  int32_t r = dx*dx + dy*dy;
836  // Is close to vertex / closer than previous hit?
837  if (r <= best_hit_range)
838  {
839  if (IsVertexHit(i, drag_cursor, shift_down, ctrl_down))
840  {
841  *drag_border = VertexToDragBorder(i);
842  best_hit_range = r;
843  has_hit = true;
844  }
845  }
846  ++i;
847  }
848  // Check hit on edge if edge selection is possible
849  if (!has_hit && (allow_edge_selection || shift_down || ctrl_down))
850  {
851  best_hit_range = hit_range*hit_range;
852  i = 0;
853  for (const Edge &edge : graph.edges)
854  {
855  // Get affected vertices
856  assert(edge.vertex_indices[0] >= 0 && edge.vertex_indices[1] >= 0);
857  assert(edge.vertex_indices[0] < graph.vertices.size() && edge.vertex_indices[1] < graph.vertices.size());
858  const Vertex &v0 = graph.vertices[edge.vertex_indices[0]];
859  const Vertex &v1 = graph.vertices[edge.vertex_indices[1]];
860  // Relative edge pos
861  int32_t dx0 = x - AbsX(v0.x);
862  int32_t dy0 = y - AbsY(v0.y);
863  int32_t dx1 = v1.x - v0.x;
864  int32_t dy1 = v1.y - v0.y;
865  // Check if within line range
866  int32_t d = dx0 * dx1 + dy0 * dy1;
867  if (d > 0 && d < dx1 * dx1 + dy1 * dy1)
868  {
869  // Get squared distance from edge
870  d = dx1 * dy0 - dy1 * dx0;
871  d = d * d / (dx1 * dx1 + dy1 * dy1);
872  // In hit range?
873  if (d <= best_hit_range)
874  {
875  if (IsEdgeHit(i, drag_cursor, shift_down, ctrl_down))
876  *drag_border = EdgeToDragBorder(i);
877  best_hit_range = d;
878  has_hit = true;
879  }
880  }
881  ++i;
882  }
883  }
884  // Check if structure change is allowed
885  if (has_hit && (shift_down || ctrl_down) && structure_fix)
886  {
887  *drag_cursor = Qt::CursorShape::ForbiddenCursor;
888  }
889  else if (*drag_cursor == Qt::CursorShape::SizeAllCursor)
890  {
891  // Adjust cursor for constrained movement
892  if (horizontal_fix && vertical_fix)
893  {
894  *drag_cursor = Qt::CursorShape::PointingHandCursor;
895  }
896  else if (horizontal_fix)
897  {
898  *drag_cursor = Qt::CursorShape::SizeVerCursor;
899  }
900  else if (vertical_fix)
901  {
902  *drag_cursor = Qt::CursorShape::SizeHorCursor;
903  }
904  }
905  return has_hit;
906 }
907 
908 bool C4ConsoleQtGraph::IsVertexHit(int32_t vertex_index, Qt::CursorShape *drag_cursor, bool shift_down, bool ctrl_down)
909 {
910  if (shift_down && !ctrl_down)
911  {
912  // Insert vertex here
913  *drag_cursor = Qt::CursorShape::DragCopyCursor;
914  }
915  else if (ctrl_down && !shift_down)
916  {
917  // Remove this vertex (unless it's the last, which cannot be removed)
918  if (graph.vertices.size() == 1)
919  {
920  *drag_cursor = Qt::CursorShape::ForbiddenCursor;
921  }
922  else
923  {
924  *drag_cursor = Qt::CursorShape::DragMoveCursor;
925  }
926  }
927  else
928  {
929  // Normal dragging
930  *drag_cursor = Qt::CursorShape::SizeAllCursor;
931  }
932  return true;
933 }
934 
935 bool C4ConsoleQtGraph::IsEdgeHit(int32_t edge_index, Qt::CursorShape *drag_cursor, bool shift_down, bool ctrl_down)
936 {
937  if (shift_down && !ctrl_down)
938  {
939  // Insert vertex here
940  *drag_cursor = Qt::CursorShape::DragCopyCursor;
941  }
942  else if (ctrl_down && !shift_down)
943  {
944  // Remove this edge
945  *drag_cursor = Qt::CursorShape::DragMoveCursor;
946  }
947  else if (allow_edge_selection)
948  {
949  // Normal selection
950  *drag_cursor = Qt::CursorShape::PointingHandCursor; // can select, but cannot move
951  }
952  else
953  {
954  // Nothing to do with the edge. Selection disabled.
955  return false;
956  }
957  return true;
958 }
959 
960 void C4ConsoleQtGraph::DrawEdge(class C4TargetFacet &cgo, const Vertex &v0, const Vertex &v1, uint32_t clr, float line_width, float edge_width, bool highlight)
961 {
962  float vx0 = AbsX(v0.x) + cgo.X - cgo.TargetX;
963  float vy0 = AbsY(v0.y) + cgo.Y - cgo.TargetY;
964  float vx1 = AbsX(v1.x) + cgo.X - cgo.TargetX;
965  float vy1 = AbsY(v1.y) + cgo.Y - cgo.TargetY;
966  float dx = v1.x - v0.x, dy = v1.y - v0.y;
967  float d = sqrt(dx*dx + dy*dy);
968  if (highlight)
969  {
970  // Selected edge is surrounded by a highlight
971  float ddx = dy / d * edge_width;
972  float ddy = dx / d * edge_width;
973  pDraw->DrawLineDw(cgo.Surface, vx0 + ddx, vy0 + ddy, vx1 + ddx, vy1 + ddy, 0xffffffff, edge_width);
974  pDraw->DrawLineDw(cgo.Surface, vx0 - ddx, vy0 - ddy, vx1 - ddx, vy1 - ddy, 0xffffffff, edge_width);
975  }
976  // Regular line draw
977  pDraw->DrawLineDw(cgo.Surface, vx0, vy0, vx1, vy1, clr, edge_width);
978  // Arrowheads
979  if (draw_arrows)
980  {
981  // Arrowhead points to outer rim of vertex circle if possible
982  float d_vtx = std::min<float>(d, line_width * 3 * sqrtf(2));
983  float arrx = vx1 - dx / d * d_vtx;
984  float arry = vy1 - dy / d * d_vtx;
985  float arrowhead_size = std::max<float>(2.0, std::min<float>(d / 2, 8.0)) * edge_width;
986  float ddx1 = (-dy / 2 - dx);
987  float ddy1 = (+dx / 2 - dy);
988  float d1 = sqrt(ddx1*ddx1 + ddy1*ddy1);
989  float ddx2 = (+dy / 2 - dx);
990  float ddy2 = (-dx / 2 - dy);
991  float d2 = sqrt(ddx2*ddx2 + ddy2*ddy2);
992  pDraw->DrawLineDw(cgo.Surface, arrx, arry, arrx + ddx1 * arrowhead_size / d1, arry + ddy1 * arrowhead_size / d1, clr, edge_width);
993  pDraw->DrawLineDw(cgo.Surface, arrx, arry, arrx + ddx2 * arrowhead_size / d2, arry + ddy2 * arrowhead_size / d2, clr, edge_width);
994  }
995 }
996 
997 void C4ConsoleQtGraph::Draw(class C4TargetFacet &cgo, float line_width)
998 {
999  // Draw edges as lines
1000  int32_t i = 0;
1001  for (const Edge &edge : graph.edges)
1002  {
1003  uint32_t clr = GetBorderColor(EdgeToDragBorder(i), false, edge.color);
1004  assert(edge.vertex_indices[0] >= 0 && edge.vertex_indices[1] >= 0);
1005  assert(edge.vertex_indices[0] < graph.vertices.size() && edge.vertex_indices[1] < graph.vertices.size());
1006  const Vertex &v0 = graph.vertices[edge.vertex_indices[0]];
1007  const Vertex &v1 = graph.vertices[edge.vertex_indices[1]];
1008  float edge_width = line_width * edge.line_thickness;
1009  bool highlight = (IsEdgeDrag(selected_border) && DragBorderToEdge(selected_border) == i);
1010  DrawEdge(cgo, v0, v1, clr, line_width, edge_width, highlight);
1011  ++i;
1012  }
1013  // Draw vertices as circles with cross inside
1014  i = 0;
1015  for (const Vertex &vtx : graph.vertices)
1016  {
1017  uint32_t clr = GetBorderColor(VertexToDragBorder(i), false, vtx.color);
1018  float d = line_width * 3;
1019  float dc = sqrtf(2) * d;
1020  int32_t x = AbsX(vtx.x) + cgo.X - cgo.TargetX;
1021  int32_t y = AbsY(vtx.y) + cgo.Y - cgo.TargetY;
1022  pDraw->DrawLineDw(cgo.Surface, x - d, y - d, x + d, y + d, clr, line_width);
1023  pDraw->DrawLineDw(cgo.Surface, x - d, y + d, x + d, y - d, clr, line_width);
1024  // Selected vertex is surrounded by a highlight
1025  if (IsVertexDrag(selected_border) && DragBorderToVertex(selected_border) == i)
1026  {
1027  pDraw->DrawCircleDw(cgo.Surface, x, y, dc + line_width, 0xffffffff, line_width);
1028  pDraw->DrawCircleDw(cgo.Surface, x, y, dc - line_width, 0xffffffff, line_width);
1029  }
1030  pDraw->DrawCircleDw(cgo.Surface, x, y, dc, clr, line_width);
1031  ++i;
1032  }
1033 }
1034 
1035 bool C4ConsoleQtGraph::StartDragging(int32_t *border, int32_t x, int32_t y, bool shift_down, bool ctrl_down)
1036 {
1037  assert(*border != -1);
1038  drag_snap_offset_x = drag_snap_offset_y = 0;
1039  drag_snapped = false;
1040  drag_snap_vertex = -1;
1041  drag_source_vertex_index = -1;
1042  if (shift_down && !ctrl_down && !structure_fix)
1043  {
1044  // Shift: Insert vertex
1045  if (IsEdgeDrag(*border))
1046  {
1047  // Insert on edge
1048  *border = dragging_border = VertexToDragBorder(InsertVertexOnEdge(DragBorderToEdge(*border), x - AbsX(), y - AbsY()));
1049  }
1050  else
1051  {
1052  // Insert from other vertex
1053  drag_source_vertex_index = DragBorderToVertex(*border);
1054  *border = dragging_border = VertexToDragBorder(InsertVertexOnVertex(drag_source_vertex_index, x - AbsX(), y - AbsY()));
1055  }
1056  // Start dragging
1057  return true;
1058  }
1059  else if (ctrl_down && !shift_down && !structure_fix)
1060  {
1061  // Ctrl: Delete vertex or edge
1062  if (IsEdgeDrag(*border))
1063  {
1064  // Delete edge
1065  RemoveEdge(DragBorderToEdge(*border));
1066  }
1067  else
1068  {
1069  // Remove vertex unless it is the last one
1070  // If there's only one vertex left, just keep it
1071  if (graph.vertices.size() > 1) RemoveVertex(DragBorderToVertex(*border), true);
1072  }
1073  // Notify script
1074  emit ShapeDragged();
1075  // Do not start dragging or selecting
1076  *border = -1;
1077  return false;
1078  }
1079  else if (!shift_down && !ctrl_down)
1080  {
1081  // Regular dragging of vertices
1082  if (IsVertexDrag(*border))
1083  {
1084  return C4ConsoleQtShape::StartDragging(border, x, y, shift_down, ctrl_down);
1085  }
1086  else
1087  {
1088  // No dragging of edges
1089  return false;
1090  }
1091  }
1092  else
1093  {
1094  // Unknown modifiers or trying to modify a structure-fixed graph
1095  return false;
1096  }
1097 }
1098 
1099 void C4ConsoleQtGraph::Drag(int32_t x, int32_t y, int32_t dx, int32_t dy, int32_t hit_range, Qt::CursorShape *drag_cursor)
1100 {
1101  // Dragging: Only vertices
1102  if (IsVertexDrag(dragging_border) && DragBorderToVertex(dragging_border) < graph.vertices.size())
1103  {
1104  int32_t dragged_vertex_index = DragBorderToVertex(dragging_border);
1105  Vertex &dragged_vertex = graph.vertices[dragged_vertex_index];
1106  // Regular vertex movement
1107  dx -= drag_snap_offset_x;
1108  dy -= drag_snap_offset_y;
1109  if (!horizontal_fix) dragged_vertex.x += dx;
1110  if (!vertical_fix) dragged_vertex.y += dy;
1111  // Handle snap to combine with other vertices
1112  if (!IsPolyline() && !structure_fix)
1113  {
1114  int32_t i = 0;
1115  drag_snap_vertex = -1;
1116  int32_t best_hit_range_sq = hit_range * hit_range * 4;
1117  for (Vertex &check_vertex : graph.vertices)
1118  {
1119  if (i != dragged_vertex_index && i != drag_source_vertex_index)
1120  {
1121  int32_t cdx = check_vertex.x - dragged_vertex.x - dx;
1122  int32_t cdy = check_vertex.y - dragged_vertex.y - dy;
1123  int32_t cdsq = cdx*cdx + cdy*cdy;
1124  if (cdsq <= best_hit_range_sq)
1125  {
1126  drag_snap_vertex = i;
1127  best_hit_range_sq = cdsq;
1128  }
1129  }
1130  ++i;
1131  }
1132  // Snapped? Move vertex to snap vertex then
1133  drag_snapped = (drag_snap_vertex >= 0);
1134  if (drag_snapped)
1135  {
1136  drag_snap_offset_x = graph.vertices[drag_snap_vertex].x - dragged_vertex.x;
1137  drag_snap_offset_y = graph.vertices[drag_snap_vertex].y - dragged_vertex.y;
1138  dragged_vertex.x += drag_snap_offset_x;
1139  dragged_vertex.y += drag_snap_offset_y;
1140  *drag_cursor = Qt::CursorShape::DragMoveCursor;
1141  }
1142  else
1143  {
1144  drag_snap_offset_x = drag_snap_offset_y = 0;
1145  *drag_cursor = Qt::CursorShape::SizeAllCursor;
1146  }
1147  }
1148  // Regular drag: Emit signal to update value
1149  EditGraph(true, C4ControlEditGraph::Action::CEG_SetVertexPos, dragged_vertex_index, dragged_vertex.x, dragged_vertex.y);
1150  }
1151 }
1152 
1153 void C4ConsoleQtGraph::StopDragging()
1154 {
1155  // Is it a vertex recombination?
1156  if (IsVertexDrag(dragging_border))
1157  {
1158  int32_t dragged_vertex = DragBorderToVertex(dragging_border);
1159  if (drag_snapped)
1160  {
1161  if (dragged_vertex && dragged_vertex != drag_snap_vertex)
1162  {
1163  // dragged_vertex is to be merged into drag_snap_vertex (keeping drag_snap_vertex)
1164  // find all edge targets already existing for drag_snap_vertex (this may include dragged_vertex)
1165  std::set<int32_t> vertices_connected_to_snap_vertex;
1166  int32_t idx;
1167  for (Edge &edge : graph.edges)
1168  {
1169  if (edge.connects_to(drag_snap_vertex, &idx))
1170  {
1171  vertices_connected_to_snap_vertex.insert(edge.vertex_indices[!idx]);
1172  }
1173  }
1174  // make sure that any connection from dragged_vertex to drag_snap_vertex will be excluded by the check
1175  vertices_connected_to_snap_vertex.insert(drag_snap_vertex);
1176  // move all connections that did not exist yet from dragged_vertex to drag_snap_vertex
1177  int32_t i_edge = 0;
1178  for (Edge &edge : graph.edges)
1179  {
1180  if (edge.connects_to(dragged_vertex, &idx))
1181  {
1182  if (!vertices_connected_to_snap_vertex.count(edge.vertex_indices[!idx]))
1183  {
1184  EditEdge(i_edge, idx, drag_snap_vertex);
1185  }
1186  }
1187  ++i_edge;
1188  }
1189  // ...and remove the dragged vertex. This will kill any remaining connections
1190  RemoveVertex(dragged_vertex, false);
1191  return;
1192  }
1193  }
1194  // Regular drag: Emit signal to update value
1195  EditGraph(true, C4ControlEditGraph::Action::CEG_SetVertexPos, dragged_vertex, graph.vertices[dragged_vertex].x, graph.vertices[dragged_vertex].y);
1196  }
1197  drag_snapped = false;
1198  // Reset drag
1199  C4ConsoleQtShape::StopDragging();
1200 }
1201 
1202 void C4ConsoleQtGraph::SetValue(const C4Value &val)
1203 {
1204  // Load from a proplist
1205  // Expected format for graph e.g.:
1206  // { Vertices = [{ X=123, Y=456, Color=0xff0000 }, { X=789, Y=753 }], Edges = [{ Vertices=[0, 1], Color=0xff00ff, LineWidth=2 }]
1207  C4PropList *valp = val.getPropList();
1208  if (valp)
1209  {
1210  graph.SetVerticesValue(valp->GetPropertyArray(P_Vertices));
1211  graph.SetEdgesValue(valp->GetPropertyArray(P_Edges));
1212  }
1213 }
1214 
1215 C4Value C4ConsoleQtGraph::GetValue() const
1216 {
1217  // Store graph as nested arrays / proplists
1218  C4ValueArray *vvertices = graph.GetVerticesValue();
1219  C4ValueArray *vedges = graph.GetEdgesValue();
1220  C4PropList *vmain = C4PropList::New();
1221  vmain->SetProperty(P_Vertices, C4VArray(vvertices));
1222  vmain->SetProperty(P_Edges, C4VArray(vedges));
1223  return C4VPropList(vmain);
1224 }
1225 
1226 void C4ConsoleQtGraph::EditEdge(int32_t edge_index, int32_t change_vertex_index, int32_t new_vertex)
1227 {
1228  EditGraph(true, C4ControlEditGraph::Action::CEG_EditEdge, edge_index, change_vertex_index, new_vertex);
1229 }
1230 
1231 int32_t C4ConsoleQtGraph::AddVertex(int32_t new_x, int32_t new_y)
1232 {
1233  EditGraph(true, C4ControlEditGraph::Action::CEG_InsertVertex, graph.vertices.size(), new_x, new_y);
1234  return graph.vertices.size() - 1;
1235 }
1236 
1237 int32_t C4ConsoleQtGraph::AddEdge(int32_t connect_vertex_index_1, int32_t connect_vertex_index_2)
1238 {
1239  EditGraph(true, C4ControlEditGraph::Action::CEG_InsertEdge, graph.edges.size(), connect_vertex_index_1, connect_vertex_index_2);
1240  return graph.edges.size() - 1;
1241 }
1242 
1243 int32_t C4ConsoleQtGraph::InsertVertexOnEdge(int32_t split_edge_index, int32_t x, int32_t y)
1244 {
1245  assert(split_edge_index >= 0 && split_edge_index < graph.edges.size());
1246  // Insert vertex by splitting an edge
1247  int32_t new_vertex_index = AddVertex(x, y);
1248  AddEdge(new_vertex_index, graph.edges[split_edge_index].vertex_indices[1]);
1249  EditEdge(split_edge_index, 1, new_vertex_index);
1250  // Return index of newly added vertex
1251  return new_vertex_index;
1252 }
1253 
1254 int32_t C4ConsoleQtGraph::InsertVertexOnVertex(int32_t target_vertex_index, int32_t x, int32_t y)
1255 {
1256  assert(target_vertex_index >= 0 && target_vertex_index < graph.vertices.size());
1257  // Insert vertex
1258  int32_t new_vertex_index = AddVertex(x, y);
1259  // Connect new vertex to target vertex
1260  AddEdge(target_vertex_index, new_vertex_index);
1261  // Return index of newly added vertex
1262  return new_vertex_index;
1263 }
1264 
1265 void C4ConsoleQtGraph::RemoveEdge(int32_t edge_index)
1266 {
1267  assert(edge_index >= 0 && edge_index < graph.edges.size());
1268  // Remove the edge
1269  Edge removed_edge = graph.edges[edge_index];
1270  EditGraph(true, C4ControlEditGraph::Action::CEG_RemoveEdge, edge_index, 0, 0);
1271  // Kill unconnected vertices
1272  if (!graph.GetEdgeCountForVertex(removed_edge.vertex_indices[0]))
1273  {
1274  RemoveVertex(removed_edge.vertex_indices[0], false);
1275  // Removing the first vertex will have shifted the index of the second vertex
1276  if (removed_edge.vertex_indices[1] > removed_edge.vertex_indices[0]) --removed_edge.vertex_indices[1];
1277  }
1278  if (!graph.GetEdgeCountForVertex(removed_edge.vertex_indices[1]))
1279  {
1280  // (RemoveVertex will always keep at least one vertex)
1281  RemoveVertex(removed_edge.vertex_indices[1], false);
1282  }
1283 }
1284 
1285 void C4ConsoleQtGraph::RemoveVertex(int32_t remove_vertex_index, bool create_skip_connection)
1286 {
1287  assert(remove_vertex_index >= 0 && remove_vertex_index < graph.vertices.size());
1288  // Create skip connection if the vertex had exactly two edges
1289  if (create_skip_connection && graph.GetEdgeCountForVertex(remove_vertex_index) == 2)
1290  {
1291  Edge *combine_edge = nullptr;
1292  int32_t i = 0;
1293  for (Edge &edge : graph.edges)
1294  {
1295  int32_t connect_idx;
1296  if (edge.connects_to(remove_vertex_index, &connect_idx))
1297  {
1298  if (!combine_edge)
1299  {
1300  combine_edge = &edge;
1301  }
1302  else
1303  {
1304  // Let edge bridge over the removed vertex
1305  int32_t v = combine_edge->vertex_indices[combine_edge->vertex_indices[0] == remove_vertex_index];
1306  EditEdge(i, connect_idx, v);
1307  // The removal check will remove the other edge (combine_edge)
1308  break;
1309  }
1310  }
1311  ++i;
1312  }
1313  }
1314  // Remove the actual vertex
1315  EditGraph(true, C4ControlEditGraph::Action::CEG_RemoveVertex, remove_vertex_index, 0, 0);
1316 }
1317 
1318 void C4ConsoleQtGraph::InsertVertexBefore(int32_t insert_vertex_index, int32_t x, int32_t y)
1319 {
1320  // Insert vertex at position in vertex list
1321  EditGraph(true, C4ControlEditGraph::Action::CEG_InsertVertex, insert_vertex_index, x, y);
1322 }
1323 
1324 void C4ConsoleQtGraph::InsertEdgeBefore(int32_t insert_edge_index, int32_t connect_vertex_index_1, int32_t connect_vertex_index_2)
1325 {
1326  // Insert vertex at position in vertex list
1327  EditGraph(true, C4ControlEditGraph::Action::CEG_InsertEdge, insert_edge_index, connect_vertex_index_1, connect_vertex_index_2);
1328 }
1329 
1330 bool C4ConsoleQtGraph::IsSelectionAllowed(int32_t border) const
1331 {
1332  // Independent selection settings for vertices and edges
1333  if (IsVertexDrag(border)) return allow_vertex_selection;
1334  if (IsEdgeDrag(border)) return allow_edge_selection;
1335  return false;
1336 }
1337 
1338 bool C4ConsoleQtGraph::GetSelectedData(const C4Value &shape_val, const class C4PropertyPath &shape_property_path, C4PropList **shape_item_editorprops, C4PropList **shape_item_value, C4String **shape_item_name, class C4PropertyPath *shape_item_target_path) const
1339 {
1340  // Selection may either be a vertex or an edge
1341  C4PropList *delegate = nullptr;
1342  C4ValueArray *vitems = nullptr;
1343  int32_t selected_item = -1;
1344  C4PropertyPath item_array_path;
1345  if (IsVertexDrag(selected_border))
1346  {
1347  selected_item = DragBorderToVertex(selected_border);
1348  delegate = vertex_delegate.getPropList();
1349  C4PropList *shape_val_proplist = shape_val.getPropList();
1350  if (!shape_val_proplist) return false;
1351  vitems = shape_val_proplist->GetPropertyArray(P_Vertices);
1352  item_array_path = C4PropertyPath(shape_property_path, ::Strings.P[P_Vertices].GetCStr());
1353  }
1354  else if (IsEdgeDrag(selected_border))
1355  {
1356  selected_item = DragBorderToEdge(selected_border);
1357  delegate = edge_delegate.getPropList();
1358  C4PropList *shape_val_proplist = shape_val.getPropList();
1359  if (!shape_val_proplist) return false;
1360  vitems = shape_val_proplist->GetPropertyArray(P_Edges);
1361  item_array_path = C4PropertyPath(shape_property_path, ::Strings.P[P_Edges].GetCStr());
1362  }
1363  if (!delegate) return false;
1364  // Get edge/vertex value
1365  if (!vitems || vitems->GetSize() <= selected_item) return false;
1366  *shape_item_value = vitems->GetItem(selected_item).getPropList();
1367  if (!*shape_item_value) return false;
1368  // Get edge/vertex path
1369  *shape_item_target_path = C4PropertyPath(item_array_path, selected_item);
1370  // Get delegate information
1371  *shape_item_name = delegate->GetPropertyStr(P_Name);
1372  *shape_item_editorprops = delegate->GetPropertyPropList(P_EditorProps);
1373  if (!*shape_item_editorprops) return false; // required
1374  return true;
1375 }
1376 
1377 void C4ConsoleQtGraph::EditGraph(bool signal_change, C4ControlEditGraph::Action action, int32_t index, int32_t x, int32_t y)
1378 {
1379  // Execute edit operation on graph
1380  // Validity checks are done by the individual operations
1381  switch (action)
1382  {
1383  case C4ControlEditGraph::Action::CEG_None:
1384  // Should never be sent
1385  assert(false);
1386  break;
1387  case C4ControlEditGraph::Action::CEG_SetVertexPos:
1388  graph.SetVertexPos(index, x, y);
1389  break;
1390  case C4ControlEditGraph::Action::CEG_EditEdge:
1391  graph.EditEdge(index, x, y);
1392  break;
1393  case C4ControlEditGraph::Action::CEG_InsertVertex:
1394  graph.InsertVertexBefore(index, x, y);
1395  break;
1396  case C4ControlEditGraph::Action::CEG_InsertEdge:
1397  graph.InsertEdgeBefore(index, x, y);
1398  break;
1399  case C4ControlEditGraph::Action::CEG_RemoveVertex:
1400  graph.RemoveVertex(index);
1401  break;
1402  case C4ControlEditGraph::Action::CEG_RemoveEdge:
1403  graph.RemoveEdge(index);
1404  break;
1405  }
1406  // Signal change to the owner of this graph
1407  // The owner should propagate the value through the control queue to edit the underlying script data and update any other clients
1408  if (signal_change)
1409  {
1410  emit GraphEdit(action, index, x, y);
1411  }
1412 }
1413 
1414 void C4ConsoleQtGraph::EditGraphValue(C4Value &val, C4ControlEditGraph::Action action, int32_t index, int32_t x, int32_t y)
1415 {
1416  // Execute action on C4Value representing graph
1417  // Get graph props: Can be either vertices as array (for polyline/polygon) or proplist with Vertices and Edges props (for general graphs)
1418  C4ValueArray *vvertices = nullptr, *vedges = nullptr;
1419  C4PropList *vval = val.getPropList();
1420  if (vval)
1421  {
1422  if (vval->IsFrozen()) return;
1423  vvertices = vval->GetPropertyArray(P_Vertices);
1424  vedges = vval->GetPropertyArray(P_Edges);
1425  if (!vvertices || !vedges || vvertices->IsFrozen() || vedges->IsFrozen()) return;
1426  }
1427  else
1428  {
1429  vvertices = val.getArray();
1430  if (!vvertices || vvertices->IsFrozen()) return;
1431  }
1432  // Validity checks are done by the individual operations
1433  switch (action)
1434  {
1435  case C4ControlEditGraph::Action::CEG_None:
1436  // Should never be sent
1437  assert(false);
1438  break;
1439  case C4ControlEditGraph::Action::CEG_SetVertexPos:
1440  {
1441  GraphData::EditGraphValue_SetVertexPos(vvertices, index, x, y);
1442  break;
1443  }
1444  case C4ControlEditGraph::Action::CEG_EditEdge:
1445  {
1446  if (vedges) // Ignore on polyline / polygon
1447  {
1448  GraphData::EditGraphValue_EditEdge(vvertices, vedges, index, x, y);
1449  }
1450  break;
1451  }
1452  case C4ControlEditGraph::Action::CEG_InsertVertex:
1453  {
1454  GraphData::EditGraphValue_InsertVertexBefore(vvertices, vedges, index, x, y);
1455  break;
1456  }
1457  case C4ControlEditGraph::Action::CEG_InsertEdge:
1458  {
1459  if (vedges) // Ignore on polyline / polygon
1460  {
1461  GraphData::EditGraphValue_InsertEdgeBefore(vvertices, vedges, index, x, y);
1462  }
1463  break;
1464  }
1465  case C4ControlEditGraph::Action::CEG_RemoveVertex:
1466  {
1467  GraphData::EditGraphValue_RemoveVertex(vvertices, vedges, index);
1468  break;
1469  }
1470  case C4ControlEditGraph::Action::CEG_RemoveEdge:
1471  {
1472  if (vedges) // Ignore on polyline / polygon
1473  {
1474  GraphData::EditGraphValue_RemoveEdge(vvertices, vedges, index);
1475  }
1476  break;
1477  }
1478  }
1479 }
1480 
1481 
1482 /* Open poly line */
1483 
1484 C4ConsoleQtPolyline::C4ConsoleQtPolyline(class C4Object *for_obj, C4PropList *props, const class C4PropertyDelegateShape *parent_delegate, class C4ConsoleQtShapes *shape_list)
1485  : C4ConsoleQtGraph(for_obj, props, parent_delegate, shape_list)
1486 {
1487  if (props)
1488  {
1489  start_from_object = props->GetPropertyBool(P_StartFromObject, start_from_object);
1490  }
1491 }
1492 
1493 void C4ConsoleQtPolyline::Draw(class C4TargetFacet &cgo, float line_width)
1494 {
1495  // Line from object center to first vertex
1496  if (start_from_object && graph.vertices.size())
1497  {
1498  C4ConsoleQtGraph::Vertex v0;
1499  if (!is_relative)
1500  {
1501  // In non-relative mode, the root coordinate needs to be pushed to the object manually
1502  C4Object *obj = rel_obj.getObj();
1503  if (obj)
1504  {
1505  v0.x += obj->GetX();
1506  v0.y += obj->GetY();
1507  }
1508  }
1509  DrawEdge(cgo, v0, graph.vertices[0], border_color, line_width, line_width, false);
1510  }
1511  // Remaining polyline is handled by regular graph drawing
1512  C4ConsoleQtGraph::Draw(cgo, line_width);
1513 }
1514 
1515 void C4ConsoleQtPolyline::SetValue(const C4Value &val)
1516 {
1517  // Set only vertices from value. Edges just connect all vertices.
1518  graph.SetVerticesValue(val.getArray());
1519  // Init edges directly to avoid unnecessery checks done by insert edge
1520  graph.edges.clear();
1521  if (graph.vertices.size() >= 2)
1522  {
1523  graph.edges.reserve(graph.vertices.size());
1524  for (int32_t i = 0; i < graph.vertices.size() - 1; ++i)
1525  {
1526  Edge edge;
1527  edge.vertex_indices[0] = i;
1528  edge.vertex_indices[1] = i + 1;
1529  graph.edges.push_back(edge);
1530  }
1531  }
1532 }
1533 
1534 C4Value C4ConsoleQtPolyline::GetValue() const
1535 {
1536  // Polyline: Only vertices; edges are implicit
1537  return C4VArray(graph.GetVerticesValue());
1538 }
1539 
1540 int32_t C4ConsoleQtPolyline::InsertVertexOnEdge(int32_t split_edge_index, int32_t x, int32_t y)
1541 {
1542  // Split an edge
1543  InsertVertexBefore(split_edge_index + 1, x, y);
1544  InsertEdgeBefore(split_edge_index, split_edge_index, split_edge_index + 1);
1545  EditEdge(split_edge_index + 1, 0, split_edge_index + 1);
1546  return split_edge_index + 1;
1547 }
1548 
1549 int32_t C4ConsoleQtPolyline::InsertVertexOnVertex(int32_t target_vertex_index, int32_t x, int32_t y)
1550 {
1551  // Only allowed on first or last index: Extends the poly line
1552  if (!target_vertex_index)
1553  {
1554  InsertVertexBefore(0, x, y);
1555  InsertEdgeBefore(0, 0, 1);
1556  return target_vertex_index;
1557  }
1558  else if (target_vertex_index == graph.vertices.size() - 1)
1559  {
1560  InsertVertexBefore(target_vertex_index + 1, x, y);
1561  InsertEdgeBefore(target_vertex_index, target_vertex_index, target_vertex_index + 1);
1562  return target_vertex_index + 1;
1563  }
1564  else
1565  {
1566  assert(false);
1567  return 0;
1568  }
1569 }
1570 
1571 void C4ConsoleQtPolyline::RemoveEdge(int32_t edge_index)
1572 {
1573  // Find larger remaining side and keep it
1574  int32_t before_vertices = edge_index + 1;
1575  int32_t after_vertices = graph.edges.size() - edge_index;
1576  if (before_vertices > after_vertices)
1577  {
1578  // Cut everything after removed edge
1579  for (int32_t i = 0; i < after_vertices; ++i)
1580  {
1581  RemoveVertex(graph.vertices.size()-1, false);
1582  }
1583  }
1584  else
1585  {
1586  // Cut everything before removed edge
1587  for (int32_t i = 0; i < before_vertices; ++i) // O(n^2)
1588  {
1589  RemoveVertex(0, false);
1590  }
1591  }
1592 }
1593 
1594 bool C4ConsoleQtPolyline::IsVertexHit(int32_t vertex_index, Qt::CursorShape *drag_cursor, bool shift_down, bool ctrl_down)
1595 {
1596  // Cannot add vertices from other vertices; only from edges
1597  // Except for end points where it would expand the poly line
1598  if (shift_down && !ctrl_down && vertex_index && vertex_index != graph.vertices.size()-1)
1599  {
1600  return false;
1601  }
1602  return C4ConsoleQtGraph::IsVertexHit(vertex_index, drag_cursor, shift_down, ctrl_down);
1603 }
1604 
1605 
1606 /* Closed polygon */
1607 
1608 void C4ConsoleQtPolygon::SetValue(const C4Value &val)
1609 {
1610  // Set open polyline vertices and edges
1611  C4ConsoleQtPolyline::SetValue(val);
1612  // Add closing edge
1613  if (graph.vertices.size() > 2)
1614  {
1615  Edge edge;
1616  edge.vertex_indices[0] = graph.vertices.size() - 1;
1617  edge.vertex_indices[1] = 0;
1618  graph.edges.push_back(edge);
1619  }
1620 }
1621 
1622 int32_t C4ConsoleQtPolygon::InsertVertexOnEdge(int32_t split_edge_index, int32_t x, int32_t y)
1623 {
1624  // Split the edge
1625  int32_t rval = C4ConsoleQtPolyline::InsertVertexOnEdge(split_edge_index, x, y);
1626  // Close the polygon if it just became a triangle
1627  if (graph.vertices.size() == 3)
1628  {
1629  AddEdge(2, 0);
1630  }
1631  return rval;
1632 }
1633 
1634 int32_t C4ConsoleQtPolygon::InsertVertexOnVertex(int32_t target_vertex_index, int32_t x, int32_t y)
1635 {
1636  // Never called because IsHit should return false
1637  assert(false);
1638  return 0;
1639 }
1640 
1641 void C4ConsoleQtPolygon::RemoveEdge(int32_t edge_index)
1642 {
1643  // Remove both connected vertices (unless it's the last one)
1644  Edge edge = graph.edges[edge_index];
1645  int32_t vertex_index = edge.vertex_indices[1];
1646  RemoveVertex(vertex_index, true);
1647  if (graph.vertices.size() > 1)
1648  {
1649  RemoveVertex(vertex_index ? edge.vertex_indices[0] : graph.vertices.size() - 1, true);
1650  }
1651 }
1652 
1653 bool C4ConsoleQtPolygon::IsVertexHit(int32_t vertex_index, Qt::CursorShape *drag_cursor, bool shift_down, bool ctrl_down)
1654 {
1655  // Cannot add vertices from other vertices; only from edges
1656  if (shift_down && !ctrl_down)
1657  {
1658  return false;
1659  }
1660  // Skip C4ConsoleQtPolyline::IsVertexHit; it doesn't do sensible extra checks
1661  return C4ConsoleQtGraph::IsVertexHit(vertex_index, drag_cursor, shift_down, ctrl_down);
1662 }
1663 
1664 
1665 /* Shape list */
1666 
1667 C4ConsoleQtShape *C4ConsoleQtShapes::CreateShape(class C4Object *for_obj, C4PropList *props, const C4Value &val, const class C4PropertyDelegateShape *parent_delegate)
1668 {
1669  C4String *type = props->GetPropertyStr(P_Type);
1670  if (!type) return nullptr;
1671  C4ConsoleQtShape *shape = nullptr;
1672  if (type->GetData() == "rect") shape = new C4ConsoleQtRect(for_obj, props, parent_delegate, this);
1673  else if (type->GetData() == "circle") shape = new C4ConsoleQtCircle(for_obj, props, parent_delegate, this);
1674  else if (type->GetData() == "point") shape = new C4ConsoleQtPoint(for_obj, props, parent_delegate, this);
1675  else if (type->GetData() == "graph") shape = new C4ConsoleQtGraph(for_obj, props, parent_delegate, this);
1676  else if (type->GetData() == "polyline") shape = new C4ConsoleQtPolyline(for_obj, props, parent_delegate, this);
1677  else if (type->GetData() == "polygon") shape = new C4ConsoleQtPolygon(for_obj, props, parent_delegate, this);
1678  shape->SetValue(val);
1679  return shape;
1680 }
1681 
1682 void C4ConsoleQtShapes::AddShape(C4ConsoleQtShape *shape)
1683 {
1684  if (shape) shapes.emplace_back(shape);
1685 }
1686 
1687 void C4ConsoleQtShapes::RemoveShape(C4ConsoleQtShape *shape)
1688 {
1689  // Remove from list and currently moving shape
1690  shapes.remove_if([shape](auto &it) { return it.get() == shape; });
1691  if (dragging_shape == shape) dragging_shape = nullptr;
1692  if (selected_shape == shape) selected_shape = nullptr;
1693 }
1694 
1695 void C4ConsoleQtShapes::ClearShapes()
1696 {
1697  shapes.clear();
1698  dragging_shape = selected_shape = nullptr;
1699  drag_cursor = Qt::CursorShape::ArrowCursor;
1700 }
1701 
1702 void C4ConsoleQtShapes::Draw(C4TargetFacet &cgo)
1703 {
1704  // Draw all shapes with at least 1px line width
1705  ZoomDataStackItem zdsi(cgo.X, cgo.Y, cgo.Zoom);
1706  float line_width = std::max<float>(1.0f, 1.0f / cgo.Zoom);
1707  for (auto &shape : shapes) shape->Draw(cgo, line_width);
1708 }
1709 
1710 bool C4ConsoleQtShapes::MouseDown(float x, float y, float hit_range, bool shift_down, bool ctrl_down)
1711 {
1712  // Check for shape hit and start dragging if a shape is in hit range
1713  int32_t hit_range_int = std::max(int32_t(hit_range + 0.5f), 1); // Using integer hit ranges for now
1714  // Ensure no leftover other shape
1715  if (dragging_shape) MouseUp(x, y, shift_down, ctrl_down);
1716  int32_t drag_border=-1;
1717  for (auto &shape : shapes)
1718  {
1719  if (shape->IsHit(x, y, hit_range_int, &drag_cursor, &drag_border, shift_down, ctrl_down))
1720  {
1721  dragging_shape = shape.get();
1722  if (dragging_shape->StartDragging(&drag_border, int32_t(x), int32_t(y), shift_down, ctrl_down))
1723  {
1724  drag_x = x;
1725  drag_y = y;
1726  }
1727  else
1728  {
1729  // No dragging (the click may have done something else with the shape)
1730  dragging_shape = nullptr;
1731  }
1732  // Selection (independent of dragging; but drag may have changed the border)
1733  if (drag_border != -1)
1734  {
1735  if (shape->IsSelectionAllowed(drag_border))
1736  {
1737  SetSelectedShape(&*shape, drag_border);
1738  }
1739  }
1740  return true;
1741  }
1742  }
1743  return false;
1744 }
1745 
1746 void C4ConsoleQtShapes::MouseMove(float x, float y, bool left_down, float hit_range, bool shift_down, bool ctrl_down)
1747 {
1748  // Check for shape hit and start dragging if a shape is in hit range
1749  int32_t hit_range_int = std::max(int32_t(hit_range + 0.5f), 1); // Using integer hit ranges for now
1750  // mouse down move: Execute shape dragging (full pixels only)
1751  if (dragging_shape && left_down)
1752  {
1753  int32_t dx = int32_t(round(x - drag_x)),
1754  dy = int32_t(round(y - drag_y));
1755  if (dx || dy)
1756  {
1757  drag_x += dx;
1758  drag_y += dy;
1759  dragging_shape->Drag(drag_x, drag_y, dx, dy, hit_range_int, &drag_cursor);
1760  }
1761  }
1762  else if (!left_down)
1763  {
1764  // Just moving around: Update cursor
1765  drag_cursor = Qt::CursorShape::ArrowCursor;
1766  int32_t ignored;
1767  for (auto &shape : shapes) if (shape->IsHit(x, y, hit_range_int, &drag_cursor, &ignored, shift_down, ctrl_down)) break;
1768  }
1769  else
1770  {
1771  // Regular move: Reset drag cursor
1772  drag_cursor = Qt::CursorShape::ArrowCursor;
1773  }
1774 }
1775 
1776 void C4ConsoleQtShapes::MouseUp(float x, float y, bool shift_down, bool ctrl_down)
1777 {
1778  // Stop dragging
1779  if (dragging_shape)
1780  {
1781  dragging_shape->StopDragging();
1782  dragging_shape = nullptr;
1783  drag_cursor = Qt::CursorShape::ArrowCursor;
1784  }
1785 }
1786 
1787 void C4ConsoleQtShapes::SetSelectedShape(C4ConsoleQtShape *new_selection, int32_t selected_border)
1788 {
1789  // Deselect old and select new
1790  if (selected_shape) selected_shape->ResetSelection();
1791  selected_shape = new_selection;
1792  if (selected_shape)
1793  {
1794  if (!selected_shape->Select(selected_border))
1795  {
1796  // Selection failure? Deselect if still selected.
1797  if (selected_shape == new_selection) selected_shape = nullptr;
1798  }
1799  }
1800 }
1801 
1802 bool C4ConsoleQtShapes::GetSelectedShapeData(const C4Value &shape_val, const class C4PropertyPath &shape_property_path, C4PropList **shape_item_editorprops, C4PropList **shape_item_value, C4String **shape_item_name, class C4PropertyPath *shape_item_target_path) const
1803 {
1804  if (!selected_shape) return false;
1805  return selected_shape->GetSelectedData(shape_val, shape_property_path, shape_item_editorprops, shape_item_value, shape_item_name, shape_item_target_path);
1806 }
1807 
1808 
1809 /* Shape pointer holder class */
1810 
1811 bool C4ConsoleQtShapeHolder::last_visit_flag = false;
1812 
1813 void C4ConsoleQtShapeHolder::Clear()
1814 {
1815  if (shape)
1816  {
1817  ::Console.EditCursor.GetShapes()->RemoveShape(shape);
1818  shape = nullptr;
1819  }
1820 }
1821 
1822 void C4ConsoleQtShapeHolder::Set(C4ConsoleQtShape *new_shape)
1823 {
1824  if (shape == new_shape) return;
1825  Clear();
1826  shape = new_shape;
1827  if (shape) ::Console.EditCursor.GetShapes()->AddShape(shape);
1828 }
const BYTE CNAT_Bottom
Definition: C4Constants.h:112
const BYTE CNAT_Right
Definition: C4Constants.h:110
const BYTE CNAT_Top
Definition: C4Constants.h:111
const BYTE CNAT_Left
Definition: C4Constants.h:109
C4Draw * pDraw
Definition: C4Draw.cpp:42
C4Console Console
Definition: C4Globals.cpp:45
C4StringTable Strings
Definition: C4Globals.cpp:42
@ P_Proplist
@ P_proplist
@ P_Name
@ P_Color
@ P_StartFromObject
@ P_Wdt
@ P_DrawArrows
@ P_EditorProps
@ P_Vertices
@ P_EdgeDelegate
@ P_Relative
@ P_StructureFix
@ P_HorizontalFix
@ P_Y
@ P_CanMoveCenter
@ P_VertexDelegate
@ P_Hgt
@ P_wdt
@ P_hgt
@ P_Edges
@ P_Type
@ P_X
@ P_x
@ P_LineWidth
@ P_VerticalFix
@ P_Storage
@ P_y
C4Value C4VArray(C4ValueArray *pArray)
Definition: C4Value.h:246
C4Value C4VInt(int32_t i)
Definition: C4Value.h:239
C4Value C4VPropList(C4PropList *p)
Definition: C4Value.h:242
C4EditCursor EditCursor
Definition: C4Console.h:90
void DrawCircleDw(C4Surface *sfcTarget, float cx, float cy, float r, DWORD dwClr, float width=1.0f)
Definition: C4Draw.cpp:618
void DrawLineDw(C4Surface *sfcTarget, float x1, float y1, float x2, float y2, DWORD dwClr, float width=1.0f)
Definition: C4Draw.cpp:608
C4Surface * Surface
Definition: C4Facet.h:117
float Y
Definition: C4Facet.h:118
float X
Definition: C4Facet.h:118
int32_t GetX() const
Definition: C4Object.h:285
int32_t GetY() const
Definition: C4Object.h:286
bool IsFrozen() const
Definition: C4PropList.h:135
int32_t GetPropertyInt(C4PropertyName k, int32_t default_val=0) const
Definition: C4PropList.cpp:855
int32_t GetPropertyBool(C4PropertyName n, bool default_val=false) const
Definition: C4PropList.cpp:841
C4ValueArray * GetPropertyArray(C4PropertyName n) const
Definition: C4PropList.cpp:758
bool HasProperty(C4String *k) const
Definition: C4PropList.h:122
C4PropList * GetPropertyPropList(C4PropertyName k) const
Definition: C4PropList.cpp:869
C4String * GetPropertyStr(C4PropertyName k) const
Definition: C4PropList.cpp:744
static C4PropList * New(C4PropList *prototype=nullptr)
Definition: C4PropList.cpp:40
bool GetProperty(C4PropertyName k, C4Value *pResult) const
Definition: C4PropList.h:105
void SetProperty(C4PropertyName k, const C4Value &to)
Definition: C4PropList.h:124
const char * GetCStr() const
Definition: C4StringTable.h:49
C4String P[P_LAST]
float TargetY
Definition: C4Facet.h:165
float TargetX
Definition: C4Facet.h:165
float Zoom
Definition: C4Facet.h:165
const C4Value & GetItem(int32_t iElem) const
Definition: C4ValueArray.h:38
bool IsFrozen() const
Definition: C4ValueArray.h:63
void SetSize(int32_t inSize)
void SetItem(int32_t iElemNr, const C4Value &Value)
int32_t GetSize() const
Definition: C4ValueArray.h:36
C4ValueArray * getArray() const
Definition: C4Value.h:118
int32_t getInt() const
Definition: C4Value.h:112
C4PropList * getPropList() const
Definition: C4Value.h:116
void MouseMove(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam, class C4Viewport *pVP)
Definition: C4Gui.h:2832