added string-pooling for shader variable names and entity keys
[xonotic/netradiant.git] / radiant / main.cpp
1 /*
2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 /*! \mainpage GtkRadiant Documentation Index
23
24 \section intro_sec Introduction
25
26 This documentation is generated from comments in the source code.
27
28 \section links_sec Useful Links
29
30 \link include/itextstream.h include/itextstream.h \endlink - Global output and error message streams, similar to std::cout and std::cerr. \n
31
32 FileInputStream - similar to std::ifstream (binary mode) \n
33 FileOutputStream - similar to std::ofstream (binary mode) \n
34 TextFileInputStream - similar to std::ifstream (text mode) \n
35 TextFileOutputStream - similar to std::ofstream (text mode) \n
36 StringOutputStream - similar to std::stringstream \n
37
38 \link string/string.h string/string.h \endlink - C-style string comparison and memory management. \n
39 \link os/path.h os/path.h \endlink - Path manipulation for radiant's standard path format \n
40 \link os/file.h os/file.h \endlink - OS file-system access. \n
41
42 ::CopiedString - automatic string memory management \n
43 Array - automatic array memory management \n
44 HashTable - generic hashtable, similar to std::hash_map \n
45
46 \link math/vector.h math/vector.h \endlink - Vectors \n
47 \link math/matrix.h math/matrix.h \endlink - Matrices \n
48 \link math/quaternion.h math/quaternion.h \endlink - Quaternions \n
49 \link math/plane.h math/plane.h \endlink - Planes \n
50 \link math/aabb.h math/aabb.h \endlink - AABBs \n
51
52 Callback MemberCaller FunctionCaller - callbacks similar to using boost::function with boost::bind \n
53 SmartPointer SmartReference - smart-pointer and smart-reference similar to Loki's SmartPtr \n
54
55 \link generic/bitfield.h generic/bitfield.h \endlink - Type-safe bitfield \n
56 \link generic/enumeration.h generic/enumeration.h \endlink - Type-safe enumeration \n
57
58 DefaultAllocator - Memory allocation using new/delete, compliant with std::allocator interface \n
59
60 \link debugging/debugging.h debugging/debugging.h \endlink - Debugging macros \n
61
62 */
63
64 #include "main.h"
65
66 #include "version.h"
67
68 #include "debugging/debugging.h"
69
70 #include "iundo.h"
71
72 #include <gtk/gtkmain.h>
73
74 #include "cmdlib.h"
75 #include "os/file.h"
76 #include "os/path.h"
77 #include "stream/stringstream.h"
78 #include "stream/textfilestream.h"
79
80 #include "gtkutil/messagebox.h"
81 #include "gtkutil/image.h"
82 #include "console.h"
83 #include "texwindow.h"
84 #include "map.h"
85 #include "mainframe.h"
86 #include "commands.h"
87 #include "preferences.h"
88 #include "environment.h"
89 #include "referencecache.h"
90 #include "stacktrace.h"
91
92 void show_splash();
93 void hide_splash();
94
95 void error_redirect (const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
96 {
97   gboolean in_recursion;
98   gboolean is_fatal;
99   char buf[256];
100
101   in_recursion = (log_level & G_LOG_FLAG_RECURSION) != 0;
102   is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0;
103   log_level = (GLogLevelFlags) (log_level & G_LOG_LEVEL_MASK);
104
105   if (!message)
106     message = "(0) message";
107
108   if (domain)
109     strcpy (buf, domain);
110   else
111     strcpy (buf, "**");
112   strcat (buf, "-");
113
114   switch (log_level)
115   {
116   case G_LOG_LEVEL_ERROR:
117     if (in_recursion)
118       strcat (buf, "ERROR (recursed) **: ");
119     else
120       strcat (buf, "ERROR **: ");
121     break;
122   case G_LOG_LEVEL_CRITICAL:
123     if (in_recursion)
124       strcat (buf, "CRITICAL (recursed) **: ");
125     else
126       strcat (buf, "CRITICAL **: ");
127     break;
128   case G_LOG_LEVEL_WARNING:
129     if (in_recursion)
130       strcat (buf, "WARNING (recursed) **: ");
131     else
132       strcat (buf, "WARNING **: ");
133     break;
134   case G_LOG_LEVEL_MESSAGE:
135     if (in_recursion)
136       strcat (buf, "Message (recursed): ");
137     else
138       strcat (buf, "Message: ");
139     break;
140   case G_LOG_LEVEL_INFO:
141     if (in_recursion)
142       strcat (buf, "INFO (recursed): ");
143     else
144       strcat (buf, "INFO: ");
145     break;
146   case G_LOG_LEVEL_DEBUG:
147     if (in_recursion)
148       strcat (buf, "DEBUG (recursed): ");
149     else
150       strcat (buf, "DEBUG: ");
151     break;
152   default:
153     /* we are used for a log level that is not defined by GLib itself,
154      * try to make the best out of it.
155      */
156     if (in_recursion)
157       strcat (buf, "LOG (recursed:");
158     else
159       strcat (buf, "LOG (");
160     if (log_level)
161     {
162       gchar string[] = "0x00): ";
163       gchar *p = string + 2;
164       guint i;
165
166       i = g_bit_nth_msf (log_level, -1);
167       *p = i >> 4;
168       p++;
169       *p = '0' + (i & 0xf);
170       if (*p > '9')
171         *p += 'A' - '9' - 1;
172
173       strcat (buf, string);
174     } else
175       strcat (buf, "): ");
176   }
177
178   strcat (buf, message);
179   if (is_fatal)
180     strcat (buf, "\naborting...\n");
181   else
182     strcat (buf, "\n");
183
184   printf ("%s\n", buf);
185
186   ERROR_MESSAGE("GTK+ error: " << buf);
187 }
188
189 #if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER)
190 #include "crtdbg.h"
191 #endif
192
193 void crt_init()
194 {
195 #if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER)
196   _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
197 #endif
198 }
199
200 class Lock
201 {
202   bool m_locked;
203 public:
204   Lock() : m_locked(false)
205   {
206   }
207   void lock()
208   {
209     m_locked = true;
210   }
211   void unlock()
212   {
213     m_locked = false;
214   }
215   bool locked() const
216   {
217     return m_locked;
218   }
219 };
220
221 class ScopedLock
222 {
223   Lock& m_lock;
224 public:
225   ScopedLock(Lock& lock) : m_lock(lock)
226   {
227     m_lock.lock();
228   }
229   ~ScopedLock()
230   {
231     m_lock.unlock();
232   }
233 };
234
235 class PopupDebugMessageHandler : public DebugMessageHandler
236 {
237   StringOutputStream m_buffer;
238   Lock m_lock;
239 public:
240   TextOutputStream& getOutputStream()
241   {
242     if(!m_lock.locked())
243     {
244       return m_buffer;
245     }
246     return globalErrorStream();
247   }
248   bool handleMessage()
249   {
250     getOutputStream() << "----------------\n";
251     write_stack_trace(getOutputStream());
252     getOutputStream() << "----------------\n";
253     if(!m_lock.locked())
254     {
255       ScopedLock lock(m_lock);
256 #if defined _DEBUG
257       m_buffer << "Break into the debugger?\n";
258       globalErrorStream() << m_buffer.c_str();
259       bool handled = gtk_MessageBox(0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_YESNO, eMB_ICONERROR) == eIDNO;
260       m_buffer.clear();
261       return handled;
262 #else
263       m_buffer << "Please report this error to the developers\n";
264       globalErrorStream() << m_buffer.c_str();
265       gtk_MessageBox(0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_OK, eMB_ICONERROR);
266       m_buffer.clear();
267       return true;
268 #endif
269     }
270     return false;
271   }
272 };
273
274 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
275
276 void streams_init()
277 {
278   GlobalErrorStream::instance().setOutputStream(getSysPrintErrorStream());
279   GlobalOutputStream::instance().setOutputStream(getSysPrintOutputStream());
280 }
281
282 void paths_init()
283 {
284   const char* home = environment_get_home_path();
285   Q_mkdir(home);
286
287   {
288     StringOutputStream path(256);
289     path << home << RADIANT_VERSION << '/';
290     g_strSettingsPath = path.c_str();
291   }
292
293   Q_mkdir(g_strSettingsPath.c_str());
294
295   g_strAppPath = environment_get_app_path();
296
297   // radiant is installed in the parent dir of "tools/"
298   // NOTE: this is not very easy for debugging
299   // maybe add options to lookup in several places?
300   // (for now I had to create symlinks)
301   {
302     StringOutputStream path(256);
303     path << g_strAppPath.c_str() << "bitmaps/";
304     BitmapsPath_set(path.c_str());
305   }
306
307   // we will set this right after the game selection is done
308   g_strGameToolsPath = g_strAppPath;
309 }
310
311 bool check_version_file(const char* filename, const char* version)
312 {
313   TextFileInputStream file(filename);
314   if(!file.failed())
315   {
316     char buf[10];
317     buf[file.read(buf, 9)] = '\0';
318
319     // chomp it (the hard way)
320     int chomp = 0;
321     while(buf[chomp] >= '0' && buf[chomp] <= '9')
322       chomp++;
323     buf[chomp] = '\0';
324
325     return string_equal(buf, version);
326   }
327   return false;
328 }
329
330 bool check_version()
331 {
332   // a safe check to avoid people running broken installations
333   // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
334   // make something idiot proof and someone will make better idiots, this may be overkill
335   // let's leave it disabled in debug mode in any case
336   // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
337 #ifndef _DEBUG
338 #define CHECK_VERSION
339 #endif
340 #ifdef CHECK_VERSION  
341   // locate and open RADIANT_MAJOR and RADIANT_MINOR
342   bool bVerIsGood = true;
343   {
344     StringOutputStream ver_file_name(256);
345     ver_file_name << AppPath_get() << "RADIANT_MAJOR";
346     bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MAJOR_VERSION);
347   }
348   {
349     StringOutputStream ver_file_name(256);
350     ver_file_name << AppPath_get() << "RADIANT_MINOR";
351     bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MINOR_VERSION);
352   }
353
354   if (!bVerIsGood)
355   {
356     StringOutputStream msg(256);
357     msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
358     "Make sure you run the right/latest editor binary you installed\n"
359     << AppPath_get();
360     gtk_MessageBox(0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONDEFAULT);
361   }
362   return bVerIsGood;
363 #else
364   return true;
365 #endif
366 }
367
368 void create_global_pid()
369 {
370   /*!
371   the global prefs loading / game selection dialog might fail for any reason we don't know about
372   we need to catch when it happens, to cleanup the stateful prefs which might be killing it
373   and to turn on console logging for lookup of the problem
374   this is the first part of the two step .pid system
375   http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
376   */
377   StringOutputStream g_pidFile(256); ///< the global .pid file (only for global part of the startup)
378
379   g_pidFile << SettingsPath_get() << "radiant.pid";
380
381   FILE *pid;
382   pid = fopen (g_pidFile.c_str(), "r");
383   if (pid != 0)
384   {
385     fclose (pid);
386
387     if (remove (g_pidFile.c_str()) == -1)
388     {
389       StringOutputStream msg(256);
390       msg << "WARNING: Could not delete " << g_pidFile.c_str();
391       gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
392     }
393
394     // in debug, never prompt to clean registry, turn console logging auto after a failed start
395 #if !defined(_DEBUG)
396     StringOutputStream msg(256);
397     msg << "Radiant failed to start properly the last time it was run.\n"
398            "The failure may be related to current global preferences.\n"
399            "Do you want to reset global preferences to defaults?";
400
401     if (gtk_MessageBox (0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION) == eIDYES)
402     {
403       g_GamesDialog.Reset();
404     }
405
406     msg.clear();
407     msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
408
409     gtk_MessageBox (0, msg.c_str(), "Radiant - Console Log", eMB_OK);
410 #endif
411
412     // set without saving, the class is not in a coherent state yet
413     // just do the value change and call to start logging, CGamesDialog will pickup when relevant
414     g_GamesDialog.m_bForceLogConsole = true;
415     Sys_LogFile(true);
416   }
417
418   // create a primary .pid for global init run
419   pid = fopen (g_pidFile.c_str(), "w");
420   if (pid)
421     fclose (pid);
422 }
423
424 void remove_global_pid()
425 {
426   StringOutputStream g_pidFile(256);
427   g_pidFile << SettingsPath_get() << "radiant.pid";
428
429   // close the primary
430   if (remove (g_pidFile.c_str()) == -1)
431   {
432     StringOutputStream msg(256);
433     msg << "WARNING: Could not delete " << g_pidFile.c_str();
434     gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
435   }
436 }
437
438 /*!
439 now the secondary game dependant .pid file
440 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
441 */
442 void create_local_pid()
443 {
444   StringOutputStream g_pidGameFile(256); ///< the game-specific .pid file
445   g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
446
447   FILE *pid = fopen (g_pidGameFile.c_str(), "r");
448   if (pid != 0)
449   {
450     fclose (pid);
451     if (remove (g_pidGameFile.c_str()) == -1)
452     {
453       StringOutputStream msg;
454       msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
455       gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
456     }
457
458     // in debug, never prompt to clean registry, turn console logging auto after a failed start
459 #if !defined(_DEBUG)
460     StringOutputStream msg;
461     msg << "Radiant failed to start properly the last time it was run.\n"
462            "The failure may be caused by current preferences.\n"
463            "Do you want to reset all preferences to defaults?";
464
465     if (gtk_MessageBox (0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION) == eIDYES)
466     {
467       Preferences_Reset();
468     }
469
470     msg.clear();
471     msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
472
473     gtk_MessageBox (0, msg.c_str(), "Radiant - Console Log", eMB_OK);
474 #endif
475
476     // force console logging on! (will go in prefs too)
477     g_GamesDialog.m_bForceLogConsole = true;
478     Sys_LogFile(true);
479   }
480   else
481   {
482     // create one, will remove right after entering message loop
483     pid = fopen (g_pidGameFile.c_str(), "w");
484     if (pid)
485       fclose (pid);
486   }
487 }
488
489
490 /*!
491 now the secondary game dependant .pid file
492 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
493 */
494 void remove_local_pid()
495 {
496   StringOutputStream g_pidGameFile(256);
497   g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
498   remove(g_pidGameFile.c_str());
499 }
500
501 void user_shortcuts_init()
502 {
503   StringOutputStream path(256);
504   path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
505   LoadCommandMap(path.c_str());
506   SaveCommandMap(path.c_str());
507 }
508
509 int main (int argc, char* argv[])
510 {
511   crt_init();
512
513   streams_init();
514
515   gtk_disable_setlocale();
516   gtk_init(&argc, &argv);
517
518   // redirect Gtk warnings to the console
519   g_log_set_handler ("Gdk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
520                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
521   g_log_set_handler ("Gtk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
522                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
523   g_log_set_handler ("GtkGLExt", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
524                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
525   g_log_set_handler ("GLib", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
526                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
527   g_log_set_handler (0, (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
528                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
529
530   GlobalDebugMessageHandler::instance().setHandler(GlobalPopupDebugMessageHandler::instance());
531
532   environment_init(argc, argv);
533
534   paths_init();
535
536   if(!check_version())
537   {
538     return EXIT_FAILURE;
539   }
540
541   show_splash();
542
543   create_global_pid();
544
545   GlobalPreferences_Init();
546
547   g_GamesDialog.Init();
548
549   g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
550   
551   remove_global_pid();
552
553   create_local_pid();
554
555   g_Preferences.Init();
556
557   // in a very particular post-.pid startup
558   // we may have the console turned on and want to keep it that way
559   // so we use a latching system
560   if (g_GamesDialog.m_bForceLogConsole)
561   {
562     Sys_LogFile(true);
563     g_Console_enableLogging = true;
564     g_GamesDialog.m_bForceLogConsole = false;
565   }
566
567
568   Radiant_Initialise();
569
570   global_accel_init();
571
572   user_shortcuts_init();
573
574   g_pParentWnd = 0;
575   g_pParentWnd = new MainFrame();
576
577   hide_splash();
578
579   if (g_bLoadLastMap && !g_strLastMap.empty())
580   {
581     Map_LoadFile(g_strLastMap.c_str());
582   }
583   else
584   {
585     Map_New();
586   }
587
588   // load up shaders now that we have the map loaded
589   // eviltypeguy
590   TextureBrowser_ShowStartupShaders(GlobalTextureBrowser());
591
592
593   remove_local_pid();
594
595   gtk_main();
596
597   // avoid saving prefs when the app is minimized
598   if (g_pParentWnd->IsSleeping())
599   {
600     globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
601     g_preferences_globals.disable_ini = true;
602   }
603
604   Map_Free();
605
606   if (!Map_Unnamed(g_map))
607   {
608     g_strLastMap = Map_Name(g_map);
609   }
610
611   delete g_pParentWnd;
612
613   global_accel_destroy();
614
615   Radiant_Shutdown();
616
617   // close the log file if any
618   Sys_LogFile(false);
619
620   return EXIT_SUCCESS;
621 }
622