root/GtkRadiant/trunk/radiant/groupdialog.cpp

Revision 300, 50.8 kB (checked in by mattn, 6 months ago)

* fixed spawnflags dialog due to local and global variable mix (thanks divVerent)

  • Property svn:eol-style set to native
Line 
1 /*
2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 //
23 // Floating dialog that contains a notebook with at least Entities and Group tabs
24 // I merged the 2 MS Windows dialogs in a single class
25 //
26 // Leonardo Zide (leo@lokigames.com)
27 //
28
29 #ifndef _WIN32
30   #include <unistd.h>
31 #endif
32 #include <gdk/gdkkeysyms.h>
33 #include <glib/gi18n.h>
34 #include "stdafx.h"
35 #include "groupdialog.h"
36
37 GtkWidget*      EntWidgets[EntLast];
38 GtkListStore* g_entlist_store;
39 GtkListStore* g_entprops_store;
40 int                                     inspector_mode;         // W_TEXTURE, W_ENTITY, or W_CONSOLE
41 qboolean                multiple_entities;
42 qboolean                disable_spawn_get = false;
43 entity_t                *edit_entity;
44 /*
45 static GdkPixmap *tree_pixmaps[7];
46 static GdkBitmap *tree_masks[7];
47 */
48 #define IMG_PATCH 0
49 #define IMG_BRUSH 1
50 #define IMG_GROUP 2
51 #define IMG_ENTITY 3
52 #define IMG_ENTITYGROUP 4
53 #define IMG_MODEL 5
54 #define IMG_SCRIPT 6
55
56 // misc group support
57 #define MAX_GROUPS 4096
58 #define GROUP_DELIMETER '@'
59 #define GROUPNAME "QER_Group_%i"
60
61 GroupDlg g_wndGroup;
62 GroupDlg *g_pGroupDlg = &g_wndGroup;
63
64 // group_t are loaded / saved through "group_info" entities
65 // they hold epairs for group settings and additionnal access info (tree nodes)
66 group_t *g_pGroups = NULL;
67
68 // the number of active spawnflags
69 static int spawnflag_count;
70 // table: index, match spawnflag item to the spawnflag index (i.e. which bit)
71 static int spawn_table[MAX_FLAGS];
72 // we change the layout depending on how many spawn flags we need to display
73 // the table is a 4x4 in which we need to put the comment box EntWidgets[EntComment] and the spawn flags..
74 static GtkWidget *LayoutTable;
75 // 0: none of them are hooked
76 // 1: only the text, 2: text and four checks, 3: text and 8 checks
77 static int widget_state = 0;
78
79 static void entity_check (GtkWidget *widget, gpointer data);
80
81 // =============================================================================
82 // Global functions
83
84 /*
85 ===============================================================
86
87 ENTITY WINDOW
88
89 ===============================================================
90 */
91
92 void FillClassList ()
93 {
94   GtkListStore* store = g_entlist_store;
95
96   gtk_list_store_clear(store);
97
98   for (eclass_t* e = eclass ; e ; e = e->next)
99   {
100     GtkTreeIter iter;
101     gtk_list_store_append(store, &iter);
102     gtk_list_store_set(store, &iter, 0, e->name, 1, e, -1);
103   }
104 }
105
106 // SetKeyValuePairs
107 //
108 // Reset the key/value (aka property) listbox and fill it with the
109 // k/v pairs from the entity being edited.
110 //
111
112 void SetKeyValuePairs (bool bClearMD3)
113 {
114   GtkListStore* store = g_entprops_store;
115
116   gtk_list_store_clear(store);
117
118   if (edit_entity == NULL)
119   {
120     // if there's no entity, then display no key/values
121     return;
122   }
123
124   // save current key/val pair around filling epair box
125   // row_select wipes it and sets to first in list
126   Str strKey = gtk_entry_get_text (GTK_ENTRY (EntWidgets[EntKeyField]));
127   Str strVal = gtk_entry_get_text (GTK_ENTRY (EntWidgets[EntValueField]));
128
129
130   // Walk through list and add pairs
131   for(epair_t* epair = edit_entity->epairs ; epair ; epair = epair->next)
132   {
133     GtkTreeIter iter;
134     gtk_list_store_append(store, &iter);
135     gtk_list_store_set(store, &iter, 0, epair->key, 1, epair->value, -1);
136   }
137
138   gtk_entry_set_text (GTK_ENTRY (EntWidgets[EntKeyField]), strKey.GetBuffer());
139   gtk_entry_set_text (GTK_ENTRY (EntWidgets[EntValueField]), strVal.GetBuffer());
140
141   Sys_UpdateWindows(W_CAMERA | W_XY);
142 }
143
144 // SetSpawnFlags
145 //
146 // Update the checkboxes to reflect the flag state of the entity
147 //
148 void SetSpawnFlags(void)
149 {
150   int f, i, v;
151
152         disable_spawn_get = true;
153
154   f = atoi(ValueForKey (edit_entity, "spawnflags"));
155   for (i=0 ; i<spawnflag_count ; i++)
156   {
157     v = !!(f&(1<<spawn_table[i]));
158     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (EntWidgets[EntCheck1+i]), v);
159   }
160   // take care of the remaining ones
161   for (i=spawnflag_count ; i<MAX_FLAGS ; i++)
162   {
163     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (EntWidgets[EntCheck1+i]), FALSE);
164   }
165
166         disable_spawn_get = false;
167 }
168
169 // GetSpawnFlags
170 //
171 // Update the entity flags to reflect the state of the checkboxes
172 //
173 // NOTE: this function had a tendency to add "spawnflags" "0" on most entities
174 //   if this wants to set spawnflags to zero, remove the key
175
176 void GetSpawnFlags(void)
177 {
178   int f, i, v;
179   char sz[32];
180
181   f = 0;
182   for (i=0 ; i<spawnflag_count ; i++)
183   {
184     v = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (EntWidgets[EntCheck1+i]));
185     f |= v<<spawn_table[i];
186   }
187
188   if (f==0)
189   {
190     // remove all "spawnflags" keys
191     if (multiple_entities)
192     {
193       brush_t   *b;
194
195       for (b=selected_brushes.next ; b != &selected_brushes ; b=b->next)
196         DeleteKey (b->owner, "spawnflags");
197     }
198     else
199       DeleteKey (edit_entity, "spawnflags");
200   }
201   else
202   {
203     sprintf (sz, "%i", f);
204     if (multiple_entities)
205     {
206       brush_t   *b;
207
208       for (b=selected_brushes.next ; b != &selected_brushes ; b=b->next)
209         SetKeyValue(b->owner, "spawnflags", sz);
210     }
211     else
212       SetKeyValue (edit_entity, "spawnflags", sz);
213   }
214   SetKeyValuePairs ();
215 }
216
217 //#define DBG_UPDATESEL
218
219 // UpdateSel
220 //
221 // Update the listbox, checkboxes and k/v pairs to reflect the new selection
222 // iIndex is the index in the list box with the class name, -1 if not found
223 bool UpdateSel(int iIndex, eclass_t *pec)
224 {
225   int i, next_state;
226   brush_t *b;
227
228   // syndrom of crappy code, we may get into stack overflowing crap with this function and Gtk
229   // if we play with the list of entity classes
230   // using a static flag to prevent recursion
231   static bool bBlockUpdate = false;
232
233   if (bBlockUpdate)
234     return FALSE; // NOTE TTimo wtf is the return value for anyway?
235
236 #ifdef DBG_UPDATESEL
237   Sys_FPrintf(SYS_WRN, "UpdateSel\n");
238 #endif
239
240   if (selected_brushes.next == &selected_brushes)
241   {
242     edit_entity = world_entity;
243     multiple_entities = false;
244   }
245   else
246   {
247     edit_entity = selected_brushes.next->owner;
248     for (b=selected_brushes.next->next ; b != &selected_brushes ; b=b->next)
249     {
250       if (b->owner != edit_entity)
251       {
252         multiple_entities = true;
253         break;
254       }
255     }
256   }
257
258   if (iIndex != -1)
259   {
260 #ifdef DBG_UPDATESEL
261     Sys_FPrintf(SYS_WRN,"Setting focus_row to %d\n", iIndex);
262 #endif
263     bBlockUpdate = true;
264
265     GtkTreeView* view = GTK_TREE_VIEW(EntWidgets[EntList]);
266     GtkTreePath* path = gtk_tree_path_new();
267     gtk_tree_path_append_index(path, iIndex);
268     gtk_tree_selection_select_path(gtk_tree_view_get_selection(view), path);
269     gtk_tree_view_scroll_to_cell(view, path, NULL, FALSE, 0, 0);
270     gtk_tree_path_free(path);
271
272     bBlockUpdate = false;
273   }
274
275   if (pec == NULL)
276     return TRUE;
277
278   // Set up the description
279   {
280     GtkTextBuffer* buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(EntWidgets[EntComment]));
281     gtk_text_buffer_set_text (buffer, pec->comments, -1);
282   }
283
284   spawnflag_count = 0;
285
286   // do a first pass to count the spawn flags, don't touch the widgets, we don't know in what state they are
287   for (i=0 ; i<MAX_FLAGS ; i++)
288   {
289     if (pec->flagnames[i] && pec->flagnames[i][0] != 0 && strcmp(pec->flagnames[i],"-"))
290     {
291       spawn_table[spawnflag_count] = i;
292       spawnflag_count++;
293     }
294   }
295
296   // what's new widget state
297   if (spawnflag_count==0)
298     next_state = 1;
299   else if (spawnflag_count<=4)
300     next_state = 2;
301   else if (spawnflag_count<=8)
302     next_state = 3;
303   else if (spawnflag_count<=12)
304     next_state = 4;
305   else
306     next_state = 5;
307   widget_state = next_state;
308   static int last_count = 0;
309
310   // disable all remaining boxes
311   // NOTE: these boxes might not even be on display
312   for (i = 0; i < last_count; i++)
313   {
314     GtkWidget* widget = EntWidgets[EntCheck1+i];
315     gtk_label_set_text (GTK_LABEL (GTK_BIN (widget)->child), " ");
316     gtk_widget_hide (widget);
317     gtk_widget_ref (widget);
318     gtk_container_remove (GTK_CONTAINER (LayoutTable), widget);
319   }
320   last_count = spawnflag_count;
321
322   for (i=0 ; i<spawnflag_count ; i++)
323   {
324     GtkWidget* widget = EntWidgets[EntCheck1+i];
325     gtk_widget_show (widget);
326
327     Str str;
328     str = pec->flagnames[spawn_table[i]];
329     str.MakeLower ();
330
331 //    gtk_table_attach (GTK_TABLE (LayoutTable), widget, i%4, i%4+1, i/4, i/4+1,
332     gtk_table_attach (GTK_TABLE (LayoutTable), widget, i%4, i%4+1, i/4, i/4+1,
333                       (GtkAttachOptions) (GTK_FILL),
334                       (GtkAttachOptions) (GTK_FILL), 0, 0);
335     gtk_widget_unref (widget);
336
337     gtk_label_set_text (GTK_LABEL (GTK_BIN (widget)->child), str.GetBuffer ());
338   }
339
340   SetSpawnFlags();
341
342   SetKeyValuePairs();
343
344   return TRUE;
345 }
346
347 bool UpdateEntitySel(eclass_t *pec)
348 {
349 #ifdef DBG_UPDATESEL
350   Sys_FPrintf(SYS_WRN, "UpdateEntitySel\n");
351 #endif
352
353   GtkTreeModel* model = GTK_TREE_MODEL(g_entlist_store);
354   GtkTreeIter iter;
355   unsigned int i = 0;
356   for(gboolean good = gtk_tree_model_get_iter_first(model, &iter); good != FALSE; good = gtk_tree_model_iter_next(model, &iter))
357   {
358     char* text;
359     gtk_tree_model_get(model, &iter, 0, &text, -1);
360     if (strcmp (text, pec->name) == 0)
361     {
362 #ifdef DBG_UPDATESEL
363       Sys_FPrintf(SYS_WRN, "found a match: %d %s\n", i, pec->name);
364 #endif
365       return UpdateSel (i, pec);
366     }
367     g_free(text);
368     ++i;
369   }
370   return UpdateSel (-1, pec);
371 }
372
373 // CreateEntity
374 //
375 // Creates a new entity based on the currently selected brush and entity type.
376 //
377
378 void CreateEntity(void)
379 {
380   GtkTreeView* view = GTK_TREE_VIEW(EntWidgets[EntList]);
381
382   // check to make sure we have a brush
383   if (selected_brushes.next == &selected_brushes)
384   {
385     gtk_MessageBox(g_pParentWnd->m_pWidget, "You must have a selected brush to create an entity", "info");
386     return;
387   }
388
389   // find out what type of entity we are trying to create
390   GtkTreeModel* model;
391   GtkTreeIter iter;
392   if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(view), &model, &iter) == FALSE)
393   {
394     gtk_MessageBox (g_pParentWnd->m_pWidget, "You must have a selected class to create an entity", "info");
395     return;
396   }
397
398   char* text;
399   gtk_tree_model_get(model, &iter, 0, &text, -1);
400   CreateEntityFromName(text, vec3_origin);
401   g_free(text);
402
403   if (selected_brushes.next == &selected_brushes)
404     edit_entity = world_entity;
405   else
406     edit_entity = selected_brushes.next->owner;
407
408   SetKeyValuePairs();
409   Select_Deselect ();
410   Select_Brush (edit_entity->brushes.onext);
411   Sys_UpdateWindows(W_ALL);
412 }
413
414 /*
415 ===============
416 AddProp
417
418 ===============
419 */
420 void AddProp()
421 {
422   if (edit_entity == NULL)
423     return;
424
425   // Get current selection text
426   const char* key = gtk_entry_get_text (GTK_ENTRY (EntWidgets[EntKeyField]));
427   const char* value = gtk_entry_get_text (GTK_ENTRY (EntWidgets[EntValueField]));
428
429
430   // TTimo: if you change the classname to worldspawn you won't merge back in the structural brushes but create a parasite entity
431   if (!strcmp(key, "classname") && !strcmp(value, "worldspawn"))
432   {
433     gtk_MessageBox(g_pParentWnd->m_pWidget,  "Cannot change \"classname\" key back to worldspawn.", NULL, MB_OK );
434     return;
435   }
436
437
438         // RR2DO2: we don't want spaces in entity keys
439         if (strstr( key, " " ))
440         {
441     gtk_MessageBox(g_pParentWnd->m_pWidget, "No spaces are allowed in entity keys.", NULL, MB_OK );
442     return;
443         }
444
445   if (multiple_entities)
446   {
447     brush_t *b;
448
449     for (b=selected_brushes.next ; b != &selected_brushes ; b=b->next)
450       SetKeyValue(b->owner, key, value);
451   }
452   else
453     SetKeyValue(edit_entity, key, value);
454
455   // refresh the prop listbox
456   SetKeyValuePairs();
457
458
459 #ifdef USEPLUGINENTITIES
460   // if it's a plugin entity, perhaps we need to update some drawing parameters
461   // NOTE: perhaps moving this code to a seperate func would help if we need it in other places
462   // TODO: we need to call some update func in the IPluginEntity in case model name changes etc.
463   // ( for the moment only bounding brush is updated ), see UpdateModelBrush in Ritual's Q3Radiant
464   if (edit_entity->eclass->nShowFlags & ECLASS_PLUGINENTITY)
465   {
466     vec3_t mins, maxs;
467     edit_entity->pPlugEnt->GetBounds( mins, maxs );
468     // replace old bounding brush by newly computed one
469     // NOTE: this part is similar to Entity_BuildModelBrush in Ritual's Q3Radiant, it can be
470     // usefull moved into a seperate func
471     brush_t *b,*oldbrush;
472     if (edit_entity->brushes.onext != &edit_entity->brushes)
473       oldbrush = edit_entity->brushes.onext;
474     b = Brush_Create (mins, maxs, &edit_entity->eclass->texdef);
475     Entity_LinkBrush (edit_entity, b);
476     Brush_Build( b, true );
477     Select_Deselect();
478     Brush_AddToList (edit_entity->brushes.onext, &selected_brushes);
479     if (oldbrush)
480       Brush_Free( oldbrush );
481   }
482 #endif // USEPLUGINENTITIES
483 }
484
485 /*
486 ===============
487 DelProp
488
489 ===============
490 */
491 void DelProp(void)
492 {
493   if (edit_entity == NULL)
494     return;
495
496   // Get current selection text
497   const char* key = gtk_entry_get_text (GTK_ENTRY (EntWidgets[EntKeyField]));
498
499   if (multiple_entities)
500   {
501     brush_t *b;
502
503     for (b=selected_brushes.next ; b != &selected_brushes ; b=b->next)
504       DeleteKey(b->owner, key);
505   }
506   else
507     DeleteKey(edit_entity, key);
508
509   // refresh the prop listbox
510   SetKeyValuePairs();
511 }
512
513 void ResetEntity ()
514 {
515   epair_t *pep;
516   int i;
517
518   if (edit_entity == NULL)
519     return;
520
521   if (multiple_entities)
522   {
523     brush_t *b;
524
525     for (b=selected_brushes.next; b != &selected_brushes; b=b->next)
526       for (pep = b->owner->epairs; pep; )
527       {
528         if (strcmp (pep->key, "classname") != 0)
529         {
530           DeleteKey (b->owner, pep->key);
531           pep = b->owner->epairs;
532         }
533         else
534           pep = pep->next;
535       }
536   }
537   else
538     for (pep = edit_entity->epairs; pep; )
539     {
540       if (strcmp (pep->key, "classname") != 0)
541       {
542         DeleteKey (edit_entity, pep->key);
543         pep = edit_entity->epairs;
544       }
545       else
546         pep = pep->next;
547     }
548
549   // refresh the dialog
550   SetKeyValuePairs ();
551   for (i = EntCheck1; i <= EntCheck16; i++)
552     gtk_signal_handler_block_by_func (GTK_OBJECT (EntWidgets[i]), GTK_SIGNAL_FUNC (entity_check), NULL);
553   SetSpawnFlags ();
554   for (i = EntCheck1; i <= EntCheck16; i++)
555     gtk_signal_handler_unblock_by_func (GTK_OBJECT (EntWidgets[i]), GTK_SIGNAL_FUNC (entity_check), NULL);
556 }
557
558 bool GetSelectAllCriteria(CString &strKey, CString &strVal)
559 {
560   GtkTreeModel* model;
561   GtkTreeIter iter;
562   if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(EntWidgets[EntProps])), &model, &iter)
563     && (inspector_mode == W_ENTITY)
564     && GTK_WIDGET_VISIBLE (g_pGroupDlg->m_pWidget))
565   {
566     strKey = gtk_entry_get_text (GTK_ENTRY (EntWidgets[EntKeyField]));
567     strVal = gtk_entry_get_text (GTK_ENTRY (EntWidgets[EntValueField]));
568     return TRUE;
569   }
570   return FALSE;
571 }
572
573
574 void AssignSound()
575 {
576   char buffer[NAME_MAX];
577
578   strcpy (buffer, g_qeglobals.m_strHomeMaps.GetBuffer());
579   strcat (buffer, "sound/");
580
581   if( access(buffer, R_OK) != 0 )
582   {
583     // just go to fsmain
584     strcpy (buffer, g_qeglobals.m_strHomeMaps.GetBuffer());
585     strcat (buffer, "/");
586   }
587
588   const char *filename = file_dialog (g_pGroupDlg->m_pWidget, TRUE, _("Open Wav File"), buffer, "sound");
589   if (filename != NULL)
590   {
591     gtk_entry_set_text (GTK_ENTRY (EntWidgets[EntKeyField]), "noise");
592     char *aux = vfsExtractRelativePath (filename);
593     CString str;
594     if (aux)
595       str = aux;
596     else
597     {
598       Sys_FPrintf (SYS_WRN, "WARNING: could not extract the relative path, using full path instead\n");
599       str = filename;
600     }
601
602     gtk_entry_set_text (GTK_ENTRY (EntWidgets[EntValueField]), str.GetBuffer());
603     AddProp();
604   }
605 }
606
607 void AssignModel()
608 {
609   char buffer[NAME_MAX];
610
611   strcpy (buffer, g_qeglobals.m_strHomeMaps.GetBuffer());
612   strcat (buffer, "models/");
613
614   if( access(buffer, R_OK) != 0 )
615   {
616     // just go to fsmain
617     strcpy (buffer, g_qeglobals.m_strHomeMaps.GetBuffer());
618     strcat (buffer, "/");
619   }
620
621   const char *filename = file_dialog (g_pGroupDlg->m_pWidget, TRUE, _("Open Model"), buffer, MODEL_MAJOR);
622   if (filename != NULL)
623   {
624     gtk_entry_set_text (GTK_ENTRY (EntWidgets[EntKeyField]), "model");
625     // use VFS to get the correct relative path
626     char *aux = vfsExtractRelativePath (filename);
627     CString str;
628     if (aux)
629       str = aux;
630     else
631     {
632       Sys_FPrintf (SYS_WRN, "WARNING: could not extract the relative path, using full path instead\n");
633       str = filename;
634     }
635
636     gtk_entry_set_text (GTK_ENTRY (EntWidgets[EntValueField]), str.GetBuffer());
637     AddProp();
638     edit_entity->brushes.onext->bModelFailed = false;
639   }
640 }
641
642 /*
643 ==============
644 SetInspectorMode
645 ==============
646 */
647 void SetInspectorMode(int iType)
648 {
649   if (iType == W_GROUP)
650     gtk_MessageBox(g_pParentWnd->m_pWidget, "Brush grouping is not functional yet", NULL, MB_OK | MB_ICONWARNING );
651
652   if (!g_pParentWnd->FloatingGroupDialog() &&
653       (iType == W_TEXTURE || iType == W_CONSOLE))
654     return;
655
656   // Is the caller asking us to cycle to the next window?
657   if (iType == -1)
658   {
659     if (inspector_mode == W_ENTITY)
660       iType = W_TEXTURE;
661     else if (inspector_mode == W_TEXTURE)
662       iType = W_CONSOLE;
663     else if (inspector_mode == W_CONSOLE)
664       iType = W_GROUP;
665     else
666       iType = W_ENTITY;
667   }
668
669   switch(iType)
670   {
671   case W_ENTITY:
672     // entity is always first in the inspector
673     gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_entity), "Entities");
674     gtk_notebook_set_page (GTK_NOTEBOOK (g_pGroupDlg->m_pNotebook), 0);
675     break;
676
677   case W_TEXTURE:
678     g_pParentWnd->GetTexWnd()->FocusEdit();
679     gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_entity), "Textures");
680     if (g_pParentWnd->FloatingGroupDialog())
681       gtk_notebook_set_page (GTK_NOTEBOOK (g_pGroupDlg->m_pNotebook), 1);
682     break;
683
684   case W_CONSOLE:
685     gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_entity), "Console");
686     if (g_pParentWnd->FloatingGroupDialog())
687       gtk_notebook_set_page (GTK_NOTEBOOK (g_pGroupDlg->m_pNotebook), 2);
688     break;
689
690   case W_GROUP:
691     if (g_pParentWnd->FloatingGroupDialog())
692       gtk_notebook_set_page (GTK_NOTEBOOK (g_pGroupDlg->m_pNotebook), 3);
693     else
694       gtk_notebook_set_page (GTK_NOTEBOOK (g_pGroupDlg->m_pNotebook), 1);
695     break;
696
697   default:
698     break;
699   }
700 }
701
702 void Group_Add(entity_t *e)
703 {
704   /*
705   group_t *g = (group_t*)qmalloc(sizeof(group_t));
706   g->epairs = e->epairs;
707   g->next = NULL;
708   e->epairs = NULL;
709
710   // create a new group node
711   char *text = ValueForKey(g->epairs, "group");
712   g->itemOwner = gtk_ctree_insert_node (GTK_CTREE (g_wndGroup.m_pTree), g_wndGroup.m_hWorld, NULL, &text, 0,
713                                         tree_pixmaps[IMG_GROUP], tree_masks[IMG_GROUP],
714                                         tree_pixmaps[IMG_GROUP], tree_masks[IMG_GROUP], TRUE, TRUE);
715   g->next = g_pGroups;
716   g_pGroups = g;
717   */
718 }
719 /*
720 group_t* Group_Alloc(char *name)
721 {
722   group_t *g = (group_t*)qmalloc(sizeof(group_t));
723   SetKeyValue( g->epairs, "group", name );
724   return g;
725 }
726
727 group_t* Group_ForName(const char * name)
728 {
729   group_t *g = g_pGroups;
730   while (g != NULL)
731   {
732     if (strcmp( ValueForKey(g->epairs,"group"), name ) == 0)
733       break;
734     g = g->next;
735   }
736   return g;
737 }
738
739 void Group_AddToItem(brush_t *b, GtkCTreeNode* item)
740 {
741   int nImage = IMG_BRUSH;
742   if (!g_qeglobals.m_bBrushPrimitMode)
743   {
744     return;
745   }
746   const char *pName = NULL;
747   //  const char *pNamed = Brush_GetKeyValue(b, "name");
748
749   if (!b->owner || (b->owner == world_entity))
750   {
751     if (b->patchBrush)
752     {
753       pName = "Generic Patch";
754       nImage = IMG_PATCH;
755     }
756     else
757     {
758       pName = "Generic Brush";
759       nImage = IMG_BRUSH;
760     }
761   }
762   else
763   {
764     pName = b->owner->eclass->name;
765     if (b->owner->eclass->fixedsize)
766     {
767       nImage = IMG_ENTITY;
768     }
769     else
770     {
771       nImage = IMG_ENTITYGROUP;
772     }
773   }
774
775   GtkCTreeNode *newItem;
776   int i = (b->patchBrush) ? IMG_PATCH : IMG_BRUSH;
777   newItem = gtk_ctree_insert_node (GTK_CTREE (g_wndGroup.m_pTree), item, NULL, (gchar**)&pName, 0,
778                                    tree_pixmaps[i], tree_masks[i], tree_pixmaps[i],
779                                    tree_masks[i], TRUE, TRUE);
780   gtk_ctree_node_set_row_data (GTK_CTREE (g_wndGroup.m_pTree), newItem, b);
781   b->itemOwner = newItem;
782 }
783 */
784 void Group_RemoveBrush(brush_t *b)
785 {
786   /*
787   if (!g_qeglobals.m_bBrushPrimitMode)
788   {
789     return;
790   }
791   if (b->itemOwner)
792   {
793     gtk_ctree_remove_node (GTK_CTREE (g_pGroupDlg->m_pTree), b->itemOwner);
794     b->itemOwner = NULL;
795   }
796   DeleteKey(b->epairs, "group");
797   */
798 }
799 /*
800 void Group_AddToWorld(brush_t *b)
801 {
802   if (!g_qeglobals.m_bBrushPrimitMode)
803   {
804     return;
805   }
806   GtkCTreeNode *parent = gtk_ctree_node_nth (GTK_CTREE (g_pGroupDlg->m_pTree), 0);
807   Group_AddToItem(b, parent);
808 }
809 */
810 void Group_AddToProperGroup(brush_t *b)
811 {
812   /*
813   if (!g_qeglobals.m_bBrushPrimitMode)
814   {
815     return;
816   }
817
818   // NOTE: we do a local copy of the "group" key because it gets erased by Group_RemoveBrush
819   const char *pGroup = Brush_GetKeyValue(b, "group");
820   // remove the entry in the tree if there's one
821   if (b->itemOwner)
822   {
823     gtk_ctree_remove_node (GTK_CTREE (g_pGroupDlg->m_pTree), b->itemOwner);
824     b->itemOwner = NULL;
825   }
826
827   if (*pGroup != 0)
828   {
829     // find the item
830     group_t *g = Group_ForName(pGroup);
831     if (g)
832       Group_AddToItem(b, g->itemOwner);
833 #ifdef _DEBUG
834     else
835       Sys_Printf("WARNING: unexpected Group_ForName not found in Group_AddToProperGroup\n");
836 #endif
837   }
838   else
839   {
840     Group_AddToWorld(b);
841   }
842   */
843 }
844 /*
845 void Group_AddToSelected(brush_t *b)
846 {
847   if (!g_qeglobals.m_bBrushPrimitMode)
848   {
849     return;
850   }
851   GtkCTreeNode *item;
852   item = gtk_ctree_node_nth (GTK_CTREE (g_pGroupDlg->m_pTree), GTK_CLIST (g_pGroupDlg->m_pTree)->focus_row);
853   if (item == NULL)
854   {
855     item = gtk_ctree_node_nth (GTK_CTREE (g_pGroupDlg->m_pTree), 0);
856   }
857   Group_AddToItem(b, item);
858 }
859 */
860 /*
861 void Group_Save(FILE *f)
862 {
863   group_t *g = g_pGroups;
864   while (g)
865   {
866     fprintf(f,"{\n\"classname\" \"group_info\"\n\"group\" \"%s\"\n}\n", ValueForKey( g->epairs, "group" ));
867     g = g->next;
868   }
869 }
870 */
871
872 void Group_Init()
873 {
874   if (!g_qeglobals.m_bBrushPrimitMode)
875   {
876     return;
877   }
878   // start by cleaning everything
879   // clean the groups
880   //++timo FIXME: we leak, delete the groups on the way (I don't have time to do it now)
881 #ifdef _DEBUG
882   Sys_Printf("TODO: fix leak in Group_Init\n");
883 #endif
884   group_t *g = g_pGroups;
885   while (g)
886   {
887     epair_t *ep,*enext;
888     for (ep = g->epairs ; ep ; ep=enext )
889     {
890       enext = ep->next;
891       free (ep->key);
892       free (ep->value);
893       free (ep);
894     }
895     g = g->next;
896   }
897   /*
898   GtkCTreeNode *world;
899   char *text = "World";
900   g_pGroups = NULL;
901   gtk_clist_clear (GTK_CLIST (g_wndGroup.m_pTree));
902   world = gtk_ctree_insert_node (GTK_CTREE (g_wndGroup.m_pTree), NULL, NULL, &text, 0,
903                                  tree_pixmaps[IMG_GROUP], tree_masks[IMG_GROUP], tree_pixmaps[IMG_GROUP],
904                                  tree_masks[IMG_GROUP], FALSE, TRUE);
905   */
906   // walk through all the brushes, remove the itemOwner key and add them back where they belong
907   brush_t *b;
908   for (b = active_brushes.next; b != &active_brushes; b = b->next)
909   {
910     b->itemOwner = NULL;
911     Group_AddToProperGroup(b);
912   }
913   for (b = selected_brushes.next ; b != &selected_brushes ; b = b->next)
914   {
915     b->itemOwner = NULL;
916     Group_AddToProperGroup(b);
917   }
918 }
919 /*
920 // scan through world_entity for groups in this map?
921 // we use GROUPNAME "QER_group_%i" to look for existing groups and their naming
922 //++timo FIXME: is this actually needed for anything?
923 void Group_GetListFromWorld(GSList **pArray)
924 {
925   if (!g_qeglobals.m_bBrushPrimitMode)
926   {
927     return;
928   }
929
930   if (world_entity == NULL)
931   {
932     return;
933   }
934
935   char cBuff[1024];
936   for (int i =0; i < MAX_GROUPS; i++)
937   {
938     sprintf(cBuff, GROUPNAME, i);
939     char *pGroup = ValueForKey(world_entity, cBuff);
940     if (pGroup && strlen(pGroup) > 0)
941     {
942       *pArray = g_slist_append (*pArray, g_strdup (pGroup));
943     }
944     else
945     {
946       break;
947     }
948   }
949 }
950
951 void Group_RemoveListFromWorld()
952 {
953   if (!g_qeglobals.m_bBrushPrimitMode)
954   {
955     return;
956   }
957   GSList* array = NULL;
958   Group_GetListFromWorld(&array);
959
960   while (array)
961   {
962     DeleteKey(world_entity, (char*)array->data);
963     g_free (array->data);
964     array = g_slist_remove (array, array->data);
965   }
966 }
967
968 int CountChar(const char *p, char c)
969 {
970   int nCount = 0;
971   int nLen = strlen(p)-1;
972   while (nLen-- >= 0)
973   {
974     if (p[nLen] == c)
975     {
976       nCount++;
977     }
978   }
979   return nCount;
980 }
981 */
982 // =============================================================================
983 // callbacks
984
985 static void eclasslist_selection_changed(GtkTreeSelection* selection, gpointer data)
986 {
987   GtkTreeModel* model;
988   GtkTreeIter selected;
989   // no world entity, we are not ready yet
990   if( !world_entity ) {
991     return;
992   }
993   if(gtk_tree_selection_get_selected(selection, &model, &selected))
994   {
995     eclass_t* eclass;
996     gtk_tree_model_get(model, &selected, 1, &eclass, -1);
997     if(eclass != NULL)
998     {
999       GtkTreePath* path = gtk_tree_model_get_path(model, &selected);
1000       UpdateSel(gtk_tree_path_get_indices(path)[0], eclass);
1001       gtk_tree_path_free(path);
1002     }
1003   }
1004 }
1005
1006 static gint eclasslist_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
1007 {
1008   if (event->type == GDK_2BUTTON_PRESS)
1009   {
1010     CreateEntity ();
1011     return TRUE;
1012   }
1013   return FALSE;
1014