]> icculus.org git repositories - divverent/netradiant.git/blob - radiant/main.cpp
add an option -mergeportals to speed up vis, while not making the vis data MUCH worse
[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 void user_shortcuts_save()
549 {
550   StringOutputStream path(256);
551   path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
552   SaveCommandMap(path.c_str());
553 }
554
555 int main (int argc, char* argv[])
556 {
557   crt_init();
558
559   streams_init();
560
561   gtk_disable_setlocale();
562   gtk_init(&argc, &argv);
563
564   // redirect Gtk warnings to the console
565   g_log_set_handler ("Gdk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
566                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
567   g_log_set_handler ("Gtk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
568                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
569   g_log_set_handler ("GtkGLExt", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
570                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
571   g_log_set_handler ("GLib", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
572                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
573   g_log_set_handler (0, (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
574                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG|G_LOG_FLAG_FATAL|G_LOG_FLAG_RECURSION), error_redirect, 0);
575
576   GlobalDebugMessageHandler::instance().setHandler(GlobalPopupDebugMessageHandler::instance());
577
578   environment_init(argc, argv);
579
580   paths_init();
581
582   if(!check_version())
583   {
584     return EXIT_FAILURE;
585   }
586
587   show_splash();
588
589   create_global_pid();
590
591   GlobalPreferences_Init();
592
593   g_GamesDialog.Init();
594
595   g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
596   
597   remove_global_pid();
598
599   g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
600
601   create_local_pid();
602
603   // in a very particular post-.pid startup
604   // we may have the console turned on and want to keep it that way
605   // so we use a latching system
606   if (g_GamesDialog.m_bForceLogConsole)
607   {
608     Sys_LogFile(true);
609     g_Console_enableLogging = true;
610     g_GamesDialog.m_bForceLogConsole = false;
611   }
612
613
614   Radiant_Initialise();
615
616   global_accel_init();
617
618   user_shortcuts_init();
619
620   g_pParentWnd = 0;
621   g_pParentWnd = new MainFrame();
622
623   hide_splash();
624
625   if (g_bLoadLastMap && !g_strLastMap.empty())
626   {
627     Map_LoadFile(g_strLastMap.c_str());
628   }
629   else
630   {
631     Map_New();
632   }
633
634   // load up shaders now that we have the map loaded
635   // eviltypeguy
636   TextureBrowser_ShowStartupShaders(GlobalTextureBrowser());
637
638
639   remove_local_pid();
640
641   gtk_main();
642
643   // avoid saving prefs when the app is minimized
644   if (g_pParentWnd->IsSleeping())
645   {
646     globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
647     g_preferences_globals.disable_ini = true;
648   }
649
650   Map_Free();
651
652   if (!Map_Unnamed(g_map))
653   {
654     g_strLastMap = Map_Name(g_map);
655   }
656
657   delete g_pParentWnd;
658
659   user_shortcuts_save();
660
661   global_accel_destroy();
662
663   Radiant_Shutdown();
664
665   // close the log file if any
666   Sys_LogFile(false);
667
668   return EXIT_SUCCESS;
669 }
670