2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
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.
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.
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
22 /*! \mainpage GtkRadiant Documentation Index
24 \section intro_sec Introduction
26 This documentation is generated from comments in the source code.
28 \section links_sec Useful Links
30 \link include/itextstream.h include/itextstream.h \endlink - Global output and error message streams, similar to std::cout and std::cerr. \n
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
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
42 ::CopiedString - automatic string memory management \n
43 Array - automatic array memory management \n
44 HashTable - generic hashtable, similar to std::hash_map \n
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
52 Callback MemberCaller0 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
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
58 DefaultAllocator - Memory allocation using new/delete, compliant with std::allocator interface \n
60 \link debugging/debugging.h debugging/debugging.h \endlink - Debugging macros \n
65 #include "globaldefs.h"
69 #include "debugging/debugging.h"
73 #include "uilib/uilib.h"
78 #include "stream/stringstream.h"
79 #include "stream/textfilestream.h"
81 #include "gtkutil/messagebox.h"
82 #include "gtkutil/image.h"
84 #include "texwindow.h"
86 #include "mainframe.h"
88 #include "preferences.h"
89 #include "environment.h"
90 #include "referencecache.h"
91 #include "stacktrace.h"
100 void error_redirect( const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data ){
101 gboolean in_recursion;
105 in_recursion = ( log_level & G_LOG_FLAG_RECURSION ) != 0;
106 is_fatal = ( log_level & G_LOG_FLAG_FATAL ) != 0;
107 log_level = (GLogLevelFlags) ( log_level & G_LOG_LEVEL_MASK );
110 message = "(0) message";
114 strcpy( buf, domain );
123 case G_LOG_LEVEL_ERROR:
124 if ( in_recursion ) {
125 strcat( buf, "ERROR (recursed) **: " );
128 strcat( buf, "ERROR **: " );
131 case G_LOG_LEVEL_CRITICAL:
132 if ( in_recursion ) {
133 strcat( buf, "CRITICAL (recursed) **: " );
136 strcat( buf, "CRITICAL **: " );
139 case G_LOG_LEVEL_WARNING:
140 if ( in_recursion ) {
141 strcat( buf, "WARNING (recursed) **: " );
144 strcat( buf, "WARNING **: " );
147 case G_LOG_LEVEL_MESSAGE:
148 if ( in_recursion ) {
149 strcat( buf, "Message (recursed): " );
152 strcat( buf, "Message: " );
155 case G_LOG_LEVEL_INFO:
156 if ( in_recursion ) {
157 strcat( buf, "INFO (recursed): " );
160 strcat( buf, "INFO: " );
163 case G_LOG_LEVEL_DEBUG:
164 if ( in_recursion ) {
165 strcat( buf, "DEBUG (recursed): " );
168 strcat( buf, "DEBUG: " );
172 /* we are used for a log level that is not defined by GLib itself,
173 * try to make the best out of it.
175 if ( in_recursion ) {
176 strcat( buf, "LOG (recursed:" );
179 strcat( buf, "LOG (" );
182 gchar string[] = "0x00): ";
183 gchar *p = string + 2;
186 i = g_bit_nth_msf( log_level, -1 );
189 *p = '0' + ( i & 0xf );
194 strcat( buf, string );
197 strcat( buf, "): " );
201 strcat( buf, message );
203 strcat( buf, "\naborting...\n" );
210 globalErrorStream() << buf << "\n";
213 ERROR_MESSAGE( "GTK+ error: " << buf );
217 #if GDEF_COMPILER_MSVC && GDEF_DEBUG
222 #if GDEF_COMPILER_MSVC && GDEF_DEBUG
223 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
231 Lock() : m_locked( false ){
239 bool locked() const {
248 ScopedLock( Lock& lock ) : m_lock( lock ){
256 class LineLimitedTextOutputStream : public TextOutputStream
258 TextOutputStream& outputStream;
261 LineLimitedTextOutputStream( TextOutputStream& outputStream, std::size_t count )
262 : outputStream( outputStream ), count( count ){
264 std::size_t write( const char* buffer, std::size_t length ){
266 const char* p = buffer;
267 const char* end = buffer + length;
270 p = std::find( p, end, '\n' );
275 if ( --count == 0 ) {
280 outputStream.write( buffer, length );
286 class PopupDebugMessageHandler : public DebugMessageHandler
288 StringOutputStream m_buffer;
291 TextOutputStream& getOutputStream(){
292 if ( !m_lock.locked() ) {
295 return globalErrorStream();
297 bool handleMessage(){
298 getOutputStream() << "----------------\n";
299 LineLimitedTextOutputStream outputStream( getOutputStream(), 24 );
300 write_stack_trace( outputStream );
301 getOutputStream() << "----------------\n";
302 globalErrorStream() << m_buffer.c_str();
303 if ( !m_lock.locked() ) {
304 ScopedLock lock( m_lock );
306 m_buffer << "Break into the debugger?\n";
307 bool handled = ui::alert(ui::root, m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::YESNO, ui::alert_icon::Error) == ui::alert_response::NO;
311 m_buffer << "Please report this error to the developers\n";
312 ui::alert(ui::root, m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::OK, ui::alert_icon::Error);
320 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
323 GlobalErrorStream::instance().setOutputStream( getSysPrintErrorStream() );
324 GlobalOutputStream::instance().setOutputStream( getSysPrintOutputStream() );
328 g_strSettingsPath = environment_get_home_path();
330 Q_mkdir( g_strSettingsPath.c_str() );
332 g_strAppPath = environment_get_app_path();
334 // radiant is installed in the parent dir of "tools/"
335 // NOTE: this is not very easy for debugging
336 // maybe add options to lookup in several places?
337 // (for now I had to create symlinks)
339 StringOutputStream path( 256 );
340 path << g_strAppPath.c_str() << "bitmaps/";
341 BitmapsPath_set( path.c_str() );
344 // we will set this right after the game selection is done
345 g_strGameToolsPath = g_strAppPath;
348 bool check_version_file( const char* filename, const char* version ){
349 TextFileInputStream file( filename );
350 if ( !file.failed() ) {
352 buf[file.read( buf, 9 )] = '\0';
354 // chomp it (the hard way)
356 while ( buf[chomp] >= '0' && buf[chomp] <= '9' )
360 return string_equal( buf, version );
365 bool check_version(){
366 // a safe check to avoid people running broken installations
367 // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
368 // make something idiot proof and someone will make better idiots, this may be overkill
369 // let's leave it disabled in debug mode in any case
370 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
374 // locate and open RADIANT_MAJOR and RADIANT_MINOR
375 bool bVerIsGood = true;
377 StringOutputStream ver_file_name(256);
378 ver_file_name << AppPath_get() << "RADIANT_MAJOR";
379 bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MAJOR_VERSION);
382 StringOutputStream ver_file_name(256);
383 ver_file_name << AppPath_get() << "RADIANT_MINOR";
384 bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MINOR_VERSION);
388 StringOutputStream msg(256);
390 << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
391 "Make sure you run the right/latest editor binary you installed\n"
393 ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Default);
398 void create_global_pid(){
400 the global prefs loading / game selection dialog might fail for any reason we don't know about
401 we need to catch when it happens, to cleanup the stateful prefs which might be killing it
402 and to turn on console logging for lookup of the problem
403 this is the first part of the two step .pid system
404 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
406 StringOutputStream g_pidFile( 256 ); ///< the global .pid file (only for global part of the startup)
408 g_pidFile << SettingsPath_get() << "radiant.pid";
411 pid = fopen( g_pidFile.c_str(), "r" );
415 if ( remove( g_pidFile.c_str() ) == -1 ) {
416 StringOutputStream msg( 256 );
417 msg << "WARNING: Could not delete " << g_pidFile.c_str();
418 ui::alert( ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
421 // in debug, never prompt to clean registry, turn console logging auto after a failed start
423 StringOutputStream msg(256);
424 msg << "Radiant failed to start properly the last time it was run.\n"
425 "The failure may be related to current global preferences.\n"
426 "Do you want to reset global preferences to defaults?";
428 if (ui::alert(ui::root, msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question) == ui::alert_response::YES) {
429 g_GamesDialog.Reset();
433 msg << "Logging console output to " << SettingsPath_get()
434 << "radiant.log\nRefer to the log if Radiant fails to start again.";
436 ui::alert(ui::root, msg.c_str(), "Radiant - Console Log", ui::alert_type::OK);
439 // set without saving, the class is not in a coherent state yet
440 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
441 g_GamesDialog.m_bForceLogConsole = true;
445 // create a primary .pid for global init run
446 pid = fopen( g_pidFile.c_str(), "w" );
452 void remove_global_pid(){
453 StringOutputStream g_pidFile( 256 );
454 g_pidFile << SettingsPath_get() << "radiant.pid";
457 if ( remove( g_pidFile.c_str() ) == -1 ) {
458 StringOutputStream msg( 256 );
459 msg << "WARNING: Could not delete " << g_pidFile.c_str();
460 ui::alert( ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
465 now the secondary game dependant .pid file
466 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
468 void create_local_pid(){
469 StringOutputStream g_pidGameFile( 256 ); ///< the game-specific .pid file
470 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
472 FILE *pid = fopen( g_pidGameFile.c_str(), "r" );
475 if ( remove( g_pidGameFile.c_str() ) == -1 ) {
476 StringOutputStream msg;
477 msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
478 ui::alert( ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
481 // in debug, never prompt to clean registry, turn console logging auto after a failed start
483 StringOutputStream msg;
484 msg << "Radiant failed to start properly the last time it was run.\n"
485 "The failure may be caused by current preferences.\n"
486 "Do you want to reset all preferences to defaults?";
488 if (ui::alert(ui::root, msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question) == ui::alert_response::YES) {
493 msg << "Logging console output to " << SettingsPath_get()
494 << "radiant.log\nRefer to the log if Radiant fails to start again.";
496 ui::alert(ui::root, msg.c_str(), "Radiant - Console Log", ui::alert_type::OK);
499 // force console logging on! (will go in prefs too)
500 g_GamesDialog.m_bForceLogConsole = true;
505 // create one, will remove right after entering message loop
506 pid = fopen( g_pidGameFile.c_str(), "w" );
515 now the secondary game dependant .pid file
516 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
518 void remove_local_pid(){
519 StringOutputStream g_pidGameFile( 256 );
520 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
521 remove( g_pidGameFile.c_str() );
524 void user_shortcuts_init(){
525 StringOutputStream path( 256 );
526 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
527 LoadCommandMap( path.c_str() );
528 SaveCommandMap( path.c_str() );
531 void user_shortcuts_save(){
532 StringOutputStream path( 256 );
533 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
534 SaveCommandMap( path.c_str() );
537 void add_local_rc_files(){
538 #define GARUX_DISABLE_GTKTHEME
539 #ifndef GARUX_DISABLE_GTKTHEME
540 /* FIXME: HACK: not GTK3 compatible
541 https://developer.gnome.org/gtk2/stable/gtk2-Resource-Files.html#gtk-rc-add-default-file
542 https://developer.gnome.org/gtk3/stable/gtk3-Resource-Files.html#gtk-rc-add-default-file
543 > gtk_rc_add_default_file has been deprecated since version 3.0 and should not be used in newly-written code.
544 > Use GtkStyleContext with a custom GtkStyleProvider instead
548 StringOutputStream path( 512 );
549 path << AppPath_get() << ".gtkrc-2.0.radiant";
550 gtk_rc_add_default_file( path.c_str() );
554 StringOutputStream path( 512 );
555 path << AppPath_get() << ".gtkrc-2.0.win";
556 gtk_rc_add_default_file( path.c_str() );
559 #endif // GARUX_DISABLE_GTKTHEME
562 int main( int argc, char* argv[] ){
569 lib = LoadLibrary( "dwmapi.dll" );
571 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
572 if ( qDwmEnableComposition ) {
574 for ( int i = 1; i < argc; ++i ){
575 if ( !stricmp( argv[i], "-aero" ) ){
577 qDwmEnableComposition( TRUE );
583 qDwmEnableComposition( FALSE );
591 const char* mapname = NULL;
592 char const *error = NULL;
593 if ( !ui::init( &argc, &argv, "<filename.map>", &error) ) {
594 g_print( "%s\n", error );
598 // Gtk already removed parsed `--options`
600 if ( strlen( argv[1] ) > 1 ) {
601 if ( g_str_has_suffix( argv[1], ".map" ) ) {
602 if ( g_path_is_absolute( argv[1] ) ) {
606 mapname = g_build_filename( g_get_current_dir(), argv[1], NULL );
610 g_print( "bad file name, will not load: %s\n", argv[1] );
615 g_print ( "%s\n", "too many arguments" );
619 // redirect Gtk warnings to the console
620 g_log_set_handler( "Gdk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
621 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
622 g_log_set_handler( "Gtk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
623 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
624 g_log_set_handler( "GtkGLExt", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
625 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
626 g_log_set_handler( "GLib", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
627 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
628 g_log_set_handler( 0, (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
629 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
631 GlobalDebugMessageHandler::instance().setHandler( GlobalPopupDebugMessageHandler::instance() );
633 environment_init(argc, (char const **) argv);
637 add_local_rc_files();
639 if ( !check_version() ) {
647 GlobalPreferences_Init();
649 g_GamesDialog.Init();
651 g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
655 g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
659 // in a very particular post-.pid startup
660 // we may have the console turned on and want to keep it that way
661 // so we use a latching system
662 if ( g_GamesDialog.m_bForceLogConsole ) {
664 g_Console_enableLogging = true;
665 g_GamesDialog.m_bForceLogConsole = false;
669 Radiant_Initialise();
671 user_shortcuts_init();
674 g_pParentWnd = new MainFrame();
679 if( openCmdMap && *openCmdMap ){
680 Map_LoadFile( openCmdMap );
684 if ( mapname != NULL ) {
685 Map_LoadFile( mapname );
687 else if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
688 Map_LoadFile( g_strLastMap.c_str() );
695 // load up shaders now that we have the map loaded
697 TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
704 // avoid saving prefs when the app is minimized
705 if ( g_pParentWnd->IsSleeping() ) {
706 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
707 g_preferences_globals.disable_ini = true;
712 if ( !Map_Unnamed( g_map ) ) {
713 g_strLastMap = Map_Name( g_map );
718 user_shortcuts_save();
722 // close the log file if any
723 Sys_LogFile( false );