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