2ba3e3f4eca57f3bd7cf2abf7094ce5769d5ab36
[divverent/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 NetRadiant", 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("NetRadiant " RADIANT_VERSION "\n"
503           __DATE__ "\n\n"
504           RADIANT_ABOUTMSG "\n\n"
505           "By alientrap.org\n\n"
506           "This program is free software\n"
507           "licensed under the GNU GPL.\n\n"
508           "NetRadiant is unsupported, however\n"
509           "you may report your problems at\n"
510           "http://www.icculus.org/netradiant/"
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 // TextureLayout dialog 
618
619 EMessageBoxReturn DoTextureLayout (float *fx, float *fy)
620 {
621   ModalDialog dialog;
622   ModalDialogButton ok_button(dialog, eIDOK);
623   ModalDialogButton cancel_button(dialog, eIDCANCEL);
624   GtkEntry* x;
625   GtkEntry* y;
626
627   GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Patch texture layout", dialog);
628
629   GtkAccelGroup* accel = gtk_accel_group_new();
630   gtk_window_add_accel_group(window, accel);
631
632   {
633     GtkHBox* hbox = create_dialog_hbox(4, 4);
634     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
635     {
636       GtkVBox* vbox = create_dialog_vbox(4);
637       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
638       {
639         GtkLabel* label = GTK_LABEL(gtk_label_new("Texture will be fit across the patch based\n"
640           "on the x and y values given. Values of 1x1\n"
641           "will \"fit\" the texture. 2x2 will repeat\n"
642           "it twice, etc."));
643         gtk_widget_show(GTK_WIDGET(label));
644         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), TRUE, TRUE, 0);
645         gtk_label_set_justify(label, GTK_JUSTIFY_LEFT);
646       }
647       {
648         GtkTable* table = create_dialog_table(2, 2, 4, 4);
649         gtk_widget_show(GTK_WIDGET(table));
650         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0);
651         {
652           GtkLabel* label = GTK_LABEL(gtk_label_new("Texture x:"));
653           gtk_widget_show(GTK_WIDGET(label));
654           gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1,
655                             (GtkAttachOptions) (GTK_FILL),
656                             (GtkAttachOptions) (0), 0, 0);
657           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
658         }
659         {
660           GtkLabel* label = GTK_LABEL(gtk_label_new("Texture y:"));
661           gtk_widget_show(GTK_WIDGET(label));
662           gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2,
663                             (GtkAttachOptions) (GTK_FILL),
664                             (GtkAttachOptions) (0), 0, 0);
665           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
666         }
667         {
668           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
669           gtk_widget_show(GTK_WIDGET(entry));
670           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
671                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
672                             (GtkAttachOptions) (0), 0, 0);
673
674           gtk_widget_grab_focus(GTK_WIDGET(entry));
675
676           x = entry;
677         }
678         {
679           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
680           gtk_widget_show(GTK_WIDGET(entry));
681           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
682                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
683                             (GtkAttachOptions) (0), 0, 0);
684
685           y = entry;
686         }
687       }
688     }
689     {
690       GtkVBox* vbox = create_dialog_vbox(4);
691       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0);
692       {
693         GtkButton* button = create_modal_dialog_button("OK", ok_button);
694         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
695         widget_make_default(GTK_WIDGET(button));
696         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
697       }
698       {
699         GtkButton* button = create_modal_dialog_button("Cancel", cancel_button);
700         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
701         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
702       }
703     }
704   }
705
706   // Initialize
707   gtk_entry_set_text(x, "4.0");
708   gtk_entry_set_text(y, "4.0");
709
710
711   EMessageBoxReturn ret = modal_dialog_show(window, dialog);
712   if (ret == eIDOK)
713   {
714     *fx = static_cast<float>(atof(gtk_entry_get_text(x)));
715     *fy = static_cast<float>(atof(gtk_entry_get_text(y)));
716   }
717
718   gtk_widget_destroy(GTK_WIDGET(window));
719
720   return ret;
721 }
722
723 // =============================================================================
724 // Text Editor dialog 
725
726 // master window widget
727 static GtkWidget *text_editor = 0;
728 static GtkWidget *text_widget; // slave, text widget from the gtk editor
729
730 static gint editor_delete (GtkWidget *widget, gpointer data)
731 {
732   if (gtk_MessageBox (widget, "Close the shader editor ?", "Radiant", eMB_YESNO, eMB_ICONQUESTION) == eIDNO)
733     return TRUE;
734
735   gtk_widget_hide (text_editor);
736
737   return TRUE;
738 }
739
740 static void editor_save (GtkWidget *widget, gpointer data)
741 {
742   FILE *f = fopen ((char*)g_object_get_data (G_OBJECT (data), "filename"), "w");
743   gpointer text = g_object_get_data (G_OBJECT (data), "text");
744
745   if (f == 0)
746   {
747     gtk_MessageBox (GTK_WIDGET(data), "Error saving file !");
748     return;
749   }
750
751   char *str = gtk_editable_get_chars (GTK_EDITABLE (text), 0, -1);
752   fwrite (str, 1, strlen (str), f);
753   fclose (f);
754 }
755
756 static void editor_close (GtkWidget *widget, gpointer data)
757 {
758   if (gtk_MessageBox (text_editor, "Close the shader editor ?", "Radiant", eMB_YESNO, eMB_ICONQUESTION) == eIDNO)
759     return;
760
761   gtk_widget_hide (text_editor);
762 }
763
764 static void CreateGtkTextEditor()
765 {
766   GtkWidget *dlg;
767   GtkWidget *vbox, *hbox, *button, *scr, *text;
768
769   dlg = gtk_window_new (GTK_WINDOW_TOPLEVEL);
770
771   g_signal_connect(G_OBJECT(dlg), "delete_event",
772                       G_CALLBACK(editor_delete), 0);
773   gtk_window_set_default_size (GTK_WINDOW (dlg), 600, 300);
774
775   vbox = gtk_vbox_new (FALSE, 5);
776   gtk_widget_show (vbox);
777   gtk_container_add(GTK_CONTAINER(dlg), GTK_WIDGET(vbox));
778   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
779
780   scr = gtk_scrolled_window_new (0, 0);
781   gtk_widget_show (scr);
782   gtk_box_pack_start(GTK_BOX(vbox), scr, TRUE, TRUE, 0);
783   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scr), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
784   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN);
785
786   text = gtk_text_view_new();
787   gtk_container_add (GTK_CONTAINER (scr), text);
788   gtk_widget_show (text);
789   g_object_set_data (G_OBJECT (dlg), "text", text);
790   gtk_text_view_set_editable (GTK_TEXT_VIEW(text), TRUE);
791
792   hbox = gtk_hbox_new (FALSE, 5);
793   gtk_widget_show (hbox);
794   gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0);
795
796   button = gtk_button_new_with_label ("Close");
797   gtk_widget_show (button);
798   gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
799   g_signal_connect(G_OBJECT(button), "clicked",
800                       G_CALLBACK(editor_close), dlg);
801   gtk_widget_set_usize (button, 60, -2);
802
803   button = gtk_button_new_with_label ("Save");
804   gtk_widget_show (button);
805   gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
806   g_signal_connect(G_OBJECT(button), "clicked",
807                       G_CALLBACK(editor_save), dlg);
808   gtk_widget_set_usize (button, 60, -2);
809
810   text_editor = dlg;
811   text_widget = text;
812 }
813
814 static void DoGtkTextEditor (const char* filename, guint cursorpos)
815 {
816   if (!text_editor)
817     CreateGtkTextEditor(); // build it the first time we need it
818
819   // Load file
820   FILE *f = fopen (filename, "r");
821
822   if (f == 0)
823   {
824     globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
825     gtk_widget_hide (text_editor);
826   }
827   else
828   {
829     fseek (f, 0, SEEK_END);
830     int len = ftell (f);
831     void *buf = malloc (len);
832     void *old_filename;
833
834     rewind (f);
835     fread (buf, 1, len, f);
836
837     gtk_window_set_title (GTK_WINDOW (text_editor), filename);
838
839     GtkTextBuffer* text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget));
840     gtk_text_buffer_set_text(text_buffer, (char*)buf, len);
841
842     old_filename = g_object_get_data (G_OBJECT (text_editor), "filename");
843     if (old_filename)
844       free(old_filename);
845     g_object_set_data (G_OBJECT (text_editor), "filename", strdup (filename));
846
847     // trying to show later
848     gtk_widget_show (text_editor);
849
850 #ifdef WIN32
851     process_gui();
852 #endif
853
854     // only move the cursor if it's not exceeding the size..
855     // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
856     // len is the max size in bytes, not in characters either, but the character count is below that limit..
857     // thinking .. the difference between character count and byte count would be only because of CR/LF?
858     {
859       GtkTextIter text_iter;
860       // character offset, not byte offset
861       gtk_text_buffer_get_iter_at_offset(text_buffer, &text_iter, cursorpos);
862       gtk_text_buffer_place_cursor(text_buffer, &text_iter);
863     }
864
865 #ifdef WIN32
866     gtk_widget_queue_draw(text_widget);
867 #endif
868
869     free (buf);
870     fclose (f);
871   }
872 }
873
874 // =============================================================================
875 // Light Intensity dialog 
876
877 EMessageBoxReturn DoLightIntensityDlg (int *intensity)
878 {
879   ModalDialog dialog;
880   GtkEntry* intensity_entry;
881   ModalDialogButton ok_button(dialog, eIDOK);
882   ModalDialogButton cancel_button(dialog, eIDCANCEL);
883
884   GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Light intensity", dialog, -1, -1);
885
886   GtkAccelGroup *accel_group = gtk_accel_group_new();
887   gtk_window_add_accel_group(window, accel_group);
888
889   {
890     GtkHBox* hbox = create_dialog_hbox(4, 4);
891     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
892     {
893       GtkVBox* vbox = create_dialog_vbox(4);
894       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
895       {
896         GtkLabel* label = GTK_LABEL(gtk_label_new("ESC for default, ENTER to validate"));
897         gtk_widget_show(GTK_WIDGET(label));
898         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
899       }
900       {
901         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
902         gtk_widget_show(GTK_WIDGET(entry));
903         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(entry), TRUE, TRUE, 0);
904
905         gtk_widget_grab_focus(GTK_WIDGET(entry));
906
907         intensity_entry = entry;
908       }
909     }
910     {
911       GtkVBox* vbox = create_dialog_vbox(4);
912       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0);
913
914       {
915         GtkButton* button = create_modal_dialog_button("OK", ok_button);
916         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
917         widget_make_default(GTK_WIDGET(button));
918         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE);
919       }
920       {
921         GtkButton* button = create_modal_dialog_button("Cancel", cancel_button);
922         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
923         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE);
924       }
925     }
926   }
927
928   char buf[16];
929   sprintf (buf, "%d", *intensity);
930   gtk_entry_set_text(intensity_entry, buf);
931
932   EMessageBoxReturn ret = modal_dialog_show(window, dialog);
933   if(ret == eIDOK)
934     *intensity = atoi (gtk_entry_get_text(intensity_entry));
935
936   gtk_widget_destroy(GTK_WIDGET(window));
937
938   return ret;
939 }
940
941 // =============================================================================
942 // Add new shader tag dialog 
943
944 EMessageBoxReturn DoShaderTagDlg (CopiedString* tag, char* title)
945 {
946   ModalDialog dialog;
947   GtkEntry* textentry;
948   ModalDialogButton ok_button(dialog, eIDOK);
949   ModalDialogButton cancel_button(dialog, eIDCANCEL);
950
951   GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), title, dialog, -1, -1);
952
953   GtkAccelGroup *accel_group = gtk_accel_group_new();
954   gtk_window_add_accel_group(window, accel_group);
955
956   {
957     GtkHBox* hbox = create_dialog_hbox(4, 4);
958     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
959     {
960       GtkVBox* vbox = create_dialog_vbox(4);
961       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
962       {
963         //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
964                 GtkLabel* label = GTK_LABEL(gtk_label_new("ESC to cancel, ENTER to validate"));
965         gtk_widget_show(GTK_WIDGET(label));
966         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
967       }
968       {
969         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
970         gtk_widget_show(GTK_WIDGET(entry));
971         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(entry), TRUE, TRUE, 0);
972
973         gtk_widget_grab_focus(GTK_WIDGET(entry));
974
975         textentry = entry;
976       }
977     }
978     {
979       GtkVBox* vbox = create_dialog_vbox(4);
980       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0);
981
982       {
983         GtkButton* button = create_modal_dialog_button("OK", ok_button);
984         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
985         widget_make_default(GTK_WIDGET(button));
986         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE);
987       }
988       {
989         GtkButton* button = create_modal_dialog_button("Cancel", cancel_button);
990         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
991         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE);
992       }
993     }
994   }
995
996   EMessageBoxReturn ret = modal_dialog_show(window, dialog);
997   if(ret == eIDOK)
998   {
999     *tag = gtk_entry_get_text(textentry);
1000   }
1001
1002   gtk_widget_destroy(GTK_WIDGET(window));
1003
1004   return ret;
1005 }
1006
1007 EMessageBoxReturn DoShaderInfoDlg (const char* name, const char* filename, char* title)
1008 {
1009   ModalDialog dialog;
1010   ModalDialogButton ok_button(dialog, eIDOK);
1011
1012   GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), title, dialog, -1, -1);
1013
1014   GtkAccelGroup *accel_group = gtk_accel_group_new();
1015   gtk_window_add_accel_group(window, accel_group);
1016
1017   {
1018     GtkHBox* hbox = create_dialog_hbox(4, 4);
1019     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
1020     {
1021       GtkVBox* vbox = create_dialog_vbox(4);
1022       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0);
1023       {
1024         GtkLabel* label = GTK_LABEL(gtk_label_new("The selected shader"));
1025         gtk_widget_show(GTK_WIDGET(label));
1026         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
1027       }
1028       {
1029         GtkLabel* label = GTK_LABEL(gtk_label_new(name));
1030         gtk_widget_show(GTK_WIDGET(label));
1031         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
1032       }
1033       {
1034         GtkLabel* label = GTK_LABEL(gtk_label_new("is located in file"));
1035         gtk_widget_show(GTK_WIDGET(label));
1036         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
1037       }
1038       {
1039         GtkLabel* label = GTK_LABEL(gtk_label_new(filename));
1040         gtk_widget_show(GTK_WIDGET(label));
1041         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0);
1042       }
1043       {
1044         GtkButton* button = create_modal_dialog_button("OK", ok_button);
1045         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
1046         widget_make_default(GTK_WIDGET(button));
1047         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE);
1048       }
1049     }
1050   }
1051
1052   EMessageBoxReturn ret = modal_dialog_show(window, dialog);
1053
1054   gtk_widget_destroy(GTK_WIDGET(window));
1055
1056   return ret;
1057 }
1058
1059
1060
1061 #ifdef WIN32
1062 #include <gdk/gdkwin32.h>
1063 #endif
1064
1065 #ifdef WIN32
1066   // use the file associations to open files instead of builtin Gtk editor
1067 bool g_TextEditor_useWin32Editor = true;
1068 #else
1069   // custom shader editor
1070 bool g_TextEditor_useCustomEditor = false;
1071 CopiedString g_TextEditor_editorCommand("");
1072 #endif
1073
1074 void DoTextEditor (const char* filename, int cursorpos)
1075 {
1076 #ifdef WIN32
1077   if (g_TextEditor_useWin32Editor)
1078   {
1079     globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1080     ShellExecute((HWND)GDK_WINDOW_HWND (GTK_WIDGET(MainFrame_getWindow())->window), "open", filename, 0, 0, SW_SHOW );
1081     return;
1082   }
1083 #else
1084   // check if a custom editor is set
1085   if(g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty())
1086   {
1087         StringOutputStream strEditCommand(256);
1088     strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1089     
1090     globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1091     // note: linux does not return false if the command failed so it will assume success
1092     if (Q_Exec(0, const_cast<char*>(strEditCommand.c_str()), 0, true) == false)
1093     {
1094       globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1095     }
1096     else
1097     {
1098       // the command (appeared) to run successfully, no need to do anything more
1099       return;
1100     }
1101   }
1102 #endif
1103   
1104   DoGtkTextEditor (filename, cursorpos);
1105 }