1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2007, Günther
6 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18/* A window listing all objects in the game */
19
20#include <C4Include.h>
21#include <C4ObjectListDlg.h>
22#include <C4Console.h>
23#include <C4Object.h>
24
25#ifdef WITH_DEVELOPER_MODE
26#include <gtk/gtk.h>
27
28/* Some boilerplate GObject defines. 'klass' is used instead of 'class', because 'class' is a C++ keyword */
29
30#define C4_TYPE_LIST (c4_list_get_type ())
31#define C4_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), C4_TYPE_LIST, C4List))
32#define C4_IS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), C4_TYPE_LIST))
33
34typedef struct _C4List C4List;
35typedef struct _C4ListClass C4ListClass;
36
37struct _C4List
38{
39 GObject parent; /* this MUST be the first member */
40
41 C4ObjectList *data;
42
43 gint stamp; /* A random integer to check if an iter belongs to this model */
44};
45
46/* more boilerplate GObject stuff */
47
48struct _C4ListClass
49{
50 GObjectClass parent_class;
51};
52
53static GObjectClass *parent_class = nullptr;
54
55GType c4_list_get_type(void);
56
57// Initialise an instance
58static void
59c4_list_init(C4List *c4_list)
60{
61 c4_list->data = &Game.Objects;
62
63 c4_list->stamp = g_random_int(); /* Random int to check whether iters belong to this model */
64}
65
66// destructor
67static void
68c4_list_finalize(GObject *object)
69{
70 /* must chain up - finalize parent */
71 (*parent_class->finalize)(object);
72}
73
74static GtkTreeModelFlags
75c4_list_get_flags(GtkTreeModel *tree_model)
76{
77 g_return_val_if_fail(C4_IS_LIST(tree_model), (GtkTreeModelFlags)0);
78
79 // neither is this a flat list nor do the iters persist changes in the model
80 return GtkTreeModelFlags(0);
81}
82
83// converts 'path' into an iterator and stores that in 'iter'
84static gboolean
85c4_list_get_iter(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path)
86{
87 gint *indices, depth;
88
89 g_assert(C4_IS_LIST(tree_model));
90 g_assert(path != nullptr);
91
92 C4List *c4_list = C4_LIST(tree_model);
93
94 indices = gtk_tree_path_get_indices(path);
95 depth = gtk_tree_path_get_depth(path);
96
97 iter->stamp = c4_list->stamp;
98
99 C4ObjectLink *pLnk = C4_LIST(tree_model)->data->First;
100 // Skip Contained Objects in the main list
101 while (pLnk && pLnk->Obj->Contained) pLnk = pLnk->Next;
102 for (int i = 0; i < depth; ++i)
103 {
104 if (!pLnk)
105 return FALSE;
106 if (indices[i] < 0)
107 return FALSE;
108 for (int j = 0; j < indices[i]; ++j)
109 {
110 pLnk = pLnk->Next;
111 // Skip Contained Objects in the main list
112 while (i == 0 && pLnk && pLnk->Obj->Contained) pLnk = pLnk->Next;
113 if (!pLnk)
114 return FALSE;
115 }
116 iter->user_data = pLnk;
117 iter->user_data2 = pLnk->Obj->Contained;
118 pLnk = pLnk->Obj->Contents.First;
119 }
120
121 return TRUE;
122}
123
124// converts 'iter' into a new tree path and returns that.
125// Note: This is called by OnObjectRemove with an iter which is not in the
126// list anymore, but with the object and prev still usable.
127static GtkTreePath *
128c4_list_get_path(GtkTreeModel *tree_model, GtkTreeIter *iter)
129{
130 g_return_val_if_fail(C4_IS_LIST(tree_model), nullptr);
131 g_return_val_if_fail(iter != nullptr, nullptr);
132 g_return_val_if_fail(iter->user_data != nullptr, nullptr);
133
134 GtkTreePath *path = gtk_tree_path_new();
135
136 C4List *c4_list = C4_LIST(tree_model);
137
138 C4Object *pObj = ((C4ObjectLink *)iter->user_data)->Obj;
139
140 int i = 0;
141 for (C4ObjectLink *pLnk = ((C4ObjectLink *)iter->user_data)->Prev; pLnk; pLnk = pLnk->Prev)
142 {
143 // Skip Contained Objects in the main list
144 if (pObj->Contained != pLnk->Obj->Contained) continue;
145 ++i;
146 }
147 gtk_tree_path_prepend_index(path, index_: i);
148
149 pObj = (C4Object *)iter->user_data2;
150 while (pObj)
151 {
152 i = 0;
153 C4ObjectList *pList = c4_list->data;
154 if (pObj->Contained)
155 pList = &pObj->Contained->Contents;
156 for (C4ObjectLink *pLnk = pList->First; pLnk && pLnk->Obj != pObj; pLnk = pLnk->Next)
157 {
158 // Skip Contained Objects in the main list
159 if (pObj->Contained != pLnk->Obj->Contained) continue;
160 ++i;
161 }
162 gtk_tree_path_prepend_index(path, index_: i);
163 pObj = pObj->Contained;
164 }
165
166 return path;
167}
168
169// ++iter
170static gboolean
171c4_list_iter_next(GtkTreeModel *tree_model, GtkTreeIter *iter)
172{
173 g_return_val_if_fail(C4_IS_LIST(tree_model), FALSE);
174
175 if (iter == nullptr || iter->user_data == nullptr)
176 return FALSE;
177
178 C4ObjectLink *pLnk = (C4ObjectLink *)iter->user_data;
179
180 pLnk = pLnk->Next;
181
182 // Skip Contained Objects in the main list
183 if (!(C4Object *)iter->user_data2)
184 while (pLnk && pLnk->Obj->Contained)
185 pLnk = pLnk->Next;
186 if (!pLnk)
187 return FALSE;
188
189 iter->user_data = pLnk;
190 iter->user_data2 = pLnk->Obj->Contained;
191
192 return TRUE;
193}
194
195// Set 'iter' to the first child of 'parent', or the first top-level row if
196// 'parent' is 0, or return FALSE if there aren't any children.
197static gboolean
198c4_list_iter_children(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent)
199{
200 g_return_val_if_fail(parent == nullptr || parent->user_data != nullptr, FALSE);
201 g_return_val_if_fail(C4_IS_LIST(tree_model), FALSE);
202
203 C4List *c4_list = C4_LIST(tree_model);
204
205 C4ObjectLink *pLnk;
206 if (parent)
207 {
208 C4ObjectList *pList = &((C4ObjectLink *)parent->user_data)->Obj->Contents;
209 pLnk = pList->First;
210 }
211 else
212 {
213 pLnk = c4_list->data->First;
214 // Skip...
215 while (pLnk && pLnk->Obj->Contained) pLnk = pLnk->Next;
216 }
217 if (!pLnk)
218 return FALSE;
219
220 /* Set iter to first item in list */
221 iter->stamp = c4_list->stamp;
222 iter->user_data = pLnk;
223 iter->user_data2 = pLnk->Obj->Contained;
224
225 return TRUE;
226}
227
228// Return TRUE if 'parent' has children.
229static gboolean
230c4_list_iter_has_child(GtkTreeModel *tree_model,
231 GtkTreeIter *parent)
232{
233 g_return_val_if_fail(parent == nullptr || parent->user_data != nullptr, FALSE);
234 g_return_val_if_fail(C4_IS_LIST(tree_model), FALSE);
235
236 C4List *c4_list = C4_LIST(tree_model);
237
238 C4ObjectList *pList = c4_list->data;
239 if (parent)
240 pList = &((C4ObjectLink *)parent->user_data)->Obj->Contents;
241 return pList->First != nullptr;
242}
243
244// Counts the children 'parent' has.
245static gint
246c4_list_iter_n_children(GtkTreeModel *tree_model, GtkTreeIter *parent)
247{
248 g_return_val_if_fail(C4_IS_LIST(tree_model), -1);
249 g_return_val_if_fail(parent == nullptr || parent->user_data != nullptr, -1);
250
251 C4List *c4_list = C4_LIST(tree_model);
252
253 int i = 0;
254 if (parent)
255 {
256 C4ObjectList *pList = &((C4ObjectLink *)parent->user_data)->Obj->Contents;
257 C4ObjectLink *pLnk = pList->First;
258 while (pLnk)
259 {
260 ++i;
261 pLnk = pLnk->Next;
262 }
263 }
264 else
265 {
266 C4ObjectLink *pLnk = c4_list->data->First;
267 while (pLnk)
268 {
269 if (!pLnk->Obj->Contained)
270 ++i;
271 pLnk = pLnk->Next;
272 }
273 }
274 return i;
275}
276
277// Sets 'iter' to the 'n'-th child of 'parent'.
278static gboolean
279c4_list_iter_nth_child(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n)
280{
281 g_return_val_if_fail(C4_IS_LIST(tree_model), FALSE);
282 g_return_val_if_fail(parent == nullptr || parent->user_data != nullptr, FALSE);
283
284 C4List *c4_list = C4_LIST(tree_model);
285
286 C4ObjectLink *pLnk;
287 if (parent)
288 {
289 C4ObjectList *pList = &((C4ObjectLink *)parent->user_data)->Obj->Contents;
290 pLnk = pList->First;
291 for (int i = 0; i < n; ++i)
292 {
293 if (!pLnk)
294 return FALSE;
295 pLnk = pLnk->Next;
296 }
297 }
298 else
299 {
300 pLnk = c4_list->data->First;
301 for (int i = 0; i < n; ++i)
302 {
303 if (!pLnk)
304 return FALSE;
305 pLnk = pLnk->Next;
306 // Skip...
307 while (pLnk && pLnk->Obj->Contained) pLnk = pLnk->Next;
308 }
309 }
310 if (!pLnk)
311 return FALSE;
312
313 iter->stamp = c4_list->stamp;
314 iter->user_data = pLnk;
315 iter->user_data2 = pLnk->Obj->Contained;
316
317 return TRUE;
318}
319
320// Helper function.
321static gboolean c4_list_iter_for_C4Object(GtkTreeModel *tree_model, GtkTreeIter *iter, C4ObjectList *pList, C4Object *pObj)
322{
323 if (!pObj)
324 return FALSE;
325
326 C4List *c4_list = C4_LIST(tree_model);
327
328 for (C4ObjectLink *pLnk = pList->First; pLnk; pLnk = pLnk->Next)
329 {
330 if (pLnk->Obj == pObj)
331 {
332 iter->stamp = c4_list->stamp;
333 iter->user_data = pLnk;
334 iter->user_data2 = pLnk->Obj->Contained;
335
336 return TRUE;
337 }
338 }
339
340 g_return_val_if_reached(FALSE);
341}
342
343// Sets 'iter' to the parent row of 'child'.
344static gboolean
345c4_list_iter_parent(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
346{
347 g_return_val_if_fail(C4_IS_LIST(tree_model), FALSE);
348 g_return_val_if_fail(child == nullptr || child->user_data != nullptr, FALSE);
349
350 C4List *c4_list = C4_LIST(tree_model);
351
352 C4Object *pObj = (C4Object *)child->user_data2;
353
354 C4ObjectList *pList = c4_list->data;
355 if (pObj->Contained)
356 pList = &pObj->Contained->Contents;
357 return c4_list_iter_for_C4Object(tree_model, iter, pList, pObj);
358}
359
360static C4Object *
361c4_list_iter_get_C4Object(GtkTreeModel *tree_model, GtkTreeIter *iter)
362{
363 g_return_val_if_fail(C4_IS_LIST(tree_model), nullptr);
364 g_return_val_if_fail(iter != nullptr && iter->user_data != nullptr, nullptr);
365
366 return ((C4ObjectLink *)iter->user_data)->Obj;
367}
368
369// How many columns does this model have?
370static gint
371c4_list_get_n_columns(GtkTreeModel *tree_model)
372{
373 g_return_val_if_fail(C4_IS_LIST(tree_model), 0);
374
375 return 1;
376}
377
378// What sort of data is in the column?
379static GType
380c4_list_get_column_type(GtkTreeModel *tree_model, gint index)
381{
382 g_return_val_if_fail(C4_IS_LIST(tree_model), G_TYPE_INVALID);
383 g_return_val_if_fail(index < 1 && index >= 0, G_TYPE_INVALID);
384
385 return G_TYPE_POINTER;
386}
387
388// gets the data for the 'column' in the row at 'iter' and stores that in 'value'.
389static void
390c4_list_get_value(GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value)
391{
392 g_return_if_fail(C4_IS_LIST(tree_model));
393 g_return_if_fail(iter != nullptr);
394 g_return_if_fail(column == 0);
395
396 C4Object *pObj = ((C4ObjectLink *)iter->user_data)->Obj;
397 g_return_if_fail(pObj != nullptr);
398
399 g_value_init(value, G_TYPE_POINTER);
400 g_value_set_pointer(value, v_pointer: pObj);
401}
402
403// Wrapper around g_object_new.
404static C4List *
405c4_list_new(void)
406{
407 C4List *list;
408
409 list = (C4List *)g_object_new(C4_TYPE_LIST, first_property_name: nullptr);
410
411 g_assert(list != nullptr);
412
413 return list;
414}
415
416// Called once for the class.
417static void
418c4_list_class_init(C4ListClass *klass)
419{
420 GObjectClass *object_class;
421
422 parent_class = (GObjectClass *)g_type_class_peek_parent(g_class: klass);
423 object_class = (GObjectClass *)klass;
424
425 object_class->finalize = c4_list_finalize;
426}
427
428// fill in the GtkTreeModel interface with the functions above.
429static void
430c4_list_tree_model_init(GtkTreeModelIface *iface)
431{
432 iface->get_flags = c4_list_get_flags;
433 iface->get_n_columns = c4_list_get_n_columns;
434 iface->get_column_type = c4_list_get_column_type;
435 iface->get_iter = c4_list_get_iter;
436 iface->get_path = c4_list_get_path;
437 iface->get_value = c4_list_get_value;
438 iface->iter_next = c4_list_iter_next;
439 iface->iter_children = c4_list_iter_children;
440 iface->iter_has_child = c4_list_iter_has_child;
441 iface->iter_n_children = c4_list_iter_n_children;
442 iface->iter_nth_child = c4_list_iter_nth_child;
443 iface->iter_parent = c4_list_iter_parent;
444}
445
446// Return the type, registering it on first call.
447GType
448c4_list_get_type(void)
449{
450 static GType c4_list_type = 0;
451
452 if (c4_list_type == 0)
453 {
454 // Some boilerplate type registration stuff
455 static const GTypeInfo c4_list_info =
456 {
457 .class_size: sizeof(C4ListClass), /* class_size */
458 .base_init: nullptr, /* base_init */
459 .base_finalize: nullptr, /* base_finalize */
460 .class_init: (GClassInitFunc)c4_list_class_init, /* class_init */
461 .class_finalize: nullptr, /* class finalize */
462 .class_data: nullptr, /* class_data */
463 .instance_size: sizeof(C4List), /* instance_size */
464 .n_preallocs: 0, /* n_preallocs */
465 .instance_init: (GInstanceInitFunc)c4_list_init, /* instance_init */
466 .value_table: nullptr /* value_table */
467 };
468
469 c4_list_type = g_type_register_static(G_TYPE_OBJECT, type_name: "C4List",
470 info: &c4_list_info, flags: (GTypeFlags)0);
471
472 /* register the GtkTreeModel interface with the type system */
473 static const GInterfaceInfo tree_model_info =
474 {
475 .interface_init: (GInterfaceInitFunc)c4_list_tree_model_init,
476 .interface_finalize: nullptr,
477 .interface_data: nullptr
478 };
479
480 g_type_add_interface_static(instance_type: c4_list_type, GTK_TYPE_TREE_MODEL, info: &tree_model_info);
481 }
482
483 return c4_list_type;
484}
485
486void C4ObjectListDlg::OnObjectRemove(C4ObjectList *pList, C4ObjectLink *pLnk)
487{
488 if (!model) return;
489
490 C4List *c4_list = C4_LIST(model);
491 C4Object *Contained = pLnk->Obj->Contained;
492 GtkTreeIter iter;
493 iter.stamp = c4_list->stamp;
494 iter.user_data = pLnk;
495 iter.user_data2 = Contained;
496
497 // While pLnk is not in the list anymore, with pLnk->Prev and Contained a path can still be made
498 GtkTreePath *path = c4_list_get_path(GTK_TREE_MODEL(model), iter: &iter);
499
500 gtk_tree_model_row_deleted(GTK_TREE_MODEL(model), path);
501 gtk_tree_path_free(path);
502
503 // Removed from a now empty container?
504 if (Contained && !Contained->Contents.First)
505 {
506 printf(format: "Removed from a now empty container\n");
507 GtkTreeIter parent;
508 C4ObjectList *pList = c4_list->data;
509 if (Contained->Contained)
510 pList = &Contained->Contained->Contents;
511 c4_list_iter_for_C4Object(GTK_TREE_MODEL(model), iter: &parent, pList, pObj: Contained);
512
513 GtkTreePath *path = c4_list_get_path(GTK_TREE_MODEL(model), iter: &parent);
514
515 gtk_tree_model_row_has_child_toggled(GTK_TREE_MODEL(model), path, iter: &parent);
516 gtk_tree_path_free(path);
517 }
518
519 // Cheat: For the signals it must look as if the object had it's parent removed already
520 pLnk->Obj->Contained = nullptr;
521 // if removed from contents, it get's added to main list
522 if (pList != c4_list->data)
523 {
524 printf(format: "Removed from a container\n");
525 GtkTreeIter iter;
526 C4ObjectList *pList = c4_list->data;
527 c4_list_iter_for_C4Object(GTK_TREE_MODEL(model), iter: &iter, pList, pObj: pLnk->Obj);
528
529 GtkTreePath *path = c4_list_get_path(GTK_TREE_MODEL(model), iter: &iter);
530
531 gtk_tree_model_row_inserted(GTK_TREE_MODEL(model), path, iter: &iter);
532 gtk_tree_path_free(path);
533 }
534
535 // End-of-cheat
536 pLnk->Obj->Contained = Contained;
537}
538
539void C4ObjectListDlg::OnObjectAdded(C4ObjectList *pList, C4ObjectLink *pLnk)
540{
541 if (!model) return;
542
543 C4List *c4_list = C4_LIST(model);
544
545 // Inserted into a container? Remove from main list
546 if (pList != c4_list->data)
547 {
548 printf(format: "Inserted into a container\n");
549 GtkTreePath *path = gtk_tree_path_new();
550 int i = 0;
551 C4ObjectList *pList = c4_list->data;
552 C4Object *pObj = pLnk->Obj;
553 for (C4ObjectLink *pLnk2 = pList->First; pLnk2 && pLnk2->Obj != pObj; pLnk2 = pLnk2->Next)
554 {
555 // Skip Contained Objects in the main list
556 if (pLnk2->Obj->Contained) continue;
557 ++i;
558 }
559 gtk_tree_path_prepend_index(path, index_: i);
560
561 gtk_tree_model_row_deleted(GTK_TREE_MODEL(model), path);
562 gtk_tree_path_free(path);
563 }
564
565 GtkTreeIter iter;
566 iter.stamp = c4_list->stamp;
567 iter.user_data = pLnk;
568 iter.user_data2 = pLnk->Obj->Contained;
569
570 GtkTreePath *path = c4_list_get_path(GTK_TREE_MODEL(model), iter: &iter);
571
572 gtk_tree_model_row_inserted(GTK_TREE_MODEL(model), path, iter: &iter);
573 gtk_tree_path_free(path);
574
575 // Inserted into a previously empty container?
576 if (pLnk->Obj->Contained &&
577 pLnk->Obj->Contained->Contents.First == pLnk->Obj->Contained->Contents.Last)
578 {
579 printf(format: "Inserted into a previously empty container\n");
580 GtkTreeIter parent;
581 c4_list_iter_parent(GTK_TREE_MODEL(model), iter: &parent, child: &iter);
582
583 GtkTreePath *path = c4_list_get_path(GTK_TREE_MODEL(model), iter: &parent);
584
585 gtk_tree_model_row_has_child_toggled(GTK_TREE_MODEL(model), path, iter: &parent);
586 gtk_tree_path_free(path);
587 }
588}
589
590void C4ObjectListDlg::OnObjectRename(C4ObjectList *pList, C4ObjectLink *pLnk) {}
591
592void C4ObjectListDlg::OnDestroy(GtkWidget *widget, C4ObjectListDlg *dlg)
593{
594 dlg->window = nullptr;
595 dlg->model = nullptr;
596 dlg->treeview = nullptr;
597}
598
599void C4ObjectListDlg::OnSelectionChanged(GtkTreeSelection *selection, C4ObjectListDlg *dlg)
600{
601 if (dlg->updating_selection) return;
602 dlg->updating_selection = true;
603
604 GList *list = gtk_tree_selection_get_selected_rows(selection, model: nullptr);
605
606 Console.EditCursor.GetSelection().Clear();
607 for (GList *i = list; i; i = i->next)
608 {
609 GtkTreePath *path = (GtkTreePath *)i->data;
610 GtkTreeIter iter;
611 c4_list_get_iter(GTK_TREE_MODEL(dlg->model), iter: &iter, path);
612 Console.EditCursor.GetSelection().Add(nObj: ((C4ObjectLink *)iter.user_data)->Obj, eSort: C4ObjectList::stNone);
613 }
614
615 g_list_foreach(list, func: (GFunc)gtk_tree_path_free, user_data: nullptr);
616 g_list_free(list);
617
618 Console.EditCursor.OnSelectionChanged();
619 dlg->updating_selection = false;
620}
621
622void C4ObjectListDlg::Update(C4ObjectList &rSelection)
623{
624 if (updating_selection) return;
625 if (!window) return;
626 updating_selection = true;
627
628 GtkTreeSelection *selection;
629
630 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
631
632 gtk_tree_selection_unselect_all(selection);
633
634 for (C4ObjectLink *pLnk = rSelection.First; pLnk; pLnk = pLnk->Next)
635 {
636 GtkTreeIter iter;
637 C4List *c4_list = C4_LIST(model);
638 C4ObjectList *pList = c4_list->data;
639 if (pLnk->Obj->Contained)
640 pList = &pLnk->Obj->Contained->Contents;
641 c4_list_iter_for_C4Object(GTK_TREE_MODEL(model), iter: &iter, pList, pObj: pLnk->Obj);
642 gtk_tree_selection_select_iter(selection, iter: &iter);
643 }
644
645 updating_selection = false;
646}
647
648C4ObjectListDlg::C4ObjectListDlg() :
649 updating_selection(false),
650 window(nullptr),
651 treeview(nullptr),
652 model(nullptr) {}
653
654C4ObjectListDlg::~C4ObjectListDlg() {}
655
656void C4ObjectListDlg::Execute() {}
657
658static void name_cell_data_func(GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
659{
660 C4Object *object = c4_list_iter_get_C4Object(tree_model: model, iter);
661
662 g_object_set(G_OBJECT(renderer), first_property_name: "text", C4Console::ClonkToGtk(text: object->GetName()).c_str(), (gpointer)nullptr);
663}
664
665#define ICON_SIZE 24
666
667static void icon_cell_data_func(GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
668{
669 C4Object *object = c4_list_iter_get_C4Object(tree_model: model, iter);
670
671 // Icons for objects with ColorByOwner are cached by object, others by Def
672 // FIXME: Invalidate cache when objects change color, and redraw.
673 gpointer key = object->Def;
674 if (object->Def->ColorByOwner) key = object;
675
676 GHashTable *table = static_cast<GHashTable *>(data);
677 GdkPixbuf *pixbuf = GDK_PIXBUF(g_hash_table_lookup(table, key));
678
679 if (pixbuf == nullptr)
680 {
681 /* Not yet cached, create from Graphics */
682 C4Surface *surface = object->Def->Graphics.Bitmap;
683 if (object->Def->Graphics.BitmapClr) surface = object->Def->Graphics.BitmapClr;
684
685 const C4Rect &picture = object->Def->PictureRect;
686 pixbuf = gdk_pixbuf_new(colorspace: GDK_COLORSPACE_RGB, TRUE, bits_per_sample: 8, width: picture.Wdt, height: picture.Hgt);
687 guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
688 surface->Lock();
689 for (int y = 0; y < picture.Hgt; ++y) for (int x = 0; x < picture.Wdt; ++x)
690 {
691 uint32_t dw = surface->GetPixDw(iX: picture.x + x, iY: picture.y + y, fApplyModulation: true);
692 *pixels = (dw >> 16) & 0xff; ++pixels;
693 *pixels = (dw >> 8) & 0xff; ++pixels;
694 *pixels = (dw) & 0xff; ++pixels;
695 *pixels = 0xff - ((dw >> 24) & 0xff); ++pixels;
696 }
697 surface->Unlock();
698
699 // Scale down to ICON_SIZE, keeping aspect ratio
700 guint dest_width, dest_height;
701 if (picture.Wdt >= picture.Hgt)
702 {
703 double factor = static_cast<double>(picture.Hgt) / static_cast<double>(picture.Wdt);
704 dest_width = ICON_SIZE;
705 dest_height = dest_width * factor;
706 }
707 else
708 {
709 double factor = static_cast<double>(picture.Wdt) / static_cast<double>(picture.Hgt);
710 dest_height = ICON_SIZE;
711 dest_width = dest_height * factor;
712 }
713
714 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(src: pixbuf, dest_width, dest_height, interp_type: GDK_INTERP_HYPER);
715 g_object_unref(G_OBJECT(pixbuf));
716 pixbuf = scaled;
717
718 g_hash_table_insert(hash_table: table, key, value: pixbuf);
719 }
720
721 g_object_set(G_OBJECT(renderer), first_property_name: "pixbuf", pixbuf, nullptr);
722}
723
724#undef ICON_SIZE
725
726void C4ObjectListDlg::Open()
727{
728 // Create Window if necessary
729 if (window == nullptr)
730 {
731 // The Windows
732 window = gtk_window_new(type: GTK_WINDOW_TOPLEVEL);
733
734 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
735 gtk_window_set_type_hint(GTK_WINDOW(window), hint: GDK_WINDOW_TYPE_HINT_UTILITY);
736 gtk_window_set_role(GTK_WINDOW(window), role: "objectlist");
737 gtk_window_set_title(GTK_WINDOW(window), title: "Objects");
738 gtk_window_set_default_size(GTK_WINDOW(window), width: 180, height: 300);
739
740 gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(Console.window));
741
742 g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(OnDestroy), this);
743
744 // The VBox and Tree
745 GtkWidget *vbox = gtk_box_new(orientation: GTK_ORIENTATION_VERTICAL, spacing: 8);
746
747 GtkWidget *scrolled_wnd = gtk_scrolled_window_new(hadjustment: nullptr, vadjustment: nullptr);
748 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_wnd), hscrollbar_policy: GTK_POLICY_AUTOMATIC, vscrollbar_policy: GTK_POLICY_AUTOMATIC);
749 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_wnd), type: GTK_SHADOW_IN);
750
751 model = G_OBJECT(c4_list_new());
752
753 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
754
755 g_object_unref(object: model); /* destroy store automatically with view */
756
757 GtkTreeViewColumn *col = gtk_tree_view_column_new();
758 GtkCellRenderer *renderer;
759
760 renderer = gtk_cell_renderer_pixbuf_new();
761 gtk_tree_view_column_pack_start(tree_column: col, cell: renderer, FALSE);
762 gtk_tree_view_column_set_cell_data_func(tree_column: col, cell_renderer: renderer, func: icon_cell_data_func, func_data: g_hash_table_new_full(hash_func: nullptr, key_equal_func: nullptr, key_destroy_func: nullptr, value_destroy_func: (GDestroyNotify)g_object_unref), destroy: (GDestroyNotify)g_hash_table_unref);
763
764 renderer = gtk_cell_renderer_text_new();
765 gtk_tree_view_column_pack_start(tree_column: col, cell: renderer, TRUE);
766 gtk_tree_view_column_set_cell_data_func(tree_column: col, cell_renderer: renderer, func: name_cell_data_func, func_data: nullptr, destroy: nullptr);
767
768 gtk_tree_view_column_set_title(tree_column: col, title: "Name");
769 gtk_tree_view_column_set_sizing(tree_column: col, type: GTK_TREE_VIEW_COLUMN_FIXED);
770 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column: col);
771
772 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
773 gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
774
775 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
776 gtk_tree_selection_set_mode(selection, type: GTK_SELECTION_MULTIPLE);
777
778 g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(OnSelectionChanged), this);
779
780 gtk_container_add(GTK_CONTAINER(scrolled_wnd), widget: treeview);
781 gtk_box_pack_start(GTK_BOX(vbox), child: scrolled_wnd, TRUE, TRUE, padding: 0);
782
783 gtk_container_add(GTK_CONTAINER(window), widget: vbox);
784
785 gtk_widget_show_all(widget: window);
786 }
787}
788
789#else
790
791C4ObjectListDlg::C4ObjectListDlg() {}
792
793C4ObjectListDlg::~C4ObjectListDlg() {}
794
795void C4ObjectListDlg::Execute() {}
796
797void C4ObjectListDlg::Open() {}
798
799void C4ObjectListDlg::Update(C4ObjectList &rSelection) {}
800
801void C4ObjectListDlg::OnObjectRemove(C4ObjectList *pList, C4ObjectLink *pLnk) {}
802
803void C4ObjectListDlg::OnObjectAdded(C4ObjectList *pList, C4ObjectLink *pLnk) {}
804
805void C4ObjectListDlg::OnObjectRename(C4ObjectList *pList, C4ObjectLink *pLnk) {}
806
807#endif
808
809C4ObjectListChangeListener &ObjectListChangeListener = Console.ObjectListDlg;
810