minor refactoring
[xonotic/netradiant.git] / radiant / entitylist.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
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 #include "entitylist.h"
23
24 #include "iselection.h"
25
26 #include <gtk/gtktreemodel.h>
27 #include <gtk/gtktreeview.h>
28 #include <gtk/gtktreeselection.h>
29 #include <gtk/gtkcellrenderertext.h>
30
31 #include "string/string.h"
32 #include "scenelib.h"
33 #include "nameable.h"
34 #include "signal/isignal.h"
35 #include "generic/object.h"
36
37 #include "gtkutil/widget.h"
38 #include "gtkutil/window.h"
39 #include "gtkutil/idledraw.h"
40 #include "gtkutil/accelerator.h"
41 #include "gtkutil/closure.h"
42
43 #include "treemodel.h"
44
45 void RedrawEntityList();
46 typedef FreeCaller<RedrawEntityList> RedrawEntityListCaller;
47
48 typedef struct _GtkTreeView GtkTreeView;
49
50 class EntityList
51 {
52 public:
53   enum EDirty
54   {
55     eDefault,
56     eSelection,
57     eInsertRemove,
58   };
59
60   EDirty m_dirty;
61
62   IdleDraw m_idleDraw;
63   WindowPositionTracker m_positionTracker;
64
65   GtkWindow* m_window;
66   GtkTreeView* m_tree_view;
67   GraphTreeModel* m_tree_model;
68   bool m_selection_disabled;
69
70   EntityList() :
71     m_dirty(EntityList::eDefault),
72     m_idleDraw(RedrawEntityListCaller()),
73     m_window(0),
74     m_selection_disabled(false)
75   {
76   }
77
78   bool visible() const
79   {
80     return GTK_WIDGET_VISIBLE(GTK_WIDGET(m_window));
81   }
82 };
83
84 namespace
85 {
86   EntityList* g_EntityList;
87
88   inline EntityList& getEntityList()
89   {
90     ASSERT_NOTNULL(g_EntityList);
91     return *g_EntityList;
92   }
93 }
94
95
96 inline Nameable* Node_getNameable(scene::Node& node)
97 {
98   return NodeTypeCast<Nameable>::cast(node);
99 }
100
101 const char* node_get_name(scene::Node& node)
102 {
103   Nameable* nameable = Node_getNameable(node);
104   return (nameable != 0)
105     ? nameable->name()
106     : "node";
107 }
108
109 template<typename value_type>
110 inline void gtk_tree_model_get_pointer(GtkTreeModel* model, GtkTreeIter* iter, gint column, value_type** pointer)
111 {
112   GValue value = GValue_default();
113   gtk_tree_model_get_value(model, iter, column, &value);
114   *pointer = (value_type*)g_value_get_pointer(&value);
115 }
116
117
118
119 void entitylist_treeviewcolumn_celldatafunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeModel* model, GtkTreeIter* iter, gpointer data)
120 {
121   scene::Node* node;
122   gtk_tree_model_get_pointer(model, iter, 0, &node);
123   scene::Instance* instance;
124   gtk_tree_model_get_pointer(model, iter, 1, &instance);
125   if(node != 0)
126   {
127     gtk_cell_renderer_set_fixed_size(renderer, -1, -1);
128     char* name = const_cast<char*>(node_get_name(*node));
129     g_object_set(G_OBJECT(renderer), "text", name, "visible", TRUE, 0);
130
131     //globalOutputStream() << "rendering cell " << makeQuoted(name) << "\n";
132     GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(getEntityList().m_tree_view));
133     if(instance->childSelected())
134     {
135       g_object_set(G_OBJECT(renderer), "cell-background-gdk", &style->base[GTK_STATE_ACTIVE], 0);
136     }
137     else
138     {
139       g_object_set(G_OBJECT(renderer), "cell-background-gdk", &style->base[GTK_STATE_NORMAL], 0);
140     }
141   }
142   else
143   {
144     gtk_cell_renderer_set_fixed_size(renderer, -1, 0);
145     g_object_set(G_OBJECT(renderer), "text", "", "visible", FALSE, 0);
146   }
147 }
148
149 static gboolean entitylist_tree_select(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer data)
150 {
151   GtkTreeIter iter;
152   gtk_tree_model_get_iter(model, &iter, path);
153   scene::Node* node;
154   gtk_tree_model_get_pointer(model, &iter, 0, &node);
155   scene::Instance* instance;
156   gtk_tree_model_get_pointer(model, &iter, 1, &instance);
157   Selectable* selectable = Instance_getSelectable(*instance);
158
159   if(node == 0)
160   {
161     if(path_currently_selected != FALSE)
162     {
163       getEntityList().m_selection_disabled = true;
164       GlobalSelectionSystem().setSelectedAll(false);
165       getEntityList().m_selection_disabled = false;
166     }
167   }
168   else if(selectable != 0)
169   {
170     getEntityList().m_selection_disabled = true;
171     selectable->setSelected(path_currently_selected == FALSE);
172     getEntityList().m_selection_disabled = false;
173     return TRUE;
174   }
175
176   return FALSE;
177 }
178
179 static gboolean entitylist_tree_select_null(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer data)
180 {
181   return TRUE;
182 }
183
184 void EntityList_ConnectSignals(GtkTreeView* view)
185 {
186   GtkTreeSelection* select = gtk_tree_view_get_selection(view);
187   gtk_tree_selection_set_select_function(select, entitylist_tree_select, NULL, 0);
188 }
189
190 void EntityList_DisconnectSignals(GtkTreeView* view)
191 {
192   GtkTreeSelection* select = gtk_tree_view_get_selection(view);
193   gtk_tree_selection_set_select_function(select, entitylist_tree_select_null, 0, 0);
194 }
195
196
197
198 gboolean treemodel_update_selection(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
199 {
200   GtkTreeView* view = reinterpret_cast<GtkTreeView*>(data);
201
202   scene::Instance* instance;
203   gtk_tree_model_get_pointer(model, iter, 1, &instance);
204   Selectable* selectable = Instance_getSelectable(*instance);
205
206   if(selectable != 0)
207   {
208     GtkTreeSelection* selection = gtk_tree_view_get_selection(view);
209     if(selectable->isSelected())
210     {
211       gtk_tree_selection_select_path(selection, path);
212     }
213     else
214     {
215       gtk_tree_selection_unselect_path(selection, path);
216     }
217   }
218
219   return FALSE;
220 }
221
222 void EntityList_UpdateSelection(GtkTreeModel* model, GtkTreeView* view)
223 {
224   EntityList_DisconnectSignals(view);
225   gtk_tree_model_foreach(model, treemodel_update_selection, view);
226   EntityList_ConnectSignals(view);
227 }
228
229
230 void RedrawEntityList()
231 {
232   switch(getEntityList().m_dirty)
233   {
234   case EntityList::eInsertRemove:
235   case EntityList::eSelection:
236     EntityList_UpdateSelection(GTK_TREE_MODEL(getEntityList().m_tree_model), getEntityList().m_tree_view);
237   default:
238     break;
239   }
240   getEntityList().m_dirty = EntityList::eDefault;
241 }
242
243 void entitylist_queue_draw()
244 {
245   getEntityList().m_idleDraw.queueDraw();
246 }
247
248 void EntityList_SelectionUpdate()
249 {
250   if(getEntityList().m_selection_disabled)
251     return;
252
253   if(getEntityList().m_dirty < EntityList::eSelection)
254     getEntityList().m_dirty = EntityList::eSelection;
255   entitylist_queue_draw();
256 }
257
258 void EntityList_SelectionChanged(const Selectable& selectable)
259 {
260   EntityList_SelectionUpdate();
261 }
262
263 void entitylist_treeview_rowcollapsed(GtkTreeView* view, GtkTreeIter* iter, GtkTreePath* path, gpointer user_data)
264 {
265 }
266
267 void entitylist_treeview_row_expanded(GtkTreeView* view, GtkTreeIter* iter, GtkTreePath* path, gpointer user_data)
268 {
269   EntityList_SelectionUpdate();
270 }
271
272
273 void EntityList_SetShown(bool shown)
274 {
275   widget_set_visible(GTK_WIDGET(getEntityList().m_window), shown);
276 }
277
278 void EntityList_toggleShown()
279 {
280   EntityList_SetShown(!getEntityList().visible());
281 }
282
283 gint graph_tree_model_compare_name(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
284 {
285   scene::Node* first;
286   gtk_tree_model_get(model, a, 0, (gpointer*)&first, -1);
287   scene::Node* second;
288   gtk_tree_model_get(model, b, 0, (gpointer*)&second, -1);
289   int result = 0;
290   if(first != 0 && second != 0)
291   {
292     result = string_compare(node_get_name(*first), node_get_name(*second));
293   }
294   if(result == 0)
295   {
296     return (first < second) ? -1 : (second < first) ? 1 : 0;
297   }
298   return result;
299 }
300
301 extern GraphTreeModel* scene_graph_get_tree_model();
302 void AttachEntityTreeModel()
303 {
304   getEntityList().m_tree_model = scene_graph_get_tree_model();
305
306   gtk_tree_view_set_model(getEntityList().m_tree_view, GTK_TREE_MODEL(getEntityList().m_tree_model));
307 }
308
309 void DetachEntityTreeModel()
310 {
311   getEntityList().m_tree_model = 0;
312
313   gtk_tree_view_set_model(getEntityList().m_tree_view, 0);
314 }
315       
316 void EntityList_constructWindow(GtkWindow* main_window)
317 {
318   ASSERT_MESSAGE(getEntityList().m_window == 0, "error");
319
320   GtkWindow* window = create_persistent_floating_window("Entity List", main_window);  
321
322   gtk_window_add_accel_group(window, global_accel);
323
324   getEntityList().m_positionTracker.connect(window);
325   
326
327   getEntityList().m_window = window;
328
329   {
330     GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
331     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(scr));
332
333     {
334       GtkWidget* view = gtk_tree_view_new();
335       gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
336
337       GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
338       GtkTreeViewColumn* column = gtk_tree_view_column_new();
339       gtk_tree_view_column_pack_start(column, renderer, TRUE);
340       gtk_tree_view_column_set_cell_data_func(column, renderer, entitylist_treeviewcolumn_celldatafunc, 0, 0);
341
342       GtkTreeSelection* select = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
343       gtk_tree_selection_set_mode(select, GTK_SELECTION_MULTIPLE);
344
345       g_signal_connect(G_OBJECT(view), "row_expanded", G_CALLBACK(entitylist_treeview_row_expanded), 0);
346       g_signal_connect(G_OBJECT(view), "row_collapsed", G_CALLBACK(entitylist_treeview_rowcollapsed), 0);
347
348       gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
349
350       gtk_widget_show(view);
351       gtk_container_add (GTK_CONTAINER(scr), view);
352       getEntityList().m_tree_view = GTK_TREE_VIEW(view);
353     }
354   }
355
356   EntityList_ConnectSignals(getEntityList().m_tree_view);
357   AttachEntityTreeModel();
358 }
359
360 void EntityList_destroyWindow()
361 {
362   DetachEntityTreeModel();
363   EntityList_DisconnectSignals(getEntityList().m_tree_view);
364   destroy_floating_window(getEntityList().m_window);
365 }
366
367 #include "preferencesystem.h"
368
369 #include "iselection.h"
370
371 namespace
372 {
373   scene::Node* nullNode = 0;
374 }
375
376 class NullSelectedInstance : public scene::Instance, public Selectable
377 {
378   class TypeCasts
379   {
380     InstanceTypeCastTable m_casts;
381   public:
382     TypeCasts()
383     {
384       InstanceStaticCast<NullSelectedInstance, Selectable>::install(m_casts);
385     }
386     InstanceTypeCastTable& get()
387     {
388       return m_casts;
389     }
390   };
391
392 public:
393   typedef LazyStatic<TypeCasts> StaticTypeCasts;
394
395   NullSelectedInstance() : Instance(scene::Path(makeReference(*nullNode)), 0, this, StaticTypeCasts::instance().get())
396   {
397   }
398
399   void setSelected(bool select)
400   {
401     ERROR_MESSAGE("error");
402   }
403   bool isSelected() const
404   {
405     return true;
406   }
407 };
408
409 typedef LazyStatic<NullSelectedInstance> StaticNullSelectedInstance;
410
411
412 void EntityList_Construct()
413 {
414   graph_tree_model_insert(scene_graph_get_tree_model(), StaticNullSelectedInstance::instance());
415
416   g_EntityList = new EntityList;
417
418   getEntityList().m_positionTracker.setPosition(c_default_window_pos);
419
420   GlobalPreferenceSystem().registerPreference("EntityInfoDlg", WindowPositionTrackerImportStringCaller(getEntityList().m_positionTracker), WindowPositionTrackerExportStringCaller(getEntityList().m_positionTracker));
421
422   typedef FreeCaller1<const Selectable&, EntityList_SelectionChanged> EntityListSelectionChangedCaller;
423   GlobalSelectionSystem().addSelectionChangeCallback(EntityListSelectionChangedCaller());
424 }
425 void EntityList_Destroy()
426 {
427   delete g_EntityList;
428
429   graph_tree_model_erase(scene_graph_get_tree_model(), StaticNullSelectedInstance::instance());
430 }