]> icculus.org git repositories - divverent/netradiant.git/blob - radiant/main.cpp
fix hang in tjunction.c
[divverent/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   // spam it...
185   globalErrorStream() << buf << "\n";
186
187   // FIXME why are warnings is_fatal?
188 #ifndef _DEBUG
189   if(is_fatal)
190 #endif
191   ERROR_MESSAGE("GTK+ error: " << buf);
192 }
193
194 #if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER)
195 #include "crtdbg.h"
196 #endif
197
198 void crt_init()
199 {
200 #if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER)
201   _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
202 #endif
203 }
204
205 class Lock
206 {
207   bool m_locked;
208 public:
209   Lock() : m_locked(false)
210   {
211   }
212   void lock()
213   {
214     m_locked = true;
215   }
216   void unlock()
217   {
218     m_locked = false;
219   }
220   bool locked() const
221   {
222     return m_locked;
223   }
224 };
225
226 class ScopedLock
227 {
228   Lock& m_lock;
229 public:
230   ScopedLock(Lock& lock) : m_lock(lock)
231   {
232     m_lock.lock();
233   }
234   ~ScopedLock()
235   {
236     m_lock.unlock();
237   }
238 };
239
240 class LineLimitedTextOutputStream : public TextOutputStream
241 {
242   TextOutputStream& outputStream;
243   std::size_t count;
244 public:
245   LineLimitedTextOutputStream(TextOutputStream& outputStream, std::size_t count)
246     : outputStream(outputStream), count(count)
247   {
248   }
249   std::size_t write(const char* buffer, std::size_t length)
250   {
251     if(count != 0)
252     {
253       const char* p = buffer;
254       const char* end = buffer+length;
255       for(;;)
256       {
257         p = std::find(p, end, '\n');
258         if(p == end)
259         {
260           break;
261         }
262         ++p;
263         if(--count == 0)
264         {
265           length = p - buffer;
266           break;
267         }
268       }
269       outputStream.write(buffer, length);
270     }
271     return length;
272   }
273 };
274
275 class PopupDebugMessageHandler : public DebugMessageHandler
276 {
277   StringOutputStream m_buffer;
278   Lock m_lock;
279 public:
280   TextOutputStream& getOutputStream()
281   {
282     if(!m_lock.locked())
283     {
284       return m_buffer;
285     }
286     return globalErrorStream();
287   }
288   bool handleMessage()
289   {
290     getOutputStream() << "----------------\n";
291     LineLimitedTextOutputStream outputStream(getOutputStream(), 24);
292     write_stack_trace(outputStream);
293     getOutputStream() << "----------------\n";
294     globalErrorStream() << m_buffer.c_str();
295     if(!m_lock.locked())
296     {
297       ScopedLock lock(m_lock);
298 #if defined _DEBUG
299       m_buffer << "Break into the debugger?\n";
300       bool handled = gtk_MessageBox(0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_YESNO, eMB_ICONERROR) == eIDNO;
301       m_buffer.clear();
302       return handled;
303 #else
304       m_buffer << "Please report this error to the developers\n";
305       gtk_MessageBox(0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_OK, eMB_ICONERROR);
306       m_buffer.clear();
307 #endif
308     }
309     return true;
310   }
311 };
312
313 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
314
315 void streams_init()
316 {
317   GlobalErrorStream::instance().setOutputStream(getSysPrintErrorStream());
318   GlobalOutputStream::instance().setOutputStream(getSysPrintOutputStream());
319 }
320
321 void paths_init()
322 {
323   const char* home = environment_get_home_path();
324   Q_mkdir(home);
325
326   {
327     StringOutputStream path(256);
328     path << home << "1." << RADIANT_MAJOR_VERSION "." << RADIANT_MINOR_VERSION << '/';
329     g_strSettingsPath = path.c_str();
330   }
331
332   Q_mkdir(g_strSettingsPath.c_str());
333
334   g_strAppPath = environment_get_app_path();
335
336   // radiant is installed in the parent dir of "tools/"
337   // NOTE: this is not very easy for debugging
338   // maybe add options to lookup in several places?
339   // (for now I had to create symlinks)
340   {
341     StringOutputStream path(256);
342     path << g_strAppPath.c_str() << "bitmaps/";
343     BitmapsPath_set(path.c_str());
344   }
345
346   // we will set this right after the game selection is done
347   g_strGameToolsPath = g_strAppPath;
348 }
349
350 bool check_version_file(const char* filename, const char* version)
351 {
352   TextFileInputStream file(filename);
353   if(!file.failed())
354   {
355     char buf[10];
356     buf[file.read(buf, 9)] = '\0';
357
358     // chomp it (the hard way)
359     int chomp = 0;
360     while(buf[chomp] >= '0' && buf[chomp] <= '9')
361       chomp++;
362     buf[chomp] = '\0';
363
364     return string_equal(buf, version);
365   }
366   return false;
367 }
368
369 bool check_version()
370 {
371   // a safe check to avoid people running broken installations
372   // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
373   // make something idiot proof and someone will make better idiots, this may be overkill
374   // let's leave it disabled in debug mode in any case
375   // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
376 #ifndef _DEBUG
377 #define CHECK_VERSION
378 #endif
379 #ifdef CHECK_VERSION  
380   // locate and open RADIANT_MAJOR and RADIANT_MINOR
381   bool bVerIsGood = true;
382   {
383     StringOutputStream ver_file_name(256);
384     ver_file_name << AppPath_get() << "RADIANT_MAJOR";
385     bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MAJOR_VERSION);
386   }
387   {
388     StringOutputStream ver_file_name(256);
389     ver_file_name << AppPath_get() << "RADIANT_MINOR";
390     bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MINOR_VERSION);
391   }
392
393   if (!bVerIsGood)
394   {
395     StringOutputStream msg(256);
396     msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
397     "Make sure you run the right/latest editor binary you installed\n"
398     << AppPath_get();
399     gtk_MessageBox(0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONDEFAULT);
400   }
401   return bVerIsGood;
402 #else
403   return true;
404 #endif
405 }
406
407 void create_global_pid()
408 {
409   /*!
410   the global prefs loading / game selection dialog might fail for any reason we don't know about
411   we need to catch when it happens, to cleanup the stateful prefs which might be killing it
412   and to turn on console logging for lookup of the problem
413   this is the first part of the two step .pid system
414   http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
415   */
416   StringOutputStream g_pidFile(256); ///< the global .pid file (only for global part of the startup)
417
418   g_pidFile << SettingsPath_get() << "radiant.pid";
419
420   FILE *pid;
421   pid = fopen (g_pidFile.c_str(), "r");
422   if (pid != 0)
423   {
424     fclose (pid);
425
426     if (remove (g_pidFile.c_str()) == -1)
427     {
428       StringOutputStream msg(256);
429       msg << "WARNING: Could not delete " << g_pidFile.c_str();
430       gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
431     }
432
433     // in debug, never prompt to clean registry, turn console logging auto after a failed start
434 #if !defined(_DEBUG)
435     StringOutputStream msg(256);
436     msg << "Radiant failed to start properly the last time it was run.\n"
437            "The failure may be related to current global preferences.\n"
438            "Do you want to reset global preferences to defaults?";
439
440     if (gtk_MessageBox (0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION) == eIDYES)
441     {
442       g_GamesDialog.Reset();
443     }
444
445     msg.clear();
446     msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
447
448     gtk_MessageBox (0, msg.c_str(), "Radiant - Console Log", eMB_OK);
449 #endif
450
451     // set without saving, the class is not in a coherent state yet
452     // just do the value change and call to start logging, CGamesDialog will pickup when relevant
453     g_GamesDialog.m_bForceLogConsole = true;
454     Sys_LogFile(true);
455   }
456
457   // create a primary .pid for global init run
458   pid = fopen (g_pidFile.c_str(), "w");
459   if (pid)
460     fclose (pid);
461 }
462
463 void remove_global_pid()
464 {
465   StringOutputStream g_pidFile(256);
466   g_pidFile << SettingsPath_get() << "radiant.pid";
467
468   // close the primary
469   if (remove (g_pidFile.c_str()) == -1)
470   {
471     StringOutputStream msg(256);
472     msg << "WARNING: Could not delete " << g_pidFile.c_str();
473     gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
474   }
475 }
476
477 /*!
478 now the secondary game dependant .pid file
479 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
480 */
481 void create_local_pid()
482 {
483   StringOutputStream g_pidGameFile(256); ///< the game-specific .pid file
484   g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
485
486   FILE *pid = fopen (g_pidGameFile.c_str(), "r");
487   if (pid != 0)
488   {
489     fclose (pid);
490     if (remove (g_pidGameFile.c_str()) == -1)
491     {
492       StringOutputStream msg;
493       msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
494       gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
495     }
496
497     // in debug, never prompt to clean registry, turn console logging auto after a failed start
498 #if !defined(_DEBUG)
499     StringOutputStream msg;
500     msg << "Radiant failed to start properly the last time it was run.\n"
501            "The failure may be caused by current preferences.\n"
502            "Do you want to reset all preferences to defaults?";
503
504     if (gtk_MessageBox (0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION) == eIDYES)
505     {
506       Preferences_Reset();
507     }
508
509     msg.clear();
510     msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
511
512     gtk_MessageBox (0, msg.c_str(), "Radiant - Console Log", eMB_OK);
513 #endif
514
515     // force console logging on! (will go in prefs too)
516     g_GamesDialog.m_bForceLogConsole = true;
517     Sys_LogFile(true);
518   }
519   else
520   {
521     // create one, will remove right after entering message loop
522     pid = fopen (g_pidGameFile.c_str(), "w");
523     if (pid)
524       fclose (pid);
525   }
526 }
527
528
529 /*!
530 now the secondary game dependant .pid file
531 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
532 */
533 void remove_local_pid()
534 {
535   StringOutputStream g_pidGameFile(256);
536   g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
537   remove(g_pidGameFile.c_str());
538 }
539
540 void user_shortcuts_init()
541 {
542   StringOutputStream path(256);
543   path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
544   LoadCommandMap(path.c_str());
545   SaveCommandMap(path.c_str());
546 }
547
548 int main (int argc, char* argv[])
549 {
550   crt_init();
551
552   streams_init();
553
554   gtk_disable_setlocale();
555   gtk_init(&argc, &argv);
556
557   // redirect Gtk warnings to the console
558   g_log_set_handler ("Gdk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
559                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
560   g_log_set_handler ("Gtk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
561                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
562   g_log_set_handler ("GtkGLExt", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
563                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
564   g_log_set_handler ("GLib", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
565                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
566   g_log_set_handler (0, (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
567                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
568
569   GlobalDebugMessageHandler::instance().setHandler(GlobalPopupDebugMessageHandler::instance());
570
571   environment_init(argc, argv);
572
573   paths_init();
574
575   if(!check_version())
576   {
577     return EXIT_FAILURE;
578   }
579
580   show_splash();
581
582   create_global_pid();
583
584   GlobalPreferences_Init();
585
586   g_GamesDialog.Init();
587
588   g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
589   
590   remove_global_pid();
591
592   g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
593
594   create_local_pid();
595
596   // in a very particular post-.pid startup
597   // we may have the console turned on and want to keep it that way
598   // so we use a latching system
599   if (g_GamesDialog.m_bForceLogConsole)
600   {
601     Sys_LogFile(true);
602     g_Console_enableLogging = true;
603     g_GamesDialog.m_bForceLogConsole = false;
604   }
605
606
607   Radiant_Initialise();
608
609   global_accel_init();
610
611   user_shortcuts_init();
612
613   g_pParentWnd = 0;
614   g_pParentWnd = new MainFrame();
615
616   hide_splash();
617
618   if (g_bLoadLastMap && !g_strLastMap.empty())
619   {
620     Map_LoadFile(g_strLastMap.c_str());
621   }
622   else
623   {
624     Map_New();
625   }
626
627   // load up shaders now that we have the map loaded
628   // eviltypeguy
629   TextureBrowser_ShowStartupShaders(GlobalTextureBrowser());
630
631
632   remove_local_pid();
633
634   gtk_main();
635
636   // avoid saving prefs when the app is minimized
637   if (g_pParentWnd->IsSleeping())
638   {
639     globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
640     g_preferences_globals.disable_ini = true;
641   }
642
643   Map_Free();
644
645   if (!Map_Unnamed(g_map))
646   {
647     g_strLastMap = Map_Name(g_map);
648   }
649
650   delete g_pParentWnd;
651
652   global_accel_destroy();
653
654   Radiant_Shutdown();
655
656   // close the log file if any
657   Sys_LogFile(false);
658
659   return EXIT_SUCCESS;
660 }
661