71dac5276d306b26483837b86daef3b8fe3dd6ea
[xonotic/netradiant.git] / radiant / gtkdlgs.cpp
1 /*
2 Copyright (c) 2001, Loki software, inc.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification, 
6 are permitted provided that the following conditions are met:
7
8 Redistributions of source code must retain the above copyright notice, this list 
9 of conditions and the following disclaimer.
10
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
14
15 Neither the name of Loki software nor the names of its contributors may be used 
16 to endorse or promote products derived from this software without specific prior 
17 written permission. 
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' 
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY 
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
29 */
30
31 //
32 // Some small dialogs that don't need much
33 //
34 // Leonardo Zide (leo@lokigames.com)
35 //
36
37 #include "gtkdlgs.h"
38
39 #include "debugging/debugging.h"
40 #include "version.h"
41 #include "aboutmsg.h"
42
43 #include "igl.h"
44 #include "iscenegraph.h"
45 #include "iselection.h"
46
47 #include <gdk/gdkkeysyms.h>
48 #include <gtk/gtkmain.h>
49 #include <gtk/gtkentry.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtkvbox.h>
52 #include <gtk/gtkframe.h>
53 #include <gtk/gtklabel.h>
54 #include <gtk/gtktable.h>
55 #include <gtk/gtkbutton.h>
56 #include <gtk/gtkcombobox.h>
57 #include <gtk/gtkscrolledwindow.h>
58 #include <gtk/gtktextview.h>
59 #include <gtk/gtktextbuffer.h>
60 #include <gtk/gtktreeview.h>
61 #include <gtk/gtkcellrenderertext.h>
62 #include <gtk/gtktreeselection.h>
63 #include <gtk/gtkliststore.h>
64
65 #include "os/path.h"
66 #include "math/aabb.h"
67 #include "container/array.h"
68 #include "generic/static.h"
69 #include "stream/stringstream.h"
70 #include "convert.h"
71 #include "gtkutil/messagebox.h"
72 #include "gtkutil/image.h"
73
74 #include "gtkmisc.h"
75 #include "brushmanip.h"
76 #include "build.h"
77 #include "qe3.h"
78 #include "texwindow.h"
79 #include "xywindow.h"
80 #include "mainframe.h"
81 #include "preferences.h"
82 #include "url.h"
83 #include "cmdlib.h"
84
85
86
87 // =============================================================================
88 // Project settings dialog
89
90 class GameComboConfiguration
91 {
92 public:
93   const char* basegame_dir;
94   const char* basegame;
95   const char* known_dir;
96   const char* known;
97   const char* custom;
98
99   GameComboConfiguration() :
100     basegame_dir(g_pGameDescription->getRequiredKeyValue("basegame")),
101     basegame(g_pGameDescription->getRequiredKeyValue("basegamename")),
102     known_dir(g_pGameDescription->getKeyValue("knowngame")),
103     known(g_pGameDescription->getKeyValue("knowngamename")),
104     custom(g_pGameDescription->getRequiredKeyValue("unknowngamename"))
105   {
106   }
107 };
108
109 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
110
111 inline GameComboConfiguration& globalGameComboConfiguration()
112 {
113   return LazyStaticGameComboConfiguration::instance();
114 }
115
116
117 struct gamecombo_t
118 {
119   gamecombo_t(int _game, const char* _fs_game, bool _sensitive)
120     : game(_game), fs_game(_fs_game), sensitive(_sensitive)
121   {}
122   int game;
123   const char* fs_game;
124   bool sensitive;
125 };
126
127 gamecombo_t gamecombo_for_dir(const char* dir)
128 {
129   if(string_equal(dir, globalGameComboConfiguration().basegame_dir))
130   {
131     return gamecombo_t(0, "", false);
132   }
133   else if(string_equal(dir, globalGameComboConfiguration().known_dir))
134   {
135     return gamecombo_t(1, dir, false);
136   }
137   else
138   {
139     return gamecombo_t(string_empty(globalGameComboConfiguration().known_dir) ? 1 : 2, dir, true);
140   }
141 }
142
143 gamecombo_t gamecombo_for_gamename(const char* gamename)
144 {
145   if ((strlen(gamename) == 0) || !strcmp(gamename, globalGameComboConfiguration().basegame))
146   {
147     return gamecombo_t(0, "", false);
148   }
149   else if (!strcmp(gamename, globalGameComboConfiguration().known))
150   {
151     return gamecombo_t(1, globalGameComboConfiguration().known_dir, false);
152   }
153   else
154   {
155     return gamecombo_t(string_empty(globalGameComboConfiguration().known_dir) ? 1 : 2, "", true);
156   }
157 }
158
159 inline void path_copy_clean(char* destination, const char* source)
160 {
161   char* i = destination;
162
163   while(*source != '\0')
164   {
165     *i++ = (*source == '\\') ? '/' : *source;
166     ++source;
167   }
168
169   if(i != destination && *(i-1) != '/')
170     *(i++) = '/';
171
172   *i = '\0';
173 }
174
175
176 struct GameCombo
177 {
178   GtkComboBox* game_select;
179   GtkEntry* fsgame_entry;
180 };
181
182 gboolean OnSelchangeComboWhatgame(GtkWidget *widget, GameCombo* combo)
183 {
184   const char *gamename;
185   {
186     GtkTreeIter iter;
187     gtk_combo_box_get_active_iter(combo->game_select, &iter);
188     gtk_tree_model_get(gtk_combo_box_get_model(combo->game_select), &iter, 0, (gpointer*)&gamename, -1);
189   }
190
191   gamecombo_t gamecombo = gamecombo_for_gamename(gamename);
192   
193   gtk_entry_set_text(combo->fsgame_entry, gamecombo.fs_game);
194   gtk_widget_set_sensitive(GTK_WIDGET(combo->fsgame_entry), gamecombo.sensitive);
195
196   return FALSE;
197 }
198
199 class MappingMode
200 {
201 public:
202   bool do_mapping_mode;
203   const char* sp_mapping_mode;
204   const char* mp_mapping_mode;
205
206   MappingMode() :
207     do_mapping_mode(!string_empty(g_pGameDescription->getKeyValue("show_gamemode"))),
208     sp_mapping_mode("Single Player mapping mode"),
209     mp_mapping_mode("Multiplayer mapping mode")
210   {
211   }
212 };
213
214 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
215
216 inline MappingMode& globalMappingMode()
217 {
218   return LazyStaticMappingMode::instance();
219 }
220
221 class ProjectSettingsDialog
222 {
223 public:
224   GameCombo game_combo;
225   GtkComboBox* gamemode_combo;
226 };
227
228 GtkWindow* ProjectSettingsDialog_construct(ProjectSettingsDialog& dialog, ModalDialog& modal)
229 {
230   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Project Settings", G_CALLBACK(dialog_delete_callback), &modal);
231
232   {
233     GtkTable* table1 = create_dialog_table(1, 2, 4, 4, 4);
234     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(table1));
235     {
236       GtkVBox* vbox = create_dialog_vbox(4);
237       gtk_table_attach(table1, GTK_WIDGET(vbox), 1, 2, 0, 1,
238                         (GtkAttachOptions) (GTK_FILL),
239                         (GtkAttachOptions) (GTK_FILL), 0, 0);
240       {
241         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &modal);
242         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
243       }
244       {
245         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &modal);
246         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
247       }
248     }
249     {
250       GtkFrame* frame = create_dialog_frame("Project settings");
251       gtk_table_attach(table1, GTK_WIDGET(frame), 0, 1, 0, 1,
252                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
253                         (GtkAttachOptions) (GTK_FILL), 0, 0);
254       {
255         GtkTable* table2 = create_dialog_table((globalMappingMode().do_mapping_mode) ? 4 : 3, 2, 4, 4, 4);
256         gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(table2));
257
258         {
259           GtkLabel* label = GTK_LABEL(gtk_label_new("Select mod"));
260           gtk_widget_show(GTK_WIDGET(label));
261           gtk_table_attach(table2, GTK_WIDGET(label), 0, 1, 0, 1,
262                             (GtkAttachOptions) (GTK_FILL),
263                             (GtkAttachOptions) (0), 0, 0);
264           gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
265         }
266         {
267           dialog.game_combo.game_select = GTK_COMBO_BOX(gtk_combo_box_new_text());
268
269           gtk_combo_box_append_text(dialog.game_combo.game_select, globalGameComboConfiguration().basegame);
270           if(globalGameComboConfiguration().known[0] != '\0')
271             gtk_combo_box_append_text(dialog.game_combo.game_select, globalGameComboConfiguration().known);
272           gtk_combo_box_append_text(dialog.game_combo.game_select, globalGameComboConfiguration().custom);
273
274           gtk_widget_show(GTK_WIDGET(dialog.game_combo.game_select));
275           gtk_table_attach(table2, GTK_WIDGET(dialog.game_combo.game_select), 1, 2, 0, 1,
276                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
277                             (GtkAttachOptions) (0), 0, 0);
278
279           g_signal_connect(G_OBJECT(dialog.game_combo.game_select), "changed", G_CALLBACK(OnSelchangeComboWhatgame), &dialog.game_combo);
280         }
281
282         {
283           GtkLabel* label = GTK_LABEL(gtk_label_new("fs_game"));
284           gtk_widget_show(GTK_WIDGET(label));
285           gtk_table_attach(table2, GTK_WIDGET(label), 0, 1, 1, 2,
286                             (GtkAttachOptions) (GTK_FILL),
287                             (GtkAttachOptions) (0), 0, 0);
288           gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
289         }
290         {
291           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
292           gtk_widget_show(GTK_WIDGET(entry));
293           gtk_table_attach(table2, GTK_WIDGET(entry), 1, 2, 1, 2,
294                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
295                             (GtkAttachOptions) (0), 0, 0);
296
297           dialog.game_combo.fsgame_entry = entry;
298        }
299   
300         if(globalMappingMode().do_mapping_mode)
301         {
302           GtkLabel* label = GTK_LABEL(gtk_label_new("Mapping mode"));
303           gtk_widget_show(GTK_WIDGET(label));
304           gtk_table_attach(table2, GTK_WIDGET(label), 0, 1, 3, 4,
305             (GtkAttachOptions) (GTK_FILL),
306             (GtkAttachOptions) (0), 0, 0);
307           gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
308
309           GtkComboBox* combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
310           gtk_combo_box_append_text(combo, globalMappingMode().sp_mapping_mode);
311           gtk_combo_box_append_text(combo, globalMappingMode().mp_mapping_mode);
312
313           gtk_widget_show(GTK_WIDGET(combo));
314           gtk_table_attach(table2, GTK_WIDGET(combo), 1, 2, 3, 4,
315             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
316             (GtkAttachOptions) (0), 0, 0);
317
318           dialog.gamemode_combo = combo;
319         }
320       }
321     }
322   }
323
324   // initialise the fs_game selection from the project settings into the dialog
325   const char* dir = gamename_get();
326   gamecombo_t gamecombo = gamecombo_for_dir(dir);
327
328   gtk_combo_box_set_active(dialog.game_combo.game_select, gamecombo.game);
329   gtk_entry_set_text(dialog.game_combo.fsgame_entry, gamecombo.fs_game);
330   gtk_widget_set_sensitive(GTK_WIDGET(dialog.game_combo.fsgame_entry), gamecombo.sensitive);
331
332   if(globalMappingMode().do_mapping_mode)
333   {
334     const char *gamemode = gamemode_get();
335     if (string_empty(gamemode) || string_equal(gamemode, "sp"))
336     {
337       gtk_combo_box_set_active(dialog.gamemode_combo, 0);
338     }
339     else
340     {
341       gtk_combo_box_set_active(dialog.gamemode_combo, 1);
342     }
343   }
344
345   return window;
346 }
347
348 void ProjectSettingsDialog_ok(ProjectSettingsDialog& dialog)
349 {
350   const char* dir = gtk_entry_get_text(dialog.game_combo.fsgame_entry);
351   
352   const char* new_gamename = path_equal(dir, globalGameComboConfiguration().basegame_dir)
353     ? ""
354     : dir;
355
356   if(!path_equal(new_gamename, gamename_get()))
357   {
358     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Changing Game Name");
359
360     EnginePath_Unrealise();
361
362     gamename_set(new_gamename);
363
364     EnginePath_Realise();
365   }
366
367   if(globalMappingMode().do_mapping_mode)
368   {
369     // read from gamemode_combo
370     int active = gtk_combo_box_get_active(dialog.gamemode_combo);
371     if(active == -1 || active == 0)
372     {
373       gamemode_set("sp");
374     }
375     else
376     {
377       gamemode_set("mp");
378     }
379   }
380 }
381
382 void DoProjectSettings()
383 {
384   if(ConfirmModified("Edit Project Settings"))
385   {
386     ModalDialog modal;
387     ProjectSettingsDialog dialog;
388
389     GtkWindow* window = ProjectSettingsDialog_construct(dialog, modal);
390
391     if(modal_dialog_show(window, modal) == eIDOK)
392     {
393       ProjectSettingsDialog_ok(dialog);
394     }
395
396     gtk_widget_destroy(GTK_WIDGET(window));
397   }
398 }
399
400 // =============================================================================
401 // Arbitrary Sides dialog
402
403 void DoSides (int type, int axis)
404 {
405   ModalDialog dialog;
406   GtkEntry* sides_entry;
407
408   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Arbitrary sides", G_CALLBACK(dialog_delete_callback), &dialog);
409
410   GtkAccelGroup* accel = gtk_accel_group_new();
411   gtk_window_add_accel_group(window, accel);
412
413   {
414     GtkHBox* hbox = create_dialog_hbox(4, 4);
415     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
416     {
417       GtkLabel* label = GTK_LABEL(gtk_label_new("Sides:"));
418       gtk_widget_show(GTK_WIDGET(label));
419       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 0);
420     }
421     {
422       GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
423       gtk_widget_show(GTK_WIDGET(entry));
424       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(entry), FALSE, FALSE, 0);
425       sides_entry = entry;
426       gtk_widget_grab_focus(GTK_WIDGET(entry));
427     }
428     {
429       GtkVBox* vbox = create_dialog_vbox(4);
430       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
431       {
432         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog);
433         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
434         widget_make_default(GTK_WIDGET(button));
435         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
436       }
437       {
438         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog);
439         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
440         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
441       }
442     }
443   }
444
445   if(modal_dialog_show(window, dialog) == eIDOK)
446   {
447     const char *str = gtk_entry_get_text(sides_entry);
448
449     Scene_BrushConstructPrefab(GlobalSceneGraph(), (EBrushPrefab)type, atoi(str), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
450   }
451
452   gtk_widget_destroy(GTK_WIDGET(window));
453 }
454
455 // =============================================================================
456 // About dialog (no program is complete without one)
457
458 void about_button_changelog (GtkWidget *widget, gpointer data)
459 {
460   StringOutputStream log(256);
461   log << AppPath_get() << "changelog.txt";
462   OpenURL(log.c_str());
463 }
464
465 void about_button_credits (GtkWidget *widget, gpointer data)
466 {
467   StringOutputStream cred(256);
468   cred << AppPath_get() << "credits.html";
469   OpenURL(cred.c_str());
470 }
471
472 void DoAbout()
473 {
474   ModalDialog dialog;
475   ModalDialogButton ok_button(dialog, eIDOK);
476
477   GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "About GtkRadiant", dialog);
478
479   {
480     GtkVBox* vbox = create_dialog_vbox(4, 4);
481     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
482
483     {
484       GtkHBox* hbox = create_dialog_hbox(4);
485       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0);
486
487       {
488         GtkVBox* vbox2 = create_dialog_vbox(4);
489         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), TRUE, FALSE, 0);
490         {
491           GtkFrame* frame = create_dialog_frame(0, GTK_SHADOW_IN);
492           gtk_box_pack_start(GTK_BOX (vbox2), GTK_WIDGET(frame), FALSE, FALSE, 0);
493           {
494             GtkImage* image = new_local_image("logo.bmp");
495             gtk_widget_show(GTK_WIDGET(image));
496             gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(image));
497           }
498         }
499       }
500
501       {
502         GtkLabel* label = GTK_LABEL(gtk_label_new("GtkRadiant " RADIANT_VERSION "\n"
503           __DATE__ "\n\n"
504           RADIANT_ABOUTMSG "\n\n"
505           "By qeradiant.com\n\n"
506           "This program is free software\n"
507           "licensed under the GNU GPL.\n\n"
508           "GtkRadiant is unsupported, however\n"
509           "you may report your problems at\n"
510           "http://zerowing.idsoftware.com/bugzilla"
511         ));
512                        
513         gtk_widget_show(GTK_WIDGET(label));
514         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 0);
515         gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
516         gtk_label_set_justify(label, GTK_JUSTIFY_LEFT);
517       }
518
519       {
520         GtkVBox* vbox2 = create_dialog_vbox(4);
521         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), FALSE, TRUE, 0);
522         {
523           GtkButton* button = create_modal_dialog_button("OK", ok_button);
524           gtk_box_pack_start (GTK_BOX (vbox2), GTK_WIDGET(button), FALSE, FALSE, 0);
525         }
526         {
527           GtkButton* button = create_dialog_button("Credits", G_CALLBACK(about_button_credits), 0);
528           gtk_box_pack_start (GTK_BOX (vbox2), GTK_WIDGET(button), FALSE, FALSE, 0);
529         }
530         {
531           GtkButton* button = create_dialog_button("Changelog", G_CALLBACK(about_button_changelog), 0);
532           gtk_box_pack_start (GTK_BOX (vbox2), GTK_WIDGET(button), FALSE, FALSE, 0);
533         }
534       }
535     }
536     {
537       GtkFrame* frame = create_dialog_frame("OpenGL Properties");
538       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), FALSE, FALSE, 0);
539       {
540         GtkTable* table = create_dialog_table(3, 2, 4, 4, 4);
541         gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(table));
542         {
543           GtkLabel* label = GTK_LABEL(gtk_label_new("Vendor:"));
544           gtk_widget_show(GTK_WIDGET(label));
545           gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1,
546                             (GtkAttachOptions) (GTK_FILL),
547                             (GtkAttachOptions) (0), 0, 0);
548           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
549         }
550         {
551           GtkLabel* label = GTK_LABEL(gtk_label_new("Version:"));
552           gtk_widget_show(GTK_WIDGET(label));
553           gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2,
554                             (GtkAttachOptions) (GTK_FILL),
555                             (GtkAttachOptions) (0), 0, 0);
556           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
557         }
558         {
559           GtkLabel* label = GTK_LABEL(gtk_label_new("Renderer:"));
560           gtk_widget_show(GTK_WIDGET(label));
561           gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 2, 3,
562                             (GtkAttachOptions) (GTK_FILL),
563                             (GtkAttachOptions) (0), 0, 0);
564           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
565         }
566         {
567           GtkLabel* label = GTK_LABEL(gtk_label_new(reinterpret_cast<const char*>(glGetString(GL_VENDOR))));
568           gtk_widget_show(GTK_WIDGET(label));
569           gtk_table_attach(table, GTK_WIDGET(label), 1, 2, 0, 1,
570                             (GtkAttachOptions) (GTK_FILL),
571                             (GtkAttachOptions) (0), 0, 0);
572           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
573         }
574         {
575           GtkLabel* label = GTK_LABEL(gtk_label_new(reinterpret_cast<const char*>(glGetString(GL_VERSION))));
576           gtk_widget_show(GTK_WIDGET(label));
577           gtk_table_attach(table, GTK_WIDGET(label), 1, 2, 1, 2,
578                             (GtkAttachOptions) (GTK_FILL),
579                             (GtkAttachOptions) (0), 0, 0);
580           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
581         }
582         {
583           GtkLabel* label = GTK_LABEL(gtk_label_new(reinterpret_cast<const char*>(glGetString(GL_RENDERER))));
584           gtk_widget_show(GTK_WIDGET(label));
585           gtk_table_attach(table, GTK_WIDGET(label), 1, 2, 2, 3,
586                             (GtkAttachOptions) (GTK_FILL),
587                             (GtkAttachOptions) (0), 0, 0);
588           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
589         }
590       }
591       {
592         GtkFrame* frame = create_dialog_frame("OpenGL Extensions");
593         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), TRUE, TRUE, 0);
594         {
595           GtkScrolledWindow* sc_extensions = create_scrolled_window(GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, 4);
596           gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET(sc_extensions));
597           {
598             GtkWidget* text_extensions = gtk_text_view_new();
599             gtk_text_view_set_editable(GTK_TEXT_VIEW(text_extensions), FALSE);
600             gtk_container_add (GTK_CONTAINER (sc_extensions), text_extensions);
601             GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_extensions));
602             gtk_text_buffer_set_text(buffer, reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)), -1);
603             gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_extensions), GTK_WRAP_WORD);
604             gtk_widget_show(text_extensions);
605           }
606         }
607       }
608     }
609   }
610
611   modal_dialog_show(window, dialog);
612
613   gtk_widget_destroy(GTK_WIDGET(window));
614 }
615
616 // =============================================================================
617 // Texture List dialog 
618
619 void DoTextureListDlg()
620 {
621   ModalDialog dialog;
622   ModalDialogButton ok_button(dialog, eIDOK);
623   ModalDialogButton cancel_button(dialog, eIDCANCEL);
624   GtkWidget* texture_list;
625
626   GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Textures", dialog, 400, 400);
627
628   GtkHBox* hbox = create_dialog_hbox(4, 4);
629   gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
630
631   {
632     GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
633     gtk_box_pack_start(GTK_BOX (hbox), GTK_WIDGET(scr), TRUE, TRUE, 0);
634
635
636     {
637       GtkListStore* store = gtk_list_store_new(1, G_TYPE_STRING);
638
639       GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
640       gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); 
641
642       {
643         GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
644         GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, 0);
645         gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
646       }
647
648       gtk_widget_show(view);
649       gtk_container_add(GTK_CONTAINER (scr), view);
650
651       {
652         // Initialize dialog
653         GSList *textures = 0;
654         TextureGroupsMenu_ListItems(textures);
655         while (textures != 0)
656         {
657           {
658             GtkTreeIter iter;
659             gtk_list_store_append(store, &iter);
660             StringOutputStream name(64);
661             name << ConvertLocaleToUTF8(reinterpret_cast<const char*>(textures->data));
662             gtk_list_store_set(store, &iter, 0, name.c_str(), -1);
663           }
664           textures = g_slist_remove (textures, textures->data);
665         }
666       }
667     
668       g_object_unref(G_OBJECT(store));
669
670       texture_list = view;
671     }
672   }
673
674   GtkVBox* vbox = create_dialog_vbox(4);
675   gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, TRUE, 0);
676   {
677     GtkButton* button = create_modal_dialog_button("Load", ok_button);
678     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
679   }
680   {
681     GtkButton* button = create_modal_dialog_button("Close", cancel_button);
682     gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
683   }
684
685   if(modal_dialog_show(window, dialog) == eIDOK)
686   {
687     GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(texture_list));
688
689     GtkTreeModel* model;
690     GtkTreeIter iter;
691     if(gtk_tree_selection_get_selected(selection, &model, &iter))
692     {
693       GtkTreePath* path = gtk_tree_model_get_path(model, &iter);
694       if(gtk_tree_path_get_depth(path) == 1)
695         TextureBrowser_ShowDirectory(GlobalTextureBrowser(), TextureGroupsMenu_GetName(gtk_tree_path_get_indices(path)[0]));
696       gtk_tree_path_free(path);
697     }
698   }
699
700   gtk_widget_destroy(GTK_WIDGET(window));
701 }
702
703 // =============================================================================
704 // TextureLayout dialog 
705
706 EMessageBoxReturn DoTextureLayout (float *fx, float *fy)
707 {
708   ModalDialog dialog;
709   ModalDialogButton ok_button(dialog, eIDOK);
710   ModalDialogButton cancel_button(dialog, eIDCANCEL);
711   GtkEntry* x;
712   GtkEntry* y;
713
714   GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Patch texture layout", dialog);
715
716   GtkAccelGroup* accel = gtk_accel_group_new();
717   gtk_window_add_accel_group(window, accel);
718
719   {
720     GtkHBox* hbox = create_dialog_hbox(4, 4);
721     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
722     {
723       GtkVBox* vbox = create_dialog_vbox(4);
724       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
725       {
726         GtkLabel* label = GTK_LABEL(gtk_label_new("Texture will be fit across the patch based\n"
727           "on the x and y values given. Values of 1x1\n"
728           "will \"fit\" the texture. 2x2 will repeat\n"
729           "it twice, etc."));
730         gtk_widget_show(GTK_WIDGET(label));
731         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), TRUE, TRUE, 0);
732         gtk_label_set_justify(label, GTK_JUSTIFY_LEFT);
733       }
734       {
735         GtkTable* table = create_dialog_table(2, 2, 4, 4);
736         gtk_widget_show(GTK_WIDGET(table));
737         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0);
738         {
739           GtkLabel* label = GTK_LABEL(gtk_label_new("Texture x:"));
740           gtk_widget_show(GTK_WIDGET(label));
741           gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1,
742                             (GtkAttachOptions) (GTK_FILL),
743                             (GtkAttachOptions) (0), 0, 0);
744           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
745         }
746         {
747           GtkLabel* label = GTK_LABEL(gtk_label_new("Texture y:"));
748           gtk_widget_show(GTK_WIDGET(label));
749           gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2,
750                             (GtkAttachOptions) (GTK_FILL),
751                             (GtkAttachOptions) (0), 0, 0);
752           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
753         }
754         {
755           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
756           gtk_widget_show(GTK_WIDGET(entry));
757           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
758                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
759                             (GtkAttachOptions) (0), 0, 0);
760
761           gtk_widget_grab_focus(GTK_WIDGET(entry));
762
763           x = entry;
764         }
765         {
766           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
767           gtk_widget_show(GTK_WIDGET(entry));
768           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
769                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
770                             (GtkAttachOptions) (0), 0, 0);
771
772           y = entry;
773         }
774       }
775     }
776     {
777       GtkVBox* vbox = create_dialog_vbox(4);
778       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0);
779       {
780         GtkButton* button = create_modal_dialog_button("OK", ok_button);
781         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
782         widget_make_default(GTK_WIDGET(button));
783         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
784       }
785       {
786         GtkButton* button = create_modal_dialog_button("Cancel", cancel_button);
787         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
788         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
789       }
790     }
791   }
792
793   // Initialize
794   gtk_entry_set_text(x, "4.0");
795   gtk_entry_set_text(y, "4.0");
796
797
798   EMessageBoxReturn ret = modal_dialog_show(window, dialog);
799   if (ret == eIDOK)
800   {
801     *fx = static_cast<float>(atof(gtk_entry_get_text(x)));
802     *fy = static_cast<float>(atof(gtk_entry_get_text(y)));
803   }
804
805   gtk_widget_destroy(GTK_WIDGET(window));
806
807   return ret;
808 }
809
810 // =============================================================================
811 // Text Editor dialog 
812
813 // master window widget
814 static GtkWidget *text_editor = 0;
815 static GtkWidget *text_widget; // slave, text widget from the gtk editor
816
817 static gint editor_delete (GtkWidget *widget, gpointer data)
818 {
819   if (gtk_MessageBox (widget, "Close the shader editor ?", "Radiant", eMB_YESNO, eMB_ICONQUESTION) == eIDNO)
820     return TRUE;
821
822   gtk_widget_hide (text_editor);
823
824   return TRUE;
825 }
826
827 static void editor_save (GtkWidget *widget, gpointer data)
828 {
829   FILE *f = fopen ((char*)g_object_get_data (G_OBJECT (data), "filename"), "w");
830   gpointer text = g_object_get_data (G_OBJECT (data), "text");
831
832   if (f == 0)
833   {
834     gtk_MessageBox (GTK_WIDGET(data), "Error saving file !");
835     return;
836   }
837
838   char *str = gtk_editable_get_chars (GTK_EDITABLE (text), 0, -1);
839   fwrite (str, 1, strlen (str), f);
840   fclose (f);
841 }
842
843 static void editor_close (GtkWidget *widget, gpointer data)
844 {
845   if (gtk_MessageBox (text_editor, "Close the shader editor ?", "Radiant", eMB_YESNO, eMB_ICONQUESTION) == eIDNO)
846     return;
847
848   gtk_widget_hide (text_editor);
849 }
850
851 static void CreateGtkTextEditor()
852 {
853   GtkWidget *dlg;
854   GtkWidget *vbox, *hbox, *button, *scr, *text;
855
856   dlg = gtk_window_new (GTK_WINDOW_TOPLEVEL);
857
858   g_signal_connect(G_OBJECT(dlg), "delete_event",
859                       G_CALLBACK(editor_delete), 0);
860   gtk_window_set_default_size (GTK_WINDOW (dlg), 600, 300);
861
862   vbox = gtk_vbox_new (FALSE, 5);
863   gtk_widget_show (vbox);
864   gtk_container_add(GTK_CONTAINER(dlg), GTK_WIDGET(vbox));
865   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
866
867   scr = gtk_scrolled_window_new (0, 0);
868   gtk_widget_show (scr);
869   gtk_box_pack_start(GTK_BOX(vbox), scr, TRUE, TRUE, 0);
870   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scr), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
871   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN);
872
873   text = gtk_text_view_new();
874   gtk_container_add (GTK_CONTAINER (scr), text);
875   gtk_widget_show (text);
876   g_object_set_data (G_OBJECT (dlg), "text", text);
877   gtk_text_view_set_editable (GTK_TEXT_VIEW(text), TRUE);
878
879   hbox = gtk_hbox_new (FALSE, 5);
880   gtk_widget_show (hbox);
881   gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0);
882
883   button = gtk_button_new_with_label ("Close");
884   gtk_widget_show (button);
885   gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
886   g_signal_connect(G_OBJECT(button), "clicked",
887                       G_CALLBACK(editor_close), dlg);
888   gtk_widget_set_usize (button, 60, -2);
889
890   button = gtk_button_new_with_label ("Save");
891   gtk_widget_show (button);
892   gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
893   g_signal_connect(G_OBJECT(button), "clicked",
894                       G_CALLBACK(editor_save), dlg);
895   gtk_widget_set_usize (button, 60, -2);
896
897   text_editor = dlg;
898   text_widget = text;
899 }
900
901 static void DoGtkTextEditor (const char* filename, guint cursorpos)
902 {
903   if (!text_editor)
904     CreateGtkTextEditor(); // build it the first time we need it
905
906   // Load file
907   FILE *f = fopen (filename, "r");
908
909   if (f == 0)
910   {
911     globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
912     gtk_widget_hide (text_editor);
913   }
914   else
915   {
916     fseek (f, 0, SEEK_END);
917     int len = ftell (f);
918     void *buf = malloc (len);
919     void *old_filename;
920
921     rewind (f);
922     fread (buf, 1, len, f);
923
924     gtk_window_set_title (GTK_WINDOW (text_editor), filename);
925
926     GtkTextBuffer* text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
927     gtk_text_buffer_set_text(text_buffer, (char*)buf, len);
928
929     old_filename = g_object_get_data (G_OBJECT (text_editor), "filename");
930     if (old_filename)
931       free(old_filename);
932     g_object_set_data (G_OBJECT (text_editor), "filename", strdup (filename));
933
934     // trying to show later
935     gtk_widget_show (text_editor);
936
937 #ifdef WIN32
938     process_gui();
939 #endif
940
941     // only move the cursor if it's not exceeding the size..
942     // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
943     // len is the max size in bytes, not in characters either, but the character count is below that limit..
944     // thinking .. the difference between character count and byte count would be only because of CR/LF?
945     {
946       GtkTextIter text_iter;
947       // character offset, not byte offset
948       gtk_text_buffer_get_iter_at_offset(text_buffer, &text_iter, cursorpos);
949       gtk_text_buffer_place_cursor(text_buffer, &text_iter);
950     }
951
952 #ifdef WIN32
953     gtk_widget_queue_draw(text_widget);
954 #endif
955
956     free (buf);
957     fclose (f);
958   }
959 }
960
961 // =============================================================================
962 // Light Intensity dialog 
963
964 EMessageBoxReturn DoLightIntensityDlg (int *intensity)
965 {
966   ModalDialog dialog;
967   GtkEntry* intensity_entry;
968   ModalDialogButton ok_button(dialog, eIDOK);
969   ModalDialogButton cancel_button(dialog, eIDCANCEL);
970
971   GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Light intensity", dialog, -1, -1);
972
973   GtkAccelGroup *accel_group = gtk_accel_group_new();
974   gtk_window_add_accel_group(window, accel_group);
975
976   {
977     GtkHBox* hbox = create_dialog_hbox(4, 4);
978     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
979     {
980       GtkVBox* vbox = create_dialog_vbox(4);
981       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
982       {
983         GtkLabel* label = GTK_LABEL(gtk_label_new("ESC for default, ENTER to validate"));
984         gtk_widget_show(GTK_WIDGET(label));
985         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
986       }
987       {
988         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
989         gtk_widget_show(GTK_WIDGET(entry));
990         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(entry), TRUE, TRUE, 0);
991
992         gtk_widget_grab_focus(GTK_WIDGET(entry));
993
994         intensity_entry = entry;
995       }
996     }
997     {
998       GtkVBox* vbox = create_dialog_vbox(4);
999       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0);
1000
1001       {
1002         GtkButton* button = create_modal_dialog_button("OK", ok_button);
1003         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
1004         widget_make_default(GTK_WIDGET(button));
1005         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE);
1006       }
1007       {
1008         GtkButton* button = create_modal_dialog_button("Cancel", cancel_button);
1009         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
1010         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE);
1011       }
1012     }
1013   }
1014
1015   char buf[16];
1016   sprintf (buf, "%d", *intensity);
1017   gtk_entry_set_text(intensity_entry, buf);
1018
1019   EMessageBoxReturn ret = modal_dialog_show(window, dialog);
1020   if(ret == eIDOK)
1021     *intensity = atoi (gtk_entry_get_text(intensity_entry));
1022
1023   gtk_widget_destroy(GTK_WIDGET(window));
1024
1025   return ret;
1026 }
1027
1028
1029 #ifdef WIN32
1030 #include <gdk/gdkwin32.h>
1031 #endif
1032
1033 #ifdef WIN32
1034   // use the file associations to open files instead of builtin Gtk editor
1035 bool g_TextEditor_useWin32Editor = true;
1036 #else
1037   // custom shader editor
1038 bool g_TextEditor_useCustomEditor = false;
1039 CopiedString g_TextEditor_editorCommand("");
1040 #endif
1041
1042 void DoTextEditor (const char* filename, int cursorpos)
1043 {
1044 #ifdef WIN32
1045   if (g_TextEditor_useWin32Editor)
1046   {
1047     globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1048     ShellExecute((HWND)GDK_WINDOW_HWND (GTK_WIDGET(MainFrame_getWindow())->window), "open", filename, 0, 0, SW_SHOW );
1049     return;
1050   }
1051 #else
1052   // check if a custom editor is set
1053   if(g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty())
1054   {
1055         StringOutputStream strEditCommand(256);
1056     strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1057     
1058     globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1059     // note: linux does not return false if the command failed so it will assume success
1060     if (Q_Exec(0, const_cast<char*>(strEditCommand.c_str()), 0, true) == false)
1061     {
1062       globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1063     }
1064     else
1065     {
1066       // the command (appeared) to run successfully, no need to do anything more
1067       return;
1068     }
1069   }
1070 #endif
1071   
1072   DoGtkTextEditor (filename, cursorpos);
1073 }