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